This is an automated email from the ASF dual-hosted git repository. ryanahamilton pushed a commit to branch backport-nav-updates-to-v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 577fbed7f9b50d8c97f164bad1c052b8d0634286 Author: Ryan Hamilton <[email protected]> AuthorDate: Thu Oct 30 10:02:15 2025 -0400 Add settings to auto-apply linting, fix linting errors (#57510) --- .gitignore | 2 - .vscode/extensions.json | 5 + .vscode/settings.json | 38 +++++++ .../src/airflow/ui/src/layouts/Nav/Nav.tsx | 6 +- .../src/airflow/ui/src/layouts/Nav/NavButton.tsx | 112 +++++++++++---------- .../airflow/ui/src/layouts/Nav/PluginMenuItem.tsx | 19 ++-- .../src/airflow/ui/src/layouts/Nav/PluginMenus.tsx | 4 +- .../ui/src/layouts/Nav/UserSettingsButton.tsx | 4 +- 8 files changed, 121 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index e7fb2a5403d..1528de626bd 100644 --- a/.gitignore +++ b/.gitignore @@ -171,8 +171,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* -.vscode/* -!.vscode/extensions.json /.vite/ # Exclude the ui .vite dir airflow-core/src/airflow/ui/.vite/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000000..0e6d5fd64e4 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..aeae611e1fe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,38 @@ +{ + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, +} diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx index 5fa459b7959..2203df73880 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx @@ -145,9 +145,9 @@ export const Nav = () => { width={16} zIndex="docked" > - <Flex alignItems="center" flexDir="column" width="100%" gap={1}> - <Box asChild boxSize={14} display="flex" alignItems="center" justifyContent="center"> - <Link to="/" title={translate("nav.home")}> + <Flex alignItems="center" flexDir="column" gap={1} width="100%"> + <Box alignItems="center" asChild boxSize={14} display="flex" justifyContent="center"> + <Link title={translate("nav.home")} to="/"> <AirflowPin _motionSafe={{ _hover: { diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/NavButton.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/NavButton.tsx index 3ebea71bef1..eaa13f57f1b 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/NavButton.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/NavButton.tsx @@ -17,8 +17,8 @@ * under the License. */ import { Box, type BoxProps, Button, Icon, type IconProps, Link, type ButtonProps } from "@chakra-ui/react"; -import { useMemo, type ForwardRefExoticComponent, type RefAttributes } from "react"; -import { IconType } from "react-icons"; +import { type ReactNode, useMemo, type ForwardRefExoticComponent, type RefAttributes } from "react"; +import type { IconType } from "react-icons"; import { Link as RouterLink, useMatch } from "react-router-dom"; const commonLabelProps: BoxProps = { @@ -31,63 +31,73 @@ const commonLabelProps: BoxProps = { }; type NavButtonProps = { - readonly icon: IconType | ForwardRefExoticComponent<IconProps & RefAttributes<SVGSVGElement>>; + readonly icon: ForwardRefExoticComponent<IconProps & RefAttributes<SVGSVGElement>> | IconType; readonly isExternal?: boolean; + readonly pluginIcon?: ReactNode; readonly title: string; readonly to?: string; } & ButtonProps; -export const NavButton = ({ icon, isExternal = false, title, to, ...rest }: NavButtonProps) => { +export const NavButton = ({ icon, isExternal = false, pluginIcon, title, to, ...rest }: NavButtonProps) => { // Use useMatch to determine if the current route matches the button's destination // This provides the same functionality as NavLink's isActive prop // Only applies to buttons with a to prop (but needs to be before any return statements) - const match = to ? useMatch({ - path: to, - end: to === "/" // Only exact match for root path - }) : undefined; + const match = useMatch({ + end: to === "/", // Only exact match for root path + path: to ?? "", + }); // Only applies to buttons with a to prop - const isActive = Boolean(match); + const isActive = Boolean(to) ? Boolean(match) : false; - const commonButtonProps = useMemo<ButtonProps>(() => ({ - _expanded: isActive ? undefined : { - bg: "brand.emphasized", // Even darker for better light mode contrast - color: "fg", - }, - _focus: isActive ? undefined : { - color: "fg", - }, - _hover: isActive ? undefined : { - bg: "brand.emphasized", // Even darker for better light mode contrast - color: "fg", - _active: { - bg: "brand.solid", - color: "white", - }, - }, - alignItems: "center", - bg: isActive ? "brand.solid" : undefined, - borderRadius: "md", - borderWidth: 0, - boxSize: 14, - color: isActive ? "white" : "fg.muted", - colorPalette: "brand", - cursor: "pointer", - flexDir: "column", - gap: 0, - overflow: "hidden", - padding: 0, - textDecoration: "none", - title, - transition: "background-color 0.2s ease, color 0.2s ease", - variant: "plain", - whiteSpace: "wrap", - ...rest, - }), [isActive, rest, title]); + const commonButtonProps = useMemo<ButtonProps>( + () => ({ + _expanded: isActive + ? undefined + : { + bg: "brand.emphasized", // Even darker for better light mode contrast + color: "fg", + }, + _focus: isActive + ? undefined + : { + color: "fg", + }, + _hover: isActive + ? undefined + : { + _active: { + bg: "brand.solid", + color: "white", + }, + bg: "brand.emphasized", // Even darker for better light mode contrast + color: "fg", + }, + alignItems: "center", + bg: isActive ? "brand.solid" : undefined, + borderRadius: "md", + borderWidth: 0, + boxSize: 14, + color: isActive ? "white" : "fg.muted", + colorPalette: "brand", + cursor: "pointer", + flexDir: "column", + gap: 0, + overflow: "hidden", + padding: 0, + textDecoration: "none", + title, + transition: "background-color 0.2s ease, color 0.2s ease", + variant: "plain", + whiteSpace: "wrap", + ...rest, + }), + [isActive, rest, title], + ); if (to === undefined) { return ( <Button {...commonButtonProps}> - <Icon as={icon} boxSize={5} /> + {pluginIcon ?? <Icon as={icon} boxSize={5} />} <Box {...commonLabelProps}>{title}</Box> </Button> ); @@ -95,9 +105,9 @@ export const NavButton = ({ icon, isExternal = false, title, to, ...rest }: NavB if (isExternal) { return ( - <Link href={to} asChild rel="noopener noreferrer" target="_blank"> + <Link asChild href={to} rel="noopener noreferrer" target="_blank"> <Button {...commonButtonProps}> - <Icon as={icon} boxSize={5} /> + {pluginIcon ?? <Icon as={icon} boxSize={5} />} <Box {...commonLabelProps}>{title}</Box> </Button> </Link> @@ -105,13 +115,9 @@ export const NavButton = ({ icon, isExternal = false, title, to, ...rest }: NavB } return ( - <Button - as={Link} - asChild - {...commonButtonProps} - > + <Button as={Link} asChild {...commonButtonProps}> <RouterLink to={to}> - <Icon as={icon} boxSize={5} /> + {pluginIcon ?? <Icon as={icon} boxSize={5} />} <Box {...commonLabelProps}>{title}</Box> </RouterLink> </Button> diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenuItem.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenuItem.tsx index c7372703237..5d36cf59e10 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenuItem.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenuItem.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Link, Image, Menu } from "@chakra-ui/react"; +import { Link, Image, Menu, Icon, Box } from "@chakra-ui/react"; import { FiExternalLink } from "react-icons/fi"; import { LuPlug } from "react-icons/lu"; import { RiArchiveStackLine } from "react-icons/ri"; @@ -46,11 +46,11 @@ export const PluginMenuItem = ({ const displayIcon = colorMode === "dark" && typeof iconDarkMode === "string" ? iconDarkMode : icon; const pluginIcon = typeof displayIcon === "string" ? ( - <Image height="20px" mr={topLevel ? 0 : 2} src={displayIcon} width="20px" /> + <Image boxSize={5} src={displayIcon} /> ) : urlRoute === "legacy-fab-views" ? ( - <RiArchiveStackLine size="20px" style={{ marginRight: topLevel ? 0 : "8px" }} /> + <Icon as={RiArchiveStackLine} boxSize={5} /> ) : ( - <LuPlug size="20px" style={{ marginRight: topLevel ? 0 : "8px" }} /> + <Icon as={LuPlug} boxSize={5} /> ); const isExternal = urlRoute === undefined || urlRoute === null; @@ -58,9 +58,10 @@ export const PluginMenuItem = ({ if (topLevel) { return ( <NavButton - icon={pluginIcon} + icon={LuPlug} isExternal={isExternal} key={name} + pluginIcon={pluginIcon} title={name} to={isExternal ? href : `plugin/${urlRoute}`} /> @@ -80,13 +81,15 @@ export const PluginMenuItem = ({ width="100%" > {pluginIcon} - {name} - <FiExternalLink /> + <Box flex="1">{name}</Box> + <Icon as={FiExternalLink} boxSize={4} color="fg.muted" /> </Link> ) : ( <RouterLink style={{ outline: "none" }} to={`plugin/${urlRoute}`}> {pluginIcon} - {name} + <Box flex="1" ml={2}> + {name} + </Box> </RouterLink> )} </Menu.Item> diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenus.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenus.tsx index c8727804890..9ac31f6947f 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenus.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/PluginMenus.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Box } from "@chakra-ui/react"; +import { Box, Icon } from "@chakra-ui/react"; import { useTranslation } from "react-i18next"; import { FiChevronRight } from "react-icons/fi"; import { LuPlug } from "react-icons/lu"; @@ -63,7 +63,7 @@ export const PluginMenus = ({ navItems }: { readonly navItems: Array<NavItemResp <Menu.Root key={key} positioning={{ placement: "right" }}> <Menu.TriggerItem display="flex" justifyContent="space-between"> {key} - <FiChevronRight /> + <Icon as={FiChevronRight} boxSize={4} color="fg.muted" /> </Menu.TriggerItem> <Menu.Content> {menuButtons.map((navItem) => ( diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx index 15ee91b0422..84d8c385fed 100644 --- a/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx +++ b/airflow-core/src/airflow/ui/src/layouts/Nav/UserSettingsButton.tsx @@ -121,7 +121,9 @@ export const UserSettingsButton = ({ externalViews }: { readonly externalViews: value={dagView} > <Icon as={dagView === "grid" ? MdOutlineAccountTree : FiGrid} boxSize={4} /> - <Box flex="1">{dagView === "grid" ? translate("defaultToGraphView") : translate("defaultToGridView")}</Box> + <Box flex="1"> + {dagView === "grid" ? translate("defaultToGraphView") : translate("defaultToGridView")} + </Box> </Menu.Item> <TimezoneMenuItem onOpen={onOpenTimezone} /> {externalViews.map((view) => (
