From dcb6752f84af2c2930047f863f545449e9880da8 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Sun, 13 Sep 2026 21:33:54 +0530 Subject: [PATCH 1/6] fix(ui): use semantic alert severity borders --- .../src/components/base/alert/alert.test.tsx | 34 +++++++++++++++++++ .../ui/src/components/base/alert/alert.tsx | 8 +++-- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.test.tsx diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.test.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.test.tsx new file mode 100644 index 000000000000..b1ecdc9b0980 --- /dev/null +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2026 Collate. + * Licensed 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 { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { Alert, AlertVariant } from './alert'; + +describe('Alert theme semantics', () => { + it.each<[AlertVariant, string, string]>([ + ['success', 'tw:bg-success-primary', 'tw:border-success-subtle'], + ['warning', 'tw:bg-warning-primary', 'tw:border-warning-subtle'], + ['error', 'tw:bg-error-primary', 'tw:border-error-subtle'], + ])( + 'uses semantic surface and border roles for the %s variant', + (variant, backgroundClass, borderClass) => { + render(); + + expect(screen.getByRole('alert')).toHaveClass( + backgroundClass, + borderClass + ); + } + ); +}); diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.tsx index f6f5bdf13d71..87c15af2bc18 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/alert/alert.tsx @@ -34,18 +34,20 @@ const variantStyles: Record< defaultIcon: FC<{ className?: string }>; } > = { + // Severity roles preserve the intended contrast on each theme surface; + // palette steps invert independently and are reserved for data-bound color. success: { - root: 'tw:border-utility-success-300 tw:bg-success-primary', + root: 'tw:border-success-subtle tw:bg-success-primary', iconColor: 'success', defaultIcon: CheckCircle, }, warning: { - root: 'tw:border-utility-warning-300 tw:bg-warning-primary', + root: 'tw:border-warning-subtle tw:bg-warning-primary', iconColor: 'warning', defaultIcon: AlertTriangle, }, error: { - root: 'tw:border-utility-error-300 tw:bg-error-primary', + root: 'tw:border-error-subtle tw:bg-error-primary', iconColor: 'error', defaultIcon: AlertCircle, }, From 6517f23333d85bae2b3cd82f43f6e859bfd7af27 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Sun, 13 Sep 2026 23:32:11 +0530 Subject: [PATCH 2/6] fix(ui): theme AI sidebar navigation colors --- .../ContextCenterSubNavSections.test.tsx | 47 +++++++++++++++++++ .../Sidebar/ContextCenterSubNavSections.tsx | 11 +++-- .../platform/ai-shell/Sidebar/sidebar.less | 29 +++++------- 3 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.test.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.test.tsx new file mode 100644 index 000000000000..526304b2cbda --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Collate. + * Licensed 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 { render } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import ContextCenterSubNavSections from './ContextCenterSubNavSections'; + +jest.mock('../../../../hooks/currentUserStore/useCurrentUserStore', () => ({ + useCurrentUserPreferences: () => ({ + preferences: { recentlyViewedQuickLinks: [] }, + }), +})); + +jest.mock('../../../../hooks/useApplicationStore', () => ({ + useApplicationStore: () => ({ currentUser: undefined }), +})); + +describe('ContextCenterSubNavSections', () => { + it('uses the semantic muted-text role for section headings', () => { + const { container } = render( + + + + ); + const heading = container.querySelector( + '.ask-sub-panel__section > .prose > span' + ); + + expect(heading).toHaveClass('tw:text-quaternary'); + expect(heading).not.toHaveClass('tw:text-gray-500'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.tsx b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.tsx index 6104e2e37390..3f873d7e030b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/ContextCenterSubNavSections.tsx @@ -223,7 +223,10 @@ const ContextCenterSubNavSections: FC = ({ return ( <>
- + {t('label.quick-action-plural')}
    @@ -275,7 +278,7 @@ const ContextCenterSubNavSections: FC = ({ {!isEmpty(recentlyViewed) && (
    {t('label.recently-viewed')} @@ -289,7 +292,7 @@ const ContextCenterSubNavSections: FC = ({ {!isEmpty(bookmarks) && (
    {t('label.bookmark-plural')} @@ -305,7 +308,7 @@ const ContextCenterSubNavSections: FC = ({ className="ask-sub-panel__section ask-sub-panel__section--with-header" key={tagFqn}> {startCase(tagFqn.split(FQN_SEPARATOR_CHAR)[1])} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less index 2ace10bf35d7..84bd45974529 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less @@ -67,27 +67,24 @@ @ask-recent-chats-card-radius: 10px; -// Colors reference Tailwind v4 theme tokens emitted at `:root` by -// `@openmetadata/ui-core-components/globals.css`. The `--tw-` prefix -// comes from this app's `tailwind.css` (`prefix(tw)`). -// `@ask-bg-hover` stays literal — overlay convention, no semantic token. +// Use shared roles where Gate 0 defines a light-preserving mapping. The +// palette-backed page, selected, brand, and badge roles remain pending #6564. @ask-bg-page: var(--tw-color-utility-gray-blue-50); -@ask-bg-card: var(--tw-color-bg-primary); +@ask-bg-card: var(--om-color-bg-surface); @ask-bg-active: var(--tw-color-gray-blue-100); -@ask-bg-hover: rgba(0, 0, 0, 0.04); -@ask-text-primary: var(--tw-color-text-primary); -@ask-text-secondary: var(--tw-color-text-secondary); -@ask-text-tertiary: var(--tw-color-text-tertiary); -@ask-text-quaternary: var(--tw-color-text-quaternary); +@ask-bg-hover: var(--om-color-interactive-hover); +@ask-text-primary: var(--om-color-text-primary); +@ask-text-secondary: var(--om-color-text-secondary); +@ask-text-tertiary: var(--om-color-text-tertiary); +@ask-text-quaternary: var(--om-color-text-quaternary); @ask-brand: var(--tw-color-brand-600); @ask-brand-active: var(--tw-color-brand-800); @ask-brand-50: var(--tw-color-brand-50); -@ask-border-secondary: var(--tw-color-border-secondary); +@ask-border-secondary: var(--om-color-border-secondary); // Utility palette — used by the count badges on nav items. @ask-util-gray-50: var(--tw-color-gray-50); @ask-util-gray-200: var(--tw-color-gray-200); -@ask-util-gray-600: var(--tw-color-gray-600); @ask-util-gray-700: var(--tw-color-gray-700); @ask-transition-duration: 200ms; @@ -927,14 +924,14 @@ &:not(&--active):not(&--interactive-icon):not(&--recent-chats):hover { background: @ask-bg-hover; - color: @ask-util-gray-600; + color: @ask-text-tertiary; svg { width: 22px; height: 22px; * { - stroke: @ask-util-gray-600; + stroke: @ask-text-tertiary; } } } @@ -1292,14 +1289,14 @@ &:not(&--active):hover { background: @ask-bg-hover; - color: @ask-util-gray-600; + color: @ask-text-tertiary; svg { width: 22px; height: 22px; * { - stroke: @ask-util-gray-600; + stroke: @ask-text-tertiary; } } } From a0420bcd42cc3a3a51a8b524197c07bb196efe0e Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Sun, 13 Sep 2026 23:34:18 +0530 Subject: [PATCH 3/6] docs(ui): clarify sidebar token boundary --- .../ui/src/components/platform/ai-shell/Sidebar/sidebar.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less index 84bd45974529..c1846b902452 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less @@ -67,8 +67,8 @@ @ask-recent-chats-card-radius: 10px; -// Use shared roles where Gate 0 defines a light-preserving mapping. The -// palette-backed page, selected, brand, and badge roles remain pending #6564. +// Use shared roles already defined by Gate 0. The palette-backed page, +// selected, brand, and badge roles remain pending #6564. @ask-bg-page: var(--tw-color-utility-gray-blue-50); @ask-bg-card: var(--om-color-bg-surface); @ask-bg-active: var(--tw-color-gray-blue-100); From 00e57f5c05c126d1c219385d1123f3a63fcdb09e Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Sun, 13 Sep 2026 23:43:20 +0530 Subject: [PATCH 4/6] fix(ui): preserve semantic sidebar hover --- .../design-tokens/gen-token-reference.js | 72 +++++++++++++++---- .../design-tokens/theme-contract.test.js | 1 + .../ui/specs/tokens/token-reference.md | 11 +-- .../platform/ai-shell/Sidebar/sidebar.less | 2 +- .../main/resources/ui/src/styles/tokens.css | 5 +- 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-token-reference.js b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-token-reference.js index 0fcb464d752a..3f47befca49d 100644 --- a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-token-reference.js +++ b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-token-reference.js @@ -67,19 +67,67 @@ function resolve(name, defs, seen = new Set()) { } const GROUPS = [ - { key: /^--om-space-/, title: 'Spacing', note: 'padding / margin / gap. See foundations/spacing.md.' }, - { key: /^--om-page-/, title: 'Layout', note: 'Shared viewport and shell-derived dimensions.' }, - { key: /^--om-radius-/, title: 'Radius', note: 'border-radius. See foundations/radius.md.' }, - { key: /^--om-font-size-/, title: 'Font size', note: 'font-size. See foundations/typography.md.' }, + { + key: /^--om-space-/, + title: 'Spacing', + note: 'padding / margin / gap. See foundations/spacing.md.', + }, + { + key: /^--om-page-/, + title: 'Layout', + note: 'Shared viewport and shell-derived dimensions.', + }, + { + key: /^--om-radius-/, + title: 'Radius', + note: 'border-radius. See foundations/radius.md.', + }, + { + key: /^--om-font-size-/, + title: 'Font size', + note: 'font-size. See foundations/typography.md.', + }, { key: /^--om-font-weight-/, title: 'Font weight', note: 'font-weight.' }, - { key: /^--om-font-|^--om-line-height-/, title: 'Font family & line height', note: '' }, - { key: /^--om-shadow-/, title: 'Elevation', note: 'box-shadow. See foundations/elevation.md.' }, - { key: /^--om-z-/, title: 'z-index', note: 'stacking. Prefer the semantic ladder for new work.' }, - { key: /^--om-duration-|^--om-ease-/, title: 'Motion', note: 'transition / animation. See foundations/motion.md.' }, - { key: /^--om-color-(text|bg|border|fg|link|interactive|focus)/, title: 'Semantic colors', note: 'Prefer these — they adapt to dark mode.' }, - { key: /^--om-color-(white|black|transparent)/, title: 'Absolute colors', note: '' }, - { key: /^--om-color-/, title: 'Palette colors', note: 'Fixed swatches; do NOT adapt to dark mode. Prefer semantic tokens.' }, - { key: /^--om-legacy-color-/, title: 'Legacy colors', note: 'Exact migrated one-offs (migration debt). Do not use in new code; re-express with a semantic token.' }, + { + key: /^--om-font-|^--om-line-height-|^--om-letter-spacing-/, + title: 'Font family, line height & letter spacing', + note: '', + }, + { + key: /^--om-shadow-/, + title: 'Elevation', + note: 'box-shadow. See foundations/elevation.md.', + }, + { + key: /^--om-z-/, + title: 'z-index', + note: 'stacking. Prefer the semantic ladder for new work.', + }, + { + key: /^--om-duration-|^--om-ease-/, + title: 'Motion', + note: 'transition / animation. See foundations/motion.md.', + }, + { + key: /^--om-color-(text|bg|border|fg|link|interactive|focus)/, + title: 'Semantic colors', + note: 'Prefer these — they adapt to dark mode.', + }, + { + key: /^--om-color-(white|black|transparent)/, + title: 'Absolute colors', + note: '', + }, + { + key: /^--om-color-/, + title: 'Palette colors', + note: 'Fixed swatches; do NOT adapt to dark mode. Prefer semantic tokens.', + }, + { + key: /^--om-legacy-color-/, + title: 'Legacy colors', + note: 'Exact migrated one-offs (migration debt). Do not use in new code; re-express with a semantic token.', + }, ]; function classify(name) { diff --git a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js index ffc9c9cb6d1c..c2ed5619ba9c 100644 --- a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js +++ b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/theme-contract.test.js @@ -258,6 +258,7 @@ test('exposes the approved roles through the legacy token bridge', () => { '--om-color-bg-surface': 'var(--color-bg-surface, #ffffff)', '--om-color-bg-raised': 'var(--color-bg-raised, #ffffff)', '--om-color-bg-overlay-surface': 'var(--color-bg-overlay-surface, #ffffff)', + '--om-color-bg-secondary-hover': 'var(--color-bg-secondary_hover, #f5f5f5)', '--om-color-border-subtle': 'var(--color-border-subtle, rgb(0 0 0 / 0.08))', '--om-color-border-hover': 'var(--color-border-hover, #a4a7ae)', '--om-color-border-brand-subtle': diff --git a/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md b/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md index fde2c468973f..d1435ef5278a 100644 --- a/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md +++ b/openmetadata-ui/src/main/resources/ui/specs/tokens/token-reference.md @@ -4,7 +4,7 @@ Master map of every **project (`--om-*`) token** — the tokens components reference. Each references the matching upstream `globals.css` token (or holds a raw value) and resolves to the value shown. Full layering: [../README.md](../README.md). -Total project tokens: **804**. +Total project tokens: **807**. ## Spacing (62) @@ -162,7 +162,7 @@ font-weight. | `--om-font-weight-semibold` | `600` | | `--om-font-weight-thin` | `100` | -## Font family & line height (7) +## Font family, line height & letter spacing (8) | Token | Value | | --- | --- | @@ -170,6 +170,7 @@ font-weight. Consolas, 'Liberation Mono', 'Courier New', monospace` | | `--om-font-sans` | `'Inter', 'Poppins', -apple-system, 'Segoe UI', Roboto, Arial, sans-serif` | +| `--om-letter-spacing-wide` | `0.08em` | | `--om-line-height-none` | `1` | | `--om-line-height-normal` | `1.5` | | `--om-line-height-relaxed` | `1.625` | @@ -197,7 +198,7 @@ box-shadow. See foundations/elevation.md. 0px 8px 8px -4px rgba(10, 13, 18, 0.03)` | | `--om-shadow-xs` | `0px 1px 2px rgba(10, 13, 18, 0.05)` | -## z-index (31) +## z-index (32) stacking. Prefer the semantic ladder for new work. @@ -229,6 +230,7 @@ stacking. Prefer the semantic ladder for new work. | `--om-z-dropdown` | `1000` | | `--om-z-max` | `9999` | | `--om-z-modal` | `1500` | +| `--om-z-n1` | `-1` | | `--om-z-overlay` | `1050` | | `--om-z-popover` | `2000` | | `--om-z-raised` | `1` | @@ -268,7 +270,7 @@ transition / animation. See foundations/motion.md. | `--om-ease-out` | `cubic-bezier(0, 0, 0.2, 1)` | | `--om-ease-standard` | `cubic-bezier(0.4, 0, 0.2, 1)` | -## Semantic colors (52) +## Semantic colors (53) Prefer these — they adapt to dark mode. @@ -287,6 +289,7 @@ Prefer these — they adapt to dark mode. | `--om-color-bg-quaternary` | `#e9eaeb` | | `--om-color-bg-raised` | `#ffffff` | | `--om-color-bg-secondary` | `#fafafa` | +| `--om-color-bg-secondary-hover` | `#f5f5f5` | | `--om-color-bg-success` | `#ecfdf3` | | `--om-color-bg-surface` | `#ffffff` | | `--om-color-bg-tertiary` | `#f5f5f5` | diff --git a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less index c1846b902452..cec230f8f31c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/platform/ai-shell/Sidebar/sidebar.less @@ -72,7 +72,7 @@ @ask-bg-page: var(--tw-color-utility-gray-blue-50); @ask-bg-card: var(--om-color-bg-surface); @ask-bg-active: var(--tw-color-gray-blue-100); -@ask-bg-hover: var(--om-color-interactive-hover); +@ask-bg-hover: var(--om-color-bg-secondary-hover); @ask-text-primary: var(--om-color-text-primary); @ask-text-secondary: var(--om-color-text-secondary); @ask-text-tertiary: var(--om-color-text-tertiary); diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css index 62b2fbd70068..e45572be3e3c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css +++ b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css @@ -179,6 +179,7 @@ --om-color-bg: var(--color-bg-primary, #ffffff); --om-color-bg-primary: var(--color-bg-primary, #ffffff); --om-color-bg-secondary: var(--color-bg-secondary, #fafafa); + --om-color-bg-secondary-hover: var(--color-bg-secondary_hover, #f5f5f5); --om-color-bg-tertiary: var(--color-bg-tertiary, #f5f5f5); --om-color-bg-quaternary: var(--color-bg-quaternary, #e9eaeb); --om-color-bg-disabled: var(--color-bg-disabled, #f5f5f5); @@ -841,13 +842,14 @@ --om-space-290: 290px; /* Extended radius — off-scale values in use. */ + --om-radius-10: 10px; + --om-radius-2xl: 16px; --om-radius-1: 1px; --om-radius-3: 3px; --om-radius-3_2: 3.2px; --om-radius-5: 5px; --om-radius-7: 7px; --om-radius-9: 9px; - --om-radius-10: 10px; --om-radius-13: 13px; --om-radius-14: 14px; --om-radius-15: 15px; @@ -876,6 +878,7 @@ /* z-index — exact values preserved to keep stacking order identical. For new work prefer the semantic ladder documented in specs. */ + --om-z-n1: -1; --om-z-0: 0; --om-z-1: 1; --om-z-2: 2; From 058050aa18e4517b8d6d995cf876dc1700134b62 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Mon, 14 Sep 2026 00:19:29 +0530 Subject: [PATCH 5/6] fix(ui): correct generated token ordering --- .../ui/scripts/design-tokens/gen-tokens.js | 22 +++++-- .../ui/scripts/design-tokens/scanner.test.js | 58 +++++++++++++++++-- .../ui/scripts/design-tokens/token-map.js | 25 ++++++-- .../main/resources/ui/src/styles/tokens.css | 5 +- 4 files changed, 88 insertions(+), 22 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-tokens.js b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-tokens.js index 70ef49e25f01..21c58cf6d3b5 100644 --- a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-tokens.js +++ b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/gen-tokens.js @@ -30,7 +30,13 @@ const map = require('./token-map'); const STYLES_ROOT = path.resolve(__dirname, '../../src'); const TOKENS_CSS = path.resolve(STYLES_ROOT, 'styles/tokens.css'); const SIDECAR = path.resolve(__dirname, 'legacy-colors.json'); -const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', 'target', 'output']); +const SKIP_DIRS = new Set([ + 'node_modules', + 'dist', + 'build', + 'target', + 'output', +]); const BEGIN = '/* @tokens:generated-begin'; const END = '/* @tokens:generated-end */'; const OM_USAGE_RE = /var\(\s*(--om-[\w-]+)/g; @@ -147,17 +153,21 @@ function main() { const r = map.registry; process.stdout.write( `tokens.css regenerated:\n` + - ` palette (full upstream): ${Object.keys(map.constants.UPSTREAM).length}\n` + + ` palette passthrough: ${ + Object.keys(map.constants.UPSTREAM).length - + map.constants.MANUAL_ABSOLUTE_COLOR_TOKENS.size + }\n` + ` legacy colors: ${r.legacyColors.size}\n` + ` extended spacing: ${ - [...r.spacing.keys()].filter((px) => !map.constants.CORE_SPACING.has(px)) - .length + [...r.spacing.keys()].filter( + (px) => !map.constants.CORE_SPACING.has(px) + ).length }\n` + ` extended radius: ${ - [...r.radius.keys()].filter((k) => /^\d+$/.test(k)).length + [...r.radius.keys()].filter(map.isNumericSlug).length }\n` + ` extended font-size: ${ - [...r.fontSize.keys()].filter((k) => /^\d+$/.test(k)).length + [...r.fontSize.keys()].filter(map.isNumericSlug).length }\n` + ` z-index tokens: ${r.zIndex.size}\n` + ` duration tokens: ${r.duration.size}\n` diff --git a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/scanner.test.js b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/scanner.test.js index 142a8dfe9511..e59b88fac74f 100644 --- a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/scanner.test.js +++ b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/scanner.test.js @@ -14,6 +14,7 @@ /* Focused round-trip checks for the token scanner. Run: node scanner.test.js */ const assert = require('assert'); const { processText } = require('./scanner'); +const tokenMap = require('./token-map'); let pass = 0; function check(name, input, expected) { @@ -22,7 +23,13 @@ function check(name, input, expected) { assert.strictEqual(newText, expected); pass++; } catch (e) { - process.stderr.write(`\nFAIL: ${name}\n in: ${JSON.stringify(input)}\n got: ${JSON.stringify(newText)}\n exp: ${JSON.stringify(expected)}\n`); + process.stderr.write( + `\nFAIL: ${name}\n in: ${JSON.stringify( + input + )}\n got: ${JSON.stringify(newText)}\n exp: ${JSON.stringify( + expected + )}\n` + ); process.exitCode = 1; } } @@ -34,7 +41,11 @@ check( '.a { color: var(--om-color-brand-500); }' ); // color: 3-digit expands and matches white -check('short hex white', '.a { color: #FFF; }', '.a { color: var(--om-color-white); }'); +check( + 'short hex white', + '.a { color: #FFF; }', + '.a { color: var(--om-color-white); }' +); // color: no upstream home -> legacy token, exact value preserved check( 'legacy color', @@ -80,9 +91,17 @@ check( '.a { width: 100px; /* padding: 8px */ color: var(--om-color-white); }' ); // width/top are out of scope -> untouched -check('width out of scope', '.a { width: 20px; top: 8px; }', '.a { width: 20px; top: 8px; }'); +check( + 'width out of scope', + '.a { width: 20px; top: 8px; }', + '.a { width: 20px; top: 8px; }' +); // border-radius -> radius token -check('radius', '.a { border-radius: 4px; }', '.a { border-radius: var(--om-radius-sm); }'); +check( + 'radius', + '.a { border-radius: 4px; }', + '.a { border-radius: var(--om-radius-sm); }' +); // font-size + font-weight check( 'font-size and weight', @@ -104,7 +123,11 @@ check( '.a { box-shadow: 0px 2px 10px var(--om-legacy-color-0-0-0-0-12); }' ); // LESS @variable usage untouched (not a literal) -check('less var untouched', '.a { color: @grey-15; }', '.a { color: @grey-15; }'); +check( + 'less var untouched', + '.a { color: @grey-15; }', + '.a { color: @grey-15; }' +); // LESS color function: hex inside darken() must stay raw (LESS needs a literal) check( 'less color fn untouched', @@ -167,4 +190,27 @@ const once = processText(rich, 't').newText; const twice = processText(once, 't').newText; check('idempotent (all categories)', twice, once); -process.stdout.write(`\n${pass} checks passed${process.exitCode ? ' (with failures above)' : ''}\n`); +tokenMap.resetRegistry(); +for (const radius of [10, 16, 24, 1, 3.2]) { + tokenMap.resolveRadius(radius, 'px'); +} +const generatedTokens = tokenMap.emitGeneratedBlocks(); +const generatedRadiusLines = generatedTokens + .split('\n') + .filter((line) => line.includes('--om-radius-')); +assert.deepStrictEqual(generatedRadiusLines, [ + ' --om-radius-1: 1px;', + ' --om-radius-3_2: 3.2px;', + ' --om-radius-10: 10px;', +]); +assert.deepStrictEqual( + generatedTokens + .split('\n') + .filter((line) => /--om-color-(?:black|white):/.test(line)), + [] +); +pass++; + +process.stdout.write( + `\n${pass} checks passed${process.exitCode ? ' (with failures above)' : ''}\n` +); diff --git a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/token-map.js b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/token-map.js index 2a2600da9646..06d3e6554592 100644 --- a/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/token-map.js +++ b/openmetadata-ui/src/main/resources/ui/scripts/design-tokens/token-map.js @@ -31,6 +31,10 @@ const path = require('path'); const { canonicalizeColor, colorSlug } = require('./color-utils'); const UPSTREAM = require('./upstream-palette.json').palette; +const MANUAL_ABSOLUTE_COLOR_TOKENS = new Set([ + '--om-color-black', + '--om-color-white', +]); // --------------------------------------------------------------------------- // Scales (Layer 1 primitive values) @@ -373,6 +377,8 @@ function emitPaletteBlock() { upstreamVar, omName: omPaletteName(upstreamVar), })) + // Absolute colors are declared in the manual token layer and must not be overridden later. + .filter(({ omName }) => !MANUAL_ABSOLUTE_COLOR_TOKENS.has(omName)) .sort((a, b) => a.omName.localeCompare(b.omName)); const lines = entries .map((e) => ` ${e.omName}: var(${e.upstreamVar}, ${e.canon});`) @@ -404,6 +410,11 @@ function slugToNum(slug) { return Number(slug.replace('_', '.')); } +// A prefix check misclassifies semantic scale names such as `2xl` as off-scale values. +function isNumericSlug(slug) { + return /^\d+(?:_\d+)?$/.test(slug); +} + function emitSpacingExtBlock() { const pxs = [...registry.spacing.keys()] .filter((px) => !CORE_SPACING.has(px)) @@ -422,7 +433,7 @@ function emitSpacingExtBlock() { } function emitRadiusExtBlock() { - const numeric = [...registry.radius.keys()].filter((k) => /^\d/.test(k)); + const numeric = [...registry.radius.keys()].filter(isNumericSlug); let block = null; if (numeric.length) { const lines = numeric @@ -435,12 +446,14 @@ function emitRadiusExtBlock() { } function emitFontSizeExtBlock() { - const numeric = [...registry.fontSize.keys()].filter((k) => /^\d/.test(k)); + const numeric = [...registry.fontSize.keys()].filter(isNumericSlug); let block = null; if (numeric.length) { const lines = numeric .sort((a, b) => slugToNum(a) - slugToNum(b)) - .map((slug) => ` --om-font-size-${slug}: ${registry.fontSize.get(slug)};`) + .map( + (slug) => ` --om-font-size-${slug}: ${registry.fontSize.get(slug)};` + ) .join('\n'); block = ` /* Extended font sizes — off-scale values in use. */\n` + lines; } @@ -469,9 +482,7 @@ function emitDurationBlock() { const ms = sortByNum(registry.duration); let block = null; if (ms.length) { - const lines = ms - .map((n) => ` --om-duration-${n}: ${n}ms;`) - .join('\n'); + const lines = ms.map((n) => ` --om-duration-${n}: ${n}ms;`).join('\n'); block = ` /* Motion durations in use. */\n` + lines; } return block; @@ -515,8 +526,10 @@ module.exports = { registry, resetRegistry, emitGeneratedBlocks, + isNumericSlug, constants: { CORE_SPACING, + MANUAL_ABSOLUTE_COLOR_TOKENS, RADIUS_VALUE, FONT_SIZE_VALUE, FONT_WEIGHT_VALUE, diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css index e45572be3e3c..63cab6ffac48 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css +++ b/openmetadata-ui/src/main/resources/ui/src/styles/tokens.css @@ -235,7 +235,6 @@ :root { /* Palette passthrough — --om-* aliases reference the upstream palette in globals.css with a raw fallback. Components reference these. */ - --om-color-black: var(--color-black, #000000); --om-color-blue-dark-100: var(--color-blue-dark-100, #d1e0ff); --om-color-blue-dark-200: var(--color-blue-dark-200, #b2ccff); --om-color-blue-dark-25: var(--color-blue-dark-25, #f5f8ff); @@ -548,7 +547,6 @@ --om-color-warning-800: var(--color-warning-800, #93370d); --om-color-warning-900: var(--color-warning-900, #7a2e0e); --om-color-warning-950: var(--color-warning-950, #4e1d09); - --om-color-white: var(--color-white, #ffffff); --om-color-yellow-100: var(--color-yellow-100, #fef7c3); --om-color-yellow-200: var(--color-yellow-200, #feee95); --om-color-yellow-25: var(--color-yellow-25, #fefdf0); @@ -842,14 +840,13 @@ --om-space-290: 290px; /* Extended radius — off-scale values in use. */ - --om-radius-10: 10px; - --om-radius-2xl: 16px; --om-radius-1: 1px; --om-radius-3: 3px; --om-radius-3_2: 3.2px; --om-radius-5: 5px; --om-radius-7: 7px; --om-radius-9: 9px; + --om-radius-10: 10px; --om-radius-13: 13px; --om-radius-14: 14px; --om-radius-15: 15px; From 27cf2b522ea5116379809a950c2885135bddef1b Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Mon, 14 Sep 2026 13:16:25 +0530 Subject: [PATCH 6/6] fix(ui): theme textarea resize handle --- .../base/textarea/textarea.test.tsx | 33 +++++++++++++++++++ .../src/components/base/textarea/textarea.tsx | 12 +++---- 2 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.test.tsx diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.test.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.test.tsx new file mode 100644 index 000000000000..4a62bf319e44 --- /dev/null +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Collate. + * Licensed 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 { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { TextAreaBase } from './textarea'; + +describe('TextAreaBase theme semantics', () => { + it('colors the resize handle with the active semantic border token', () => { + render(); + + const textArea = screen.getByRole('textbox', { name: 'Description' }); + + expect(textArea).toHaveClass( + 'tw:[&::-webkit-resizer]:bg-border-primary', + 'tw:[&::-webkit-resizer]:mask-(image:--resize-handle-mask)' + ); + expect(textArea.style.getPropertyValue('--resize-handle-mask')).toContain( + 'data:image/svg+xml;base64,' + ); + expect(textArea.className).not.toContain('tw:dark:'); + }); +}); diff --git a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.tsx b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.tsx index bca3704fb8ae..98a2268253a0 100644 --- a/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.tsx +++ b/openmetadata-ui-core-components/src/main/resources/ui/src/components/base/textarea/textarea.tsx @@ -13,10 +13,10 @@ import { Label } from '@/components/base/input/label'; import { cx } from '@/utils/cx'; import { fontSizeClass } from '@/utils'; -// Creates a data URL for an SVG resize handle with a given color. -const getResizeHandleBg = (color: string) => { +// Masking keeps the resize glyph's shape fixed while its semantic color follows the active theme. +const getResizeHandleMask = () => { return `url(data:image/svg+xml;base64,${btoa( - `` + '' )})`; }; @@ -40,8 +40,7 @@ export const TextAreaBase = ({ // gone — the outline IS the focus indicator here, as in input.tsx. 'tw:w-full tw:scroll-py-3 tw:rounded-lg tw:bg-primary tw:px-3.5 tw:py-3 tw:text-primary tw:shadow-xs tw:outline-1 tw:-outline-offset-1 tw:outline-primary tw:transition tw:duration-100 tw:ease-linear tw:placeholder:text-placeholder tw:autofill:rounded-lg tw:autofill:text-primary', - // Resize handle - 'tw:[&::-webkit-resizer]:bg-(image:--resize-handle-bg) tw:[&::-webkit-resizer]:bg-contain tw:dark:[&::-webkit-resizer]:bg-(image:--resize-handle-bg-dark)', + 'tw:[&::-webkit-resizer]:bg-border-primary tw:[&::-webkit-resizer]:mask-(image:--resize-handle-mask) tw:[&::-webkit-resizer]:mask-contain tw:[&::-webkit-resizer]:mask-no-repeat', state.isFocused && !state.isDisabled && @@ -60,8 +59,7 @@ export const TextAreaBase = ({ } style={ { - '--resize-handle-bg': getResizeHandleBg('#D5D7DA'), - '--resize-handle-bg-dark': getResizeHandleBg('#373A41'), + '--resize-handle-mask': getResizeHandleMask(), } as React.CSSProperties } />