From 501250e0ffac3703028e6c689f3e371060c45293 Mon Sep 17 00:00:00 2001 From: Gab Date: Thu, 2 Jul 2026 15:53:57 -0300 Subject: [PATCH 1/2] [NO-ISSUE] feat(theme): expose theme-colors entry for semantic token compilation --- packages/theme/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/theme/package.json b/packages/theme/package.json index 357928e50..7ee7f8efc 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -61,6 +61,7 @@ ".": "./default.js", "./animations": "./src/tokens/primitives/animations/animate.js", "./colors": "./src/tokens/primitives/colors/colors.js", + "./theme-colors": "./src/scripts/compile-theme.js", "./texts": "./src/tokens/semantic/texts.data.js", "./tailwind-preset": "./dist/v3/tailwind-preset.js", "./tailwind-preset/v3": "./dist/v3/tailwind-preset.js", From fe357a34f1bdc1fe70b00f26361e343c869e5351 Mon Sep 17 00:00:00 2001 From: Gab Date: Thu, 2 Jul 2026 15:54:05 -0300 Subject: [PATCH 2/2] [NO-ISSUE] docs(storybook): add Theme foundations page for semantic color tokens --- apps/storybook/.storybook/preview.js | 2 +- .../components/SemanticSwatchGroup.vue | 87 ++++++++ apps/storybook/src/foundations/data/theme.js | 6 + .../src/foundations/utils/theme-tokens.js | 192 ++++++++++++++++++ .../src/stories/foundations/Theme.stories.js | 48 +++++ 5 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 apps/storybook/src/foundations/components/SemanticSwatchGroup.vue create mode 100644 apps/storybook/src/foundations/data/theme.js create mode 100644 apps/storybook/src/foundations/utils/theme-tokens.js create mode 100644 apps/storybook/src/stories/foundations/Theme.stories.js diff --git a/apps/storybook/.storybook/preview.js b/apps/storybook/.storybook/preview.js index c55e590b7..2c0153bb8 100644 --- a/apps/storybook/.storybook/preview.js +++ b/apps/storybook/.storybook/preview.js @@ -77,7 +77,7 @@ export const parameters = { order: [ 'Get Started', 'Foundations', - ['Colors', 'Spacing', 'Typography', 'Icons'], + ['Colors', 'Theme', 'Spacing', 'Typography', 'Icons'], 'Components', 'Site' ] diff --git a/apps/storybook/src/foundations/components/SemanticSwatchGroup.vue b/apps/storybook/src/foundations/components/SemanticSwatchGroup.vue new file mode 100644 index 000000000..5315055b1 --- /dev/null +++ b/apps/storybook/src/foundations/components/SemanticSwatchGroup.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/storybook/src/foundations/data/theme.js b/apps/storybook/src/foundations/data/theme.js new file mode 100644 index 000000000..193fb02b6 --- /dev/null +++ b/apps/storybook/src/foundations/data/theme.js @@ -0,0 +1,6 @@ +/** + * Semantic theme color catalog — generated from the `@aziontech/theme` + * token resolvers (see ../utils/theme-tokens.js), so the page always reflects + * every color that ships in globals.css. + */ +export { themeColorGroups, buildThemeColorGroups } from '../utils/theme-tokens.js' diff --git a/apps/storybook/src/foundations/utils/theme-tokens.js b/apps/storybook/src/foundations/utils/theme-tokens.js new file mode 100644 index 000000000..41393b057 --- /dev/null +++ b/apps/storybook/src/foundations/utils/theme-tokens.js @@ -0,0 +1,192 @@ +/** + * Semantic theme color catalog — generated from the `theme/*` token layer + * (resolved against `primitives/`), so it can never drift from what ships in + * `@aziontech/theme/globals.css`. + * + * `compileThemeVars()` resolves `tokens/theme/*` (background, border, text, + * primary, secondary, accent, ring, code-sintax, feedback/*, surfaces) against + * the color primitives, producing the component-facing theme set: `--bg-*`, + * `--primary*`, `--accent*`, `--secondary*`, `--border-*`, `--text-*`, + * `--ring-color`, feedback `--{info,success,warning,danger}{,-contrast,-border}`, + * and `--code-sintax-*`. We keep only color-valued tokens, drop the `--surface-*` + * primitive scale (documented on the Colors page), and group by role. Each token + * carries its resolved `light` and `dark` value. + * + * Tokens from `tokens/semantic/*` are intentionally NOT sourced here. + */ +import { compileThemeVars } from '@aziontech/theme/theme-colors' + +const isColor = (value) => typeof value === 'string' && /^#([0-9a-f]{3,8})$/i.test(value.trim()) + +// The surface scale is a primitive-level scale documented on the Colors page. +const isExcluded = (name) => /^--surface-/.test(name) + +/** Resolve the theme layer into one { name: { light, dark } } map. */ +function buildResolvedMap() { + const { light, dark } = compileThemeVars() + + const map = {} + for (const name of new Set([...Object.keys(light), ...Object.keys(dark)])) { + if (isExcluded(name)) continue + const l = light[name] + const d = dark[name] + if (!isColor(l) && !isColor(d)) continue + map[name] = { light: l ?? d, dark: d ?? l } + } + + return map +} + +// Ordered sections. First matching predicate wins; the trailing catch-all +// guarantees a newly-added token always surfaces somewhere. +const SECTIONS = [ + { + id: 'backgrounds', + title: 'Backgrounds', + description: + 'Surface and page backgrounds. Use --bg-canvas for pages and --bg-surface for elements on top; -raised / -overlay add elevation, and the state tokens (hover, active, selected, disabled) layer on interaction.', + match: (n) => /^--bg-/.test(n) + }, + { + id: 'text', + title: 'Text & Icons', + description: 'Accessible foreground colors for text and icons.', + match: (n) => /^--text-/.test(n) + }, + { + id: 'borders', + title: 'Borders', + description: 'Border colors for component outlines, separators and status surfaces.', + match: (n) => /^--border-/.test(n) + }, + { + id: 'brand', + title: 'Brand & Interactive', + description: + 'Brand and interactive fills. Each role pairs a base color with a -contrast foreground for accessible content on top, plus -mask / -selected tints.', + match: (n) => /^--(primary|secondary|accent)(-|$)/.test(n) + }, + { + id: 'feedback', + title: 'Feedback Colors', + description: + 'Status colors for info, success, warning and danger. Each set pairs a subtle background with an accessible -contrast foreground and a -border.', + match: (n) => /^--(info|success|warning|danger)(-|$)/.test(n) + }, + { + id: 'focus', + title: 'Focus', + description: 'Focus-ring color for keyboard navigation.', + match: (n) => /^--ring(-|$)/.test(n) + }, + { + id: 'code', + title: 'Code Syntax', + description: 'Syntax-highlighting colors for the CodeBlock component.', + match: (n) => /^--code-sintax-/.test(n) + }, + { + id: 'other', + title: 'Other', + description: 'Additional semantic color tokens.', + match: () => true + } +] + +// Human-readable notes, keyed by token name. Missing entries render without a note. +const DESCRIPTIONS = { + '--bg-canvas': 'Body / page background.', + '--bg-surface': 'Primary element background (cards, panels).', + '--bg-surface-raised': 'Raised element background — one step above the surface.', + '--bg-surface-overlay': 'Floating surface for menus, popovers and dropdowns.', + '--bg-hover': 'Hover overlay for interactive surfaces.', + '--bg-active': 'Active / pressed overlay for interactive surfaces.', + '--bg-selected': 'Background for a selected element.', + '--bg-disabled': 'Background for a disabled element.', + '--bg-mask': 'Scrim placed over content to dim it.', + '--bg-backdrop': 'Dialog / modal backdrop.', + '--bg-contrast': 'High-contrast inverse background.', + + '--text-default': 'Primary text and icons.', + '--text-muted': 'Secondary text and icons.', + '--text-disabled': 'Disabled text and icons.', + '--text-link': 'Interactive link text.', + '--text-contrast': 'Text and icons on a high-contrast surface.', + + '--border-default': 'Default border for UI components.', + '--border-muted': 'Subtle, low-emphasis border.', + '--border-strong': 'High-emphasis border.', + '--border-selected': 'Border for a selected / active element.', + + '--primary': 'Primary action / brand fill.', + '--primary-contrast': 'Text and icons on a primary fill.', + '--primary-mask': 'Translucent primary tint for hover / wash.', + '--primary-selected': 'Primary tint for a selected state.', + '--secondary': 'Secondary fill.', + '--secondary-contrast': 'Text and icons on a secondary fill.', + '--secondary-mask': 'Translucent secondary tint for hover / wash.', + '--secondary-selected': 'Secondary tint for a selected state.', + '--accent': 'Accent fill.', + '--accent-contrast': 'Text and icons on an accent fill.', + '--accent-mask': 'Translucent accent tint for hover / wash.', + '--accent-selected': 'Accent tint for a selected state.', + + '--info': 'Informational background.', + '--info-contrast': 'Text and icons on an info background.', + '--info-border': 'Border for info surfaces.', + '--success': 'Success background.', + '--success-contrast': 'Text and icons on a success background.', + '--success-border': 'Border for success surfaces.', + '--warning': 'Warning background.', + '--warning-contrast': 'Text and icons on a warning background.', + '--warning-border': 'Border for warning surfaces.', + '--danger': 'Danger / error background.', + '--danger-contrast': 'Text and icons on a danger background.', + '--danger-border': 'Border for danger surfaces.', + + '--ring-color': 'Focus ring around a focused element.', + + '--code-sintax-identifier': 'Identifiers (variables, properties).', + '--code-sintax-line-number': 'Gutter line numbers.', + '--code-sintax-keyword': 'Language keywords.', + '--code-sintax-punctuation': 'Punctuation and operators.', + '--code-sintax-function': 'Function and method names.', + '--code-sintax-type': 'Types and classes.', + '--code-sintax-string': 'String literals.' +} + +// A token's swatch style: filled by default; borders draw an outline; foreground +// (text / -contrast) roles render an "Aa" glyph on the surface (or on their base). +function swatchKind(name) { + if (/^--border-/.test(name) || /-border$/.test(name) || name === '--ring-color') return 'border' + if (/^--text-/.test(name) || /-contrast$/.test(name) || /^--code-sintax-/.test(name)) return 'text' + return 'fill' +} + +// For -contrast foregrounds, back the glyph with the base fill it sits on. +function backing(name) { + const base = name.replace(/-contrast$/, '') + if (base !== name) return `var(${base})` + return null +} + +export function buildThemeColorGroups() { + const resolved = buildResolvedMap() + const groups = SECTIONS.map((s) => ({ ...s, items: [] })) + + for (const name of Object.keys(resolved).sort()) { + const section = groups.find((g) => g.match(name)) + section.items.push({ + name, + light: resolved[name].light, + dark: resolved[name].dark, + description: DESCRIPTIONS[name] ?? '', + kind: swatchKind(name), + on: backing(name) + }) + } + + return groups.filter((g) => g.items.length > 0).map(({ match, ...g }) => g) +} + +export const themeColorGroups = buildThemeColorGroups() diff --git a/apps/storybook/src/stories/foundations/Theme.stories.js b/apps/storybook/src/stories/foundations/Theme.stories.js new file mode 100644 index 000000000..a49a537ad --- /dev/null +++ b/apps/storybook/src/stories/foundations/Theme.stories.js @@ -0,0 +1,48 @@ +import SemanticSwatchGroup from '../../foundations/components/SemanticSwatchGroup.vue' +import { PageContainer, PageHeader } from '../../foundations/components/layout/index.js' +import { themeColorGroups } from '../../foundations/data/theme.js' + +export default { + title: 'Foundations/Theme', + parameters: { + options: { showPanel: false }, + controls: { disable: true }, + actions: { disable: true }, + docs: { + description: { + component: + 'Semantic theme colors — every mode-aware color token that ships in `@aziontech/theme/globals.css`, generated straight from the token source so the catalog can never drift. Each swatch renders the live token, so toggle the theme in the toolbar to see light and dark. Click a row to copy its CSS variable.' + } + } + } +} + +export const Overview = { + name: 'Overview', + render: () => ({ + components: { PageContainer, PageHeader, SemanticSwatchGroup }, + setup() { + return { themeColorGroups } + }, + template: /* html */ ` + + + Semantic color tokens layered on top of the primitive palette. Consume these + var(--*) tokens — or the matching Tailwind + bg-* / text-* / + border-* utilities — never a raw hex, so a single theme + drives both light and dark — toggle the theme in the toolbar to preview each token. + Click any row to copy its CSS variable. + + + + + ` + }) +}