Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions airflow-core/src/airflow/ui/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { jsoncRules } from "./rules/jsonc.js";
import { perfectionistRules } from "./rules/perfectionist.js";
import { prettierRules } from "./rules/prettier.js";
import { reactRules } from "./rules/react.js";
import { remRules } from "./rules/rem.js";
import { stylisticRules } from "./rules/stylistic.js";
import { typescriptRules } from "./rules/typescript.js";
import { unicornRules } from "./rules/unicorn.js";
Expand All @@ -46,6 +47,7 @@ export default /** @type {const} @satisfies {ReadonlyArray<FlatConfig.Config>} *
prettierRules,
reactRules,
stylisticRules,
remRules,
unicornRules,
i18nextRules,
i18nRules,
Expand Down
150 changes: 150 additions & 0 deletions airflow-core/src/airflow/ui/rules/rem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*!
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { AST_NODE_TYPES } from "@typescript-eslint/utils";

export const remNamespace = "rem";

/**
* Check if a value contains rem units
* @param {string} value
* @returns {boolean}
*/
const containsRem = (value) => /\d+\.?\d*rem/u.test(value);

/**
* Convert rem value to pixels (1rem = 16px)
* @param {string} value
* @returns {string}
*/
const convertRemToPixels = (value) =>
value.replaceAll(/(?<temp1>\d+\.?\d*)rem/gu, (_, number) => {
if (typeof number === "string") {
const pixels = parseFloat(number) * 16;

return `${pixels}px`;
}

return value;
});

export const remPlugin = {
rules: {
"no-rem-in-props": {
/** @param {import('@typescript-eslint/utils').TSESLint.RuleContext<'noRemInProps', []>} context */
create(context) {
/** @param {import('@typescript-eslint/utils').TSESTree.JSXOpeningElement} node */
const checkAttributes = (node) => {
// Check all attributes for rem values in size, width, height props (but not style)
node.attributes.forEach((attr) => {
if (attr.type !== AST_NODE_TYPES.JSXAttribute || !attr.value) {
return;
}

const attrName = attr.name.name;

// Skip style attributes - rem is allowed there
if (attrName === "style") {
return;
}

// Only check size, width, height attributes
if (attrName !== "height" && attrName !== "size" && attrName !== "width") {
return;
}

let attrValue = undefined;

// Handle different attribute value types
if (attr.value.type === AST_NODE_TYPES.Literal) {
attrValue = attr.value.value;
} else if (
attr.value.type === AST_NODE_TYPES.JSXExpressionContainer &&
attr.value.expression.type === AST_NODE_TYPES.Literal
) {
attrValue = attr.value.expression.value;
}

// Check for rem values
if (typeof attrValue === "string" && containsRem(attrValue)) {
const fixedValue = convertRemToPixels(attrValue);

context.report({
data: {
attribute: attrName,
fixedValue,
value: attrValue,
},
fix(fixer) {
// For string literals, replace the entire value
if (attr.value !== null && attr.value.type === AST_NODE_TYPES.Literal) {
return fixer.replaceText(attr.value, `{${fixedValue}}`);
}
// For JSX expressions with literal values, replace just the literal
if (
attr.value !== null &&
attr.value.type === AST_NODE_TYPES.JSXExpressionContainer &&
attr.value.expression.type === AST_NODE_TYPES.Literal
) {
return fixer.replaceText(attr.value.expression, fixedValue);
}

// eslint-disable-next-line unicorn/no-null
return null;
},
messageId: "noRemInProps",
node: attr,
});
}
});
};

return {
/** @param {import('@typescript-eslint/utils').TSESTree.JSXOpeningElement} node */
JSXOpeningElement(node) {
checkAttributes(node);
},
};
},
meta: {
docs: {
category: "Best Practices",
description: "Disallow rem units in size, width, and height attributes (but allow in style)",
recommended: "error",
},
fixable: "code",
messages: {
noRemInProps:
"Avoid using rem units in {{attribute}} attribute. Use numeric pixel values instead of '{{value}}'. Auto-fix available: {{fixedValue}}",
},
type: "problem",
},
},
},
};

/** @type {import("@typescript-eslint/utils/ts-eslint").FlatConfig.Config} */
export const remRules = {
files: ["**/*.tsx"],
plugins: {
[remNamespace]: remPlugin,
},
rules: {
[`${remNamespace}/no-rem-in-props`]: "error",
},
};
2 changes: 1 addition & 1 deletion airflow-core/src/airflow/ui/src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const SearchBar = ({
/>
) : undefined}
{Boolean(hideAdvanced) ? undefined : (
<Button fontWeight="normal" height="1.75rem" variant="ghost" width={140} {...buttonProps}>
<Button fontWeight="normal" height={28} variant="ghost" width={140} {...buttonProps}>
{translate("search.advanced")}
</Button>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const TaskNames = ({ nodes, onRowClick }: Props) => {
px={1}
>
<FiChevronUp
size="1rem"
size={16}
style={{
transform: `rotate(${node.isOpen ? 0 : 180}deg)`,
transition: "transform 0.5s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export const PanelButtons = ({
<Popover.Trigger asChild>
<Button size="sm" variant="outline">
{translate("dag:panel.buttons.options")}
<FiChevronDown size="0.5rem" />
<FiChevronDown size={8} />
</Button>
</Popover.Trigger>
<Portal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const AdminButton = ({
return (
<Menu.Root positioning={{ placement: "right" }}>
<Menu.Trigger asChild>
<NavButton icon={<FiSettings size="1.75rem" />} title={translate("nav.admin")} />
<NavButton icon={<FiSettings size={28} />} title={translate("nav.admin")} />
</Menu.Trigger>
<Menu.Content>
{menuItems}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const BrowseButton = ({
return (
<Menu.Root positioning={{ placement: "right" }}>
<Menu.Trigger asChild>
<NavButton icon={<FiGlobe size="1.75rem" />} title={translate("nav.browse")} />
<NavButton icon={<FiGlobe size={28} />} title={translate("nav.browse")} />
</Menu.Trigger>
<Menu.Content>
{menuItems}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const DocsButton = ({
return (
<Menu.Root positioning={{ placement: "right" }}>
<Menu.Trigger asChild>
<NavButton icon={<FiBookOpen size="1.75rem" />} title={translate("nav.docs")} />
<NavButton icon={<FiBookOpen size={28} />} title={translate("nav.docs")} />
</Menu.Trigger>
<Menu.Content>
{links
Expand Down
6 changes: 3 additions & 3 deletions airflow-core/src/airflow/ui/src/layouts/Nav/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,16 @@ export const Nav = () => {
<AirflowPin height="35px" width="35px" />
</NavLink>
</Box>
<NavButton icon={<FiHome size="1.75rem" />} title={translate("nav.home")} to="/" />
<NavButton icon={<FiHome size="28px" />} title={translate("nav.home")} to="/" />
<NavButton
disabled={!authLinks?.authorized_menu_items.includes("Dags")}
icon={<DagIcon height="1.75rem" width="1.75rem" />}
icon={<DagIcon height="28px" width="28px" />}
title={translate("nav.dags")}
to="dags"
/>
<NavButton
disabled={!authLinks?.authorized_menu_items.includes("Assets")}
icon={<FiDatabase size="1.75rem" />}
icon={<FiDatabase size="28px" />}
title={translate("nav.assets")}
to="assets"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export const PluginMenuItem = ({
const displayIcon = colorMode === "dark" && typeof iconDarkMode === "string" ? iconDarkMode : icon;
const pluginIcon =
typeof displayIcon === "string" ? (
<Image height="1.25rem" mr={topLevel ? 0 : 2} src={displayIcon} width="1.25rem" />
<Image height="20px" mr={topLevel ? 0 : 2} src={displayIcon} width="20px" />
) : urlRoute === "legacy-fab-views" ? (
<RiArchiveStackLine size="1.25rem" style={{ marginRight: topLevel ? 0 : "8px" }} />
<RiArchiveStackLine size="20px" style={{ marginRight: topLevel ? 0 : "8px" }} />
) : (
<LuPlug size="1.25rem" style={{ marginRight: topLevel ? 0 : "8px" }} />
<LuPlug size="20px" style={{ marginRight: topLevel ? 0 : "8px" }} />
);

const isExternal = urlRoute === undefined || urlRoute === null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const SecurityButton = () => {
return (
<Menu.Root positioning={{ placement: "right" }}>
<Menu.Trigger asChild>
<NavButton icon={<FiLock size="1.75rem" />} title={translate("nav.security")} />
<NavButton icon={<FiLock size={28} />} title={translate("nav.security")} />
</Menu.Trigger>
<Menu.Content>
{authLinks.extra_menu_items.map(({ text }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const TimezoneMenuItem = ({ onOpen }: { readonly onOpen: () => void }) =>

return (
<Menu.Item onClick={onOpen} value="timezone">
<FiClock size="1.25rem" style={{ marginRight: "8px" }} />
<FiClock size={20} style={{ marginRight: "8px" }} />
{translate("timezone")}: {dayjs(time).tz(selectedTimezone).format("HH:mm z (Z)")}
</Menu.Item>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ export const UserSettingsButton = ({ externalViews }: { readonly externalViews:
return (
<Menu.Root positioning={{ placement: "right" }}>
<Menu.Trigger asChild>
<NavButton icon={<FiUser size="1.75rem" />} title={translate("user")} />
<NavButton icon={<FiUser size={28} />} title={translate("user")} />
</Menu.Trigger>
<Menu.Content>
<Menu.Item onClick={onOpenLanguage} value="language">
<FiGlobe size="1.25rem" style={{ marginRight: "8px" }} />
<FiGlobe size={20} style={{ marginRight: "8px" }} />
{translate("selectLanguage")}
</Menu.Item>
<Menu.Root>
<Menu.TriggerItem>
<FiEye size="1.25rem" style={{ marginRight: "8px" }} />
<FiEye size={20} style={{ marginRight: "8px" }} />
{translate("appearance.appearance")}
{isRTL ? (
<FiChevronLeft size="1.25rem" style={{ marginRight: "auto" }} />
<FiChevronLeft size={20} style={{ marginRight: "auto" }} />
) : (
<FiChevronRight size="1.25rem" style={{ marginLeft: "auto" }} />
<FiChevronRight size={20} style={{ marginLeft: "auto" }} />
)}
</Menu.TriggerItem>
<Menu.Content>
Expand All @@ -90,17 +90,17 @@ export const UserSettingsButton = ({ externalViews }: { readonly externalViews:
value={theme}
>
<Menu.RadioItem value={COLOR_MODES.LIGHT}>
<FiSun size="1.25rem" style={{ marginRight: "8px" }} />
<FiSun size={20} style={{ marginRight: "8px" }} />
{translate("appearance.lightMode")}
<Menu.ItemIndicator />
</Menu.RadioItem>
<Menu.RadioItem value={COLOR_MODES.DARK}>
<FiMoon size="1.25rem" style={{ marginRight: "8px" }} />
<FiMoon size={20} style={{ marginRight: "8px" }} />
{translate("appearance.darkMode")}
<Menu.ItemIndicator />
</Menu.RadioItem>
<Menu.RadioItem value={COLOR_MODES.SYSTEM}>
<FiMonitor size="1.25rem" style={{ marginRight: "8px" }} />
<FiMonitor size={20} style={{ marginRight: "8px" }} />
{translate("appearance.systemMode")}
<Menu.ItemIndicator />
</Menu.RadioItem>
Expand All @@ -113,12 +113,12 @@ export const UserSettingsButton = ({ externalViews }: { readonly externalViews:
>
{dagView === "grid" ? (
<>
<MdOutlineAccountTree size="1.25rem" style={{ marginRight: "8px" }} />
<MdOutlineAccountTree size={20} style={{ marginRight: "8px" }} />
{translate("defaultToGraphView")}
</>
) : (
<>
<FiGrid size="1.25rem" style={{ marginRight: "8px" }} />
<FiGrid size={20} style={{ marginRight: "8px" }} />
{translate("defaultToGridView")}
</>
)}
Expand All @@ -129,7 +129,7 @@ export const UserSettingsButton = ({ externalViews }: { readonly externalViews:
))}
<Menu.Item onClick={onOpenLogout} value="logout">
<FiLogOut
size="1.25rem"
size={20}
style={{ marginRight: "8px", transform: isRTL ? "rotate(180deg)" : undefined }}
/>
{translate("logout")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const DAGImportErrors = ({ iconOnly = false }: { readonly iconOnly?: bool
onClick={onOpen}
title={translate("importErrors.dagImportError", { count: importErrorsCount })}
>
<LuFileWarning size="0.5rem" />
<LuFileWarning size={8} />
{importErrorsCount}
</StateBadge>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const PluginImportErrors = ({ iconOnly = false }: { readonly iconOnly?: b
onClick={onOpen}
title={translate("plugins.importError", { count: importErrorsCount })}
>
<LuPlug size="0.5rem" />
<LuPlug size={8} />
{importErrorsCount}
</StateBadge>
) : (
Expand Down