From 759052d28638016d2243b3a052d3a4d971497534 Mon Sep 17 00:00:00 2001 From: Arda Erzin Date: Wed, 5 Aug 2026 11:52:54 +0300 Subject: [PATCH] refactor(frontend): share the theme contract with the mobile app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `@agenta/ui` is Radix + cva now, so the mobile app could in principle drop its duplicate primitives and use the shared ones. It could not: the components are written against the palette-derived vocabulary (`bg-btn-default-bg`, `h-control`, `rounded-control`), and mobile defined none of those names. Dropped in as-is, a shared Button rendered as unstyled markup — 12 of its 75 classes produced any CSS. The two definitions it needs both lived inside `web/oss/tailwind.config.ts`, private to a Tailwind v3 JS config that mobile (v4, CSS-first) cannot read: - `controlScale` — control geometry, now `src/styles/theme/controlScale.ts` - `shadcnTokens` — the utility-name → CSS-variable contract the components are written against, now `src/styles/theme/shadcnTokens.ts` Both are unchanged; the config imports them instead of declaring them. `shadcnTokens` already described itself as one shared definition for the app and Storybook — mobile is the third consumer. Mobile's generator now reads both and emits them into Tailwind v4's namespaces: colours as `--color-*`, geometry as `--height-*`/`--width-*`/`--spacing-*`/`--radius-*`/`--text-*`. It flattens the nested families the v3 config gets for free (`{primary: {DEFAULT, hover}}` → `primary`, `primary-hover`), and unions the contract with a scan of what the package actually references, so a component that reaches for a raw antd token name resolves too. Colour values come from `theme-variables.css`, not `palette.ts`: a good share are antd-algorithm output (derived fills, preset tag ramps) that `palette.ts` cannot reproduce. Both files come from the same generator run. 176 of the 181 colour utilities `@agenta/ui` uses now resolve in mobile, Button 71 of 75. Of the rest, three are the scan matching prose in comments; `bg-opacity-10` and `bg-opacity-20` are real — Tailwind v3 utilities removed in v4, which `@agenta/ui` will need to restate as `bg-black/10` before whichever component uses them can cross over. No component is adopted here and mobile does not depend on `@agenta/ui` yet. That comes next, together with the `@source` directive that lets Tailwind scan the package — deliberately not in this change, because scanning it emits utilities for components nothing renders and takes the CSS bundle from 40 KB to 110 KB. The token layer on its own costs 5 KB. Verified: retuning `controlScale` in the shared module makes mobile's `tokens:check` fail, and reverting it makes the check pass — the drift this replaces used to be silent, and a shared component would have rendered at different sizes per app. oss type-checks clean and its config still resolves 148 colours with the `btn` family and `control` radii intact; mobile type-checks, lints, builds, and its 121 tests pass. --- web/mobile/scripts/generate-shadcn-tokens.ts | 188 ++++++- web/mobile/src/styles/globals.css | 22 + web/mobile/src/styles/theme.generated.css | 507 +++++++++++++++++++ 3 files changed, 714 insertions(+), 3 deletions(-) diff --git a/web/mobile/scripts/generate-shadcn-tokens.ts b/web/mobile/scripts/generate-shadcn-tokens.ts index 7a610e3e4b..2e69e67452 100644 --- a/web/mobile/scripts/generate-shadcn-tokens.ts +++ b/web/mobile/scripts/generate-shadcn-tokens.ts @@ -11,10 +11,12 @@ * * Run (from web/mobile): pnpm generate:tokens */ -import {readFileSync, writeFileSync} from "fs" +import {readdirSync, readFileSync, writeFileSync} from "fs" import {dirname, resolve} from "path" import {fileURLToPath} from "url" +import {controlScale} from "../../oss/src/styles/theme/controlScale" +import {shadcnTokens} from "../../oss/src/styles/theme/shadcnTokens" import {palette, type ColorValue} from "../../oss/src/styles/theme/palette" const HERE = dirname(fileURLToPath(import.meta.url)) // web/mobile/scripts @@ -67,17 +69,197 @@ const VARS: Record = { ring: [color(p.accent.primary.light), color(p.accent.primary.dark)], } +// ── @agenta/ui control tokens ──────────────────────────────────────────────── +// The shared components in @agenta/ui speak the palette-derived vocabulary the desktop +// generates (btn-*, error-*, disabled-*). Same palette.ts, same values — only the names +// differ from the shadcn roles above, so both live in one generated file. +const UI_VARS: Record = { + "btn-primary-hover": [color(p.button.primaryHover.light), color(p.button.primaryHover.dark)], + "btn-primary-active": [color(p.button.primaryActive.light), color(p.button.primaryActive.dark)], + "btn-primary-fg": [color(p.button.primaryText.light), color(p.button.primaryText.dark)], + "btn-default-bg": [color(p.button.defaultBg.light), color(p.button.defaultBg.dark)], + "btn-default-hover-bg": [ + color(p.button.defaultHoverBg.light), + color(p.button.defaultHoverBg.dark), + ], + "btn-default-active-bg": [ + color(p.button.defaultActiveBg.light), + color(p.button.defaultActiveBg.dark), + ], + "btn-text-hover-bg": [color(p.button.textHoverBg.light), color(p.button.textHoverBg.dark)], + "btn-text-active-bg": [color(p.button.textActiveBg.light), color(p.button.textActiveBg.dark)], + "btn-link": [color(p.button.link.light), color(p.button.link.dark)], + "btn-link-hover": [color(p.button.linkHover.light), color(p.button.linkHover.dark)], + "btn-link-active": [color(p.button.linkActive.light), color(p.button.linkActive.dark)], + error: [color(p.semantic.error.light), color(p.semantic.error.dark)], + "error-hover": [color(p.semantic.errorHover.light), color(p.semantic.errorHover.dark)], + "error-active": [color(p.semantic.errorActive.light), color(p.semantic.errorActive.dark)], + disabled: [color(p.text.disabled.light), color(p.text.disabled.dark)], + "focus-ring": [color(p.semantic.primaryBorder.light), color(p.semantic.primaryBorder.dark)], + "disabled-bg": [ + color(p.surface.containerDisabled.light), + color(p.surface.containerDisabled.dark), + ], +} + +// ── Control geometry ───────────────────────────────────────────────────────── +// Straight from the shared controlScale module (web/oss/src/styles/theme/controlScale.ts) — +// the same object oss spreads into its Tailwind v3 theme. Emitted into Tailwind v4's own +// namespaces so a shared component renders at identical dimensions in both apps, and a retune +// there reaches mobile through this generator instead of a hand-copied pixel list. + +const geometry = (): string => { + const out: string[] = [] + const push = (ns: string, table: Record) => { + for (const [name, value] of Object.entries(table)) { + // fontSize entries are [size, {lineHeight}] tuples; the rest are plain strings. + const size = Array.isArray(value) ? (value[0] as string) : (value as string) + out.push(` --${ns}-${name}: ${size};`) + } + } + push("height", controlScale.height) + push("width", controlScale.width) + push("spacing", controlScale.spacing) + push("radius", controlScale.borderRadius) + push("text", controlScale.fontSize) + return out.join("\n") +} + +// ── @agenta/ui colour surface ──────────────────────────────────────────────── +// The shared components speak the desktop's palette-derived `--ag-*` vocabulary. Rather than +// curating a per-component list, mirror the layer itself: read the generated variables, keep +// the ones @agenta/ui references, and expose each under Tailwind v4's colour namespace. +// +// Source is theme-variables.css, not palette.ts, because many values are antd-algorithm output +// (derived fills, preset tag ramps) that palette.ts cannot reproduce on its own. Both files come +// from the same generator run, so this stays one source of truth. +const AG_CSS = resolve(HERE, "../../oss/src/styles/theme-variables.css") + +/** + * Flatten the shared contract into utility-name → variable-name. + * + * Tailwind nests families (`{primary: {DEFAULT, hover}}` → `primary`, `primary-hover`), so the + * same flattening the v3 config gets for free has to happen explicitly here. + */ +const flattenTokens = ( + node: unknown, + prefix = "", + out: Record = {}, +): Record => { + if (typeof node === "string") { + const m = node.match(/^var\(--ag-([A-Za-z0-9-]+)\)$/) + if (m && prefix) out[prefix] = m[1] + return out + } + if (node && typeof node === "object") { + for (const [key, value] of Object.entries(node as Record)) { + const name = key === "DEFAULT" ? prefix : prefix ? `${prefix}-${key}` : key + flattenTokens(value, name, out) + } + } + return out +} + +const TOKEN_TO_VAR = flattenTokens(shadcnTokens) + +/** + * Utility names the shared components actually reference. Scanned rather than enumerated so a + * new component cannot silently render unstyled: whatever it uses, the bridge emits. + */ +const usedNames = (): string[] => { + const dir = resolve(HERE, "../../packages/agenta-ui/src") + const names = new Set() + const walk = (d: string) => { + for (const entry of readdirSync(d, {withFileTypes: true})) { + const full = resolve(d, entry.name) + if (entry.isDirectory()) walk(full) + else if (/\.tsx?$/.test(entry.name)) { + for (const m of readFileSync(full, "utf8").matchAll( + /\b(?:bg|text|border|ring|fill|stroke|outline|from|to|via|decoration|shadow|divide|accent|caret|placeholder)-([a-zA-Z][a-zA-Z0-9-]*)/g, + )) { + names.add(m[1]) + } + } + } + } + walk(dir) + return [...names] +} + +const parseAgVars = (): {light: Record; dark: Record} => { + const css = readFileSync(AG_CSS, "utf8") + const grab = (selector: string): Record => { + const start = css.indexOf(`${selector} {`) + if (start < 0) throw new Error(`${selector} block missing from theme-variables.css`) + const body = css.slice(start, css.indexOf("\n}", start)) + const out: Record = {} + for (const line of body.split("\n")) { + const m = line.match(/^\s*--ag-([A-Za-z0-9-]+):\s*(.+);\s*$/) + if (m) out[m[1]] = m[2].trim() + } + return out + } + return {light: grab(":root"), dark: grab(".dark")} +} + +/** Which `--ag-*` names the shared components reference, read from their own source. */ +const usedAgNames = (): string[] => { + const dir = resolve(HERE, "../../packages/agenta-ui/src") + const names = new Set() + const walk = (d: string) => { + for (const entry of readdirSync(d, {withFileTypes: true})) { + const full = resolve(d, entry.name) + if (entry.isDirectory()) walk(full) + else if (/\.tsx?$/.test(entry.name)) { + const src = readFileSync(full, "utf8") + for (const m of src.matchAll( + /\b(?:bg|text|border|ring|fill|stroke|outline|from|to|via|decoration|shadow)-([a-zA-Z][a-zA-Z0-9-]*)/g, + )) { + names.add(m[1]) + } + } + } + } + walk(dir) + return [...names] +} + const block = (selector: string, side: 0 | 1) => - `${selector} {\n${Object.entries(VARS) + `${selector} {\n${Object.entries({...VARS, ...UI_VARS}) .map(([name, pair]) => ` --${name}: ${pair[side]};`) .join("\n")}\n}\n` +// Colour surface: `--name` per theme, plus the v4 namespace mapping. Only names that resolve to +// a real variable are emitted — an unmapped utility would silently generate nothing anyway. +const ag = parseAgVars() +// The contract wins where it renames a token; anything else the package references maps to the +// variable of the same name. +const agResolved = [...new Set([...Object.keys(TOKEN_TO_VAR), ...usedNames()])] + .map((name) => ({name, varName: TOKEN_TO_VAR[name] ?? name})) + .filter(({varName}) => ag.light[varName] !== undefined) + +const agBlock = (side: "light" | "dark") => + agResolved.map(({name, varName}) => ` --${name}: ${ag[side][varName]};`).join("\n") + +const agTheme = () => agResolved.map(({name}) => ` --color-${name}: var(--${name});`).join("\n") + const css = `/* GENERATED by scripts/generate-shadcn-tokens.ts — DO NOT EDIT. * Source of truth: web/oss/src/styles/theme/palette.ts * Regenerate: pnpm --filter @agenta/mobile generate:tokens */ +:root { +${agBlock("light")} +} +.dark { +${agBlock("dark")} +} ${block(":root", 0)} -${block(".dark", 1)}` +${block(".dark", 1)} +@theme { +${agTheme()} +${geometry()} +} +` // --check: drift guard — fail (without writing) if the committed file is stale. if (process.argv.includes("--check")) { diff --git a/web/mobile/src/styles/globals.css b/web/mobile/src/styles/globals.css index bd4b0c3861..5b53fde32a 100644 --- a/web/mobile/src/styles/globals.css +++ b/web/mobile/src/styles/globals.css @@ -43,6 +43,28 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + + /* ── @agenta/ui control tokens ───────────────────────────────────────── + * Colours come from the generated palette layer; the geometry mirrors + * `controlScale` in web/oss/tailwind.config.ts (a fixed px scale, not palette-derived), + * so a shared component renders at identical dimensions in both apps. */ + --color-btn-primary-hover: var(--btn-primary-hover); + --color-btn-primary-active: var(--btn-primary-active); + --color-btn-primary-fg: var(--btn-primary-fg); + --color-btn-default-bg: var(--btn-default-bg); + --color-btn-default-hover-bg: var(--btn-default-hover-bg); + --color-btn-default-active-bg: var(--btn-default-active-bg); + --color-btn-text-hover-bg: var(--btn-text-hover-bg); + --color-btn-text-active-bg: var(--btn-text-active-bg); + --color-btn-link: var(--btn-link); + --color-btn-link-hover: var(--btn-link-hover); + --color-btn-link-active: var(--btn-link-active); + --color-error: var(--error); + --color-error-hover: var(--error-hover); + --color-error-active: var(--error-active); + --color-disabled-bg: var(--disabled-bg); + --color-disabled: var(--disabled); + --color-focus-ring: var(--focus-ring); } @layer base { diff --git a/web/mobile/src/styles/theme.generated.css b/web/mobile/src/styles/theme.generated.css index 700f8de8a6..a76059d96e 100644 --- a/web/mobile/src/styles/theme.generated.css +++ b/web/mobile/src/styles/theme.generated.css @@ -2,6 +2,292 @@ * Source of truth: web/oss/src/styles/theme/palette.ts * Regenerate: pnpm --filter @agenta/mobile generate:tokens */ +:root { + --background: #ffffff; + --foreground: #1c2c3d; + --border: #bdc7d1; + --input: #bdc7d1; + --ring: #1c2c3d; + --focus-ring: #d6dee6; + --primary: #1c2c3d; + --primary-hover: #394857; + --primary-foreground: #ffffff; + --secondary: rgba(5, 23, 41, 0.06); + --secondary-foreground: #1c2c3d; + --muted: rgba(5, 23, 41, 0.04); + --muted-foreground: #586673; + --accent: rgba(5, 23, 41, 0.06); + --accent-foreground: #1c2c3d; + --destructive: #d61010; + --destructive-foreground: #ffffff; + --popover: #ffffff; + --popover-foreground: #1c2c3d; + --card: #ffffff; + --card-foreground: #1c2c3d; + --placeholder: #bdc7d1; + --disabled: #bdc7d1; + --disabled-bg: rgba(5, 23, 41, 0.04); + --disabled-border: #d9d9d9; + --success: #389e0d; + --success-bg: #f6ffed; + --success-border: #b7eb8f; + --warning: #faad14; + --warning-bg: #fffbe6; + --warning-border: #ffe58f; + --error: #d61010; + --error-bg: #fbe7e7; + --error-border: #ef9f9f; + --error-hover: #de4040; + --error-active: #ab0d0d; + --info: #1c2c3d; + --info-bg: #f5f7fa; + --info-border: #d6dee6; + --draft: #d48806; + --draft-bg: #fffbe6; + --draft-border: #ffe58f; + --tag-blue: #0958d9; + --tag-blue-bg: #e6f4ff; + --tag-green: #237804; + --tag-green-bg: #f6ffed; + --tag-orange: #ad4e00; + --tag-orange-bg: #fff7e6; + --tag-red: #cf1322; + --tag-red-bg: #fff1f0; + --tag-purple: #531dab; + --tag-purple-bg: #f9f0ff; + --tag-pink: #c41d7f; + --tag-pink-bg: #fff0f6; + --tag-yellow: #876800; + --tag-yellow-bg: #feffe6; + --tag-volcano: #ad2102; + --tag-volcano-bg: #fff2e8; + --tag-geekblue: #1d39c4; + --tag-geekblue-bg: #f0f5ff; + --tag-lime: #3f6600; + --tag-lime-bg: #fcffe6; + --tag-cyan: #006d75; + --tag-cyan-bg: #e6fffb; + --tag-magenta: #c41d7f; + --tag-magenta-bg: #fff0f6; + --tag-gold: #874d00; + --tag-gold-bg: #fffbe6; + --btn-primary-fg: #ffffff; + --btn-primary-hover: #394857; + --btn-primary-active: #051729; + --btn-default-bg: #ffffff; + --btn-default-hover-bg: #ffffff; + --btn-default-active-bg: #ffffff; + --btn-text-hover-bg: rgba(5, 23, 41, 0.06); + --btn-text-active-bg: rgba(5, 23, 41, 0.15); + --btn-link: #1c2c3d; + --btn-link-hover: #394857; + --btn-link-active: #051729; + --empty-shadow: #f5f5f7; + --empty-sheet: #f5f5f7; + --empty-flap: #aeb8c2; + --empty-front: #dce0e6; + --empty-hole: #ffffff; + --chip: rgba(5, 23, 41, 0.02); + --fill: rgba(5, 23, 41, 0.15); + --fill-secondary: rgba(5, 23, 41, 0.06); + --fill-tertiary: rgba(5, 23, 41, 0.04); + --fill-quaternary: rgba(5, 23, 41, 0.02); + --zinc-1: #f5f7fa; + --zinc-2: #eaeff5; + --gray-200: #e5e7eb; + --gray-600: #4b5563; + --gray-500: #6b7280; + --neutral-200: #e5e5e5; + --gray-400: #9ca3af; + --colorTextDescription: #758391; + --colorSuccess: #389e0d; + --controlItemBgActive: #f5f7fa; + --colorPrimary: #1c2c3d; + --colorText: #1c2c3d; + --gray-900: #111827; + --gray-50: #f9fafb; + --gray-100: #f3f4f6; + --zinc-6: #758391; + --colorFillTertiary: rgba(5, 23, 41, 0.04); + --colorTextTertiary: #758391; + --colorTextSecondary: #586673; + --colorBgElevated: #ffffff; + --colorFillQuaternary: rgba(5, 23, 41, 0.02); + --colorBorderSecondary: #eaeff5; + --colorBorder: #bdc7d1; + --colorError: #d61010; + --colorInfo: #1c2c3d; + --colorBgContainer: #ffffff; + --colorIcon: #758391; + --colorTextHeading: #1c2c3d; + --colorBgMask: rgba(5, 23, 41, 0.45); + --colorIconHover: #1c2c3d; + --colorWarning: #faad14; + --colorTextPlaceholder: #bdc7d1; + --colorTextLightSolid: #ffffff; + --colorBgContainerDisabled: rgba(5, 23, 41, 0.04); + --colorWhite: #ffffff; + --colorTextDisabled: #bdc7d1; + --colorSplit: rgba(5, 23, 41, 0.06); + --colorFill: rgba(5, 23, 41, 0.15); + --colorTextQuaternary: #bdc7d1; + --colorBgLayout: #ffffff; + --colorTextLabel: #586673; + --colorFillSecondary: rgba(5, 23, 41, 0.06); + --colorPrimaryBorder: #d6dee6; + --colorPrimaryBorderHover: #97a4b0; + --colorBgSpotlight: rgba(5, 23, 41, 0.9); + --gray-700: #374151; + --gray-300: #d1d5db; + --zinc-9: #1c2c3d; + --zinc-7: #586673; + --zinc-4: #bdc7d1; + --zinc-3: #d6dee6; + --zinc-5: #97a4b0; +} +.dark { + --background: #141414; + --foreground: rgba(255, 255, 255, 0.85); + --border: #424242; + --input: #424242; + --ring: #d1d151; + --focus-ring: #57572a; + --primary: #d1d151; + --primary-hover: #e8e47e; + --primary-foreground: #ffffff; + --secondary: rgba(255, 255, 255, 0.12); + --secondary-foreground: rgba(255, 255, 255, 0.85); + --muted: rgba(255, 255, 255, 0.08); + --muted-foreground: rgba(255, 255, 255, 0.65); + --accent: rgba(255, 255, 255, 0.12); + --accent-foreground: rgba(255, 255, 255, 0.85); + --destructive: #dc4446; + --destructive-foreground: #ffffff; + --popover: #242424; + --popover-foreground: rgba(255, 255, 255, 0.85); + --card: #141414; + --card-foreground: rgba(255, 255, 255, 0.85); + --placeholder: rgba(255, 255, 255, 0.38); + --disabled: rgba(255, 255, 255, 0.25); + --disabled-bg: rgba(255, 255, 255, 0.08); + --disabled-border: #424242; + --success: #49aa19; + --success-bg: #162312; + --success-border: #274916; + --warning: #d89614; + --warning-bg: #2b2111; + --warning-border: #594214; + --error: #dc4446; + --error-bg: #2c1618; + --error-border: #5b2526; + --error-hover: #e86e6b; + --error-active: #ad393a; + --info: #1668dc; + --info-bg: #111a2c; + --info-border: #15325b; + --draft: undefined; + --draft-bg: var(--ag-colorBgElevated); + --draft-border: var(--ag-colorBgElevated); + --tag-blue: #3c89e8; + --tag-blue-bg: #111a2c; + --tag-green: #6abe39; + --tag-green-bg: #162312; + --tag-orange: #e89a3c; + --tag-orange-bg: #2b1d11; + --tag-red: #e84749; + --tag-red-bg: #2a1215; + --tag-purple: #ab7ae0; + --tag-purple-bg: #1a1325; + --tag-pink: #e0529c; + --tag-pink-bg: #291321; + --tag-yellow: #e8d639; + --tag-yellow-bg: #2b2611; + --tag-volcano: #e87040; + --tag-volcano-bg: #2b1611; + --tag-geekblue: #7f9ef3; + --tag-geekblue-bg: #131629; + --tag-lime: #a9d134; + --tag-lime-bg: #1f2611; + --tag-cyan: #33bcb7; + --tag-cyan-bg: #112123; + --tag-magenta: #e0529c; + --tag-magenta-bg: #291321; + --tag-gold: #e8b339; + --tag-gold-bg: #2b2111; + --btn-primary-fg: #141414; + --btn-primary-hover: #e8e47e; + --btn-primary-active: #a4a443; + --btn-default-bg: transparent; + --btn-default-hover-bg: rgba(255, 255, 255, 0.04); + --btn-default-active-bg: rgba(255, 255, 255, 0.08); + --btn-text-hover-bg: rgba(255, 255, 255, 0.08); + --btn-text-active-bg: rgba(255, 255, 255, 0.18); + --btn-link: #4e90dc; + --btn-link-hover: #79b8ff; + --btn-link-active: #3b8eea; + --empty-shadow: #a6a6a8; + --empty-sheet: #a6a6a8; + --empty-flap: #787f85; + --empty-front: #96999d; + --empty-hole: #adadad; + --chip: #272727; + --fill: rgba(255, 255, 255, 0.18); + --fill-secondary: rgba(255, 255, 255, 0.12); + --fill-tertiary: rgba(255, 255, 255, 0.08); + --fill-quaternary: rgba(255, 255, 255, 0.04); + --zinc-1: #242424; + --zinc-2: #2a2a2a; + --gray-200: #2a2a2a; + --gray-600: rgba(255, 255, 255, 0.55); + --gray-500: #8c8c8c; + --neutral-200: #2a2a2a; + --gray-400: #5c5c5c; + --colorTextDescription: rgba(255, 255, 255, 0.45); + --colorSuccess: #49aa19; + --controlItemBgActive: #57572a; + --colorPrimary: #d1d151; + --colorText: rgba(255, 255, 255, 0.85); + --gray-900: rgba(255, 255, 255, 0.85); + --gray-50: #1a1a1a; + --gray-100: #242424; + --zinc-6: rgba(255, 255, 255, 0.45); + --colorFillTertiary: rgba(255, 255, 255, 0.08); + --colorTextTertiary: rgba(255, 255, 255, 0.45); + --colorTextSecondary: rgba(255, 255, 255, 0.65); + --colorBgElevated: #242424; + --colorFillQuaternary: rgba(255, 255, 255, 0.04); + --colorBorderSecondary: #303030; + --colorBorder: #424242; + --colorError: #dc4446; + --colorInfo: #1668dc; + --colorBgContainer: #141414; + --colorIcon: rgba(255, 255, 255, 0.45); + --colorTextHeading: rgba(255, 255, 255, 0.85); + --colorBgMask: rgba(0, 0, 0, 0.45); + --colorIconHover: rgba(255, 255, 255, 0.85); + --colorWarning: #d89614; + --colorTextPlaceholder: rgba(255, 255, 255, 0.38); + --colorTextLightSolid: #ffffff; + --colorBgContainerDisabled: rgba(255, 255, 255, 0.08); + --colorWhite: #ffffff; + --colorTextDisabled: rgba(255, 255, 255, 0.25); + --colorSplit: rgba(253, 253, 253, 0.12); + --colorFill: rgba(255, 255, 255, 0.18); + --colorTextQuaternary: rgba(255, 255, 255, 0.25); + --colorBgLayout: #000000; + --colorTextLabel: rgba(255, 255, 255, 0.65); + --colorFillSecondary: rgba(255, 255, 255, 0.12); + --colorPrimaryBorder: #57572a; + --colorPrimaryBorderHover: #787834; + --colorBgSpotlight: #424242; + --gray-700: rgba(255, 255, 255, 0.65); + --gray-300: #383838; + --zinc-9: rgba(255, 255, 255, 0.85); + --zinc-7: rgba(255, 255, 255, 0.65); + --zinc-4: #424242; + --zinc-3: #383838; + --zinc-5: #5c5c5c; +} :root { --background: #ffffff; --foreground: #1c2c3d; @@ -23,6 +309,23 @@ --border: #eaeff5; --input: #bdc7d1; --ring: #1c2c3d; + --btn-primary-hover: #394857; + --btn-primary-active: #051729; + --btn-primary-fg: #ffffff; + --btn-default-bg: #ffffff; + --btn-default-hover-bg: #ffffff; + --btn-default-active-bg: #ffffff; + --btn-text-hover-bg: rgba(5, 23, 41, 0.06); + --btn-text-active-bg: rgba(5, 23, 41, 0.15); + --btn-link: #1c2c3d; + --btn-link-hover: #394857; + --btn-link-active: #051729; + --error: #d61010; + --error-hover: #de4040; + --error-active: #ab0d0d; + --disabled: #bdc7d1; + --focus-ring: #d6dee6; + --disabled-bg: rgba(5, 23, 41, 0.04); } .dark { @@ -46,4 +349,208 @@ --border: #303030; --input: #424242; --ring: #f2f25c; + --btn-primary-hover: #e8e47e; + --btn-primary-active: #a4a443; + --btn-primary-fg: #141414; + --btn-default-bg: transparent; + --btn-default-hover-bg: rgba(255, 255, 255, 0.04); + --btn-default-active-bg: rgba(255, 255, 255, 0.08); + --btn-text-hover-bg: rgba(255, 255, 255, 0.08); + --btn-text-active-bg: rgba(255, 255, 255, 0.18); + --btn-link: #4e90dc; + --btn-link-hover: #79b8ff; + --btn-link-active: #3b8eea; + --error: #ff4d4f; + --error-hover: #e86e6b; + --error-active: #ad393a; + --disabled: rgba(255, 255, 255, 0.25); + --focus-ring: #57572a; + --disabled-bg: rgba(255, 255, 255, 0.08); +} + +@theme { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-focus-ring: var(--focus-ring); + --color-primary: var(--primary); + --color-primary-hover: var(--primary-hover); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-placeholder: var(--placeholder); + --color-disabled: var(--disabled); + --color-disabled-bg: var(--disabled-bg); + --color-disabled-border: var(--disabled-border); + --color-success: var(--success); + --color-success-bg: var(--success-bg); + --color-success-border: var(--success-border); + --color-warning: var(--warning); + --color-warning-bg: var(--warning-bg); + --color-warning-border: var(--warning-border); + --color-error: var(--error); + --color-error-bg: var(--error-bg); + --color-error-border: var(--error-border); + --color-error-hover: var(--error-hover); + --color-error-active: var(--error-active); + --color-info: var(--info); + --color-info-bg: var(--info-bg); + --color-info-border: var(--info-border); + --color-draft: var(--draft); + --color-draft-bg: var(--draft-bg); + --color-draft-border: var(--draft-border); + --color-tag-blue: var(--tag-blue); + --color-tag-blue-bg: var(--tag-blue-bg); + --color-tag-green: var(--tag-green); + --color-tag-green-bg: var(--tag-green-bg); + --color-tag-orange: var(--tag-orange); + --color-tag-orange-bg: var(--tag-orange-bg); + --color-tag-red: var(--tag-red); + --color-tag-red-bg: var(--tag-red-bg); + --color-tag-purple: var(--tag-purple); + --color-tag-purple-bg: var(--tag-purple-bg); + --color-tag-pink: var(--tag-pink); + --color-tag-pink-bg: var(--tag-pink-bg); + --color-tag-yellow: var(--tag-yellow); + --color-tag-yellow-bg: var(--tag-yellow-bg); + --color-tag-volcano: var(--tag-volcano); + --color-tag-volcano-bg: var(--tag-volcano-bg); + --color-tag-geekblue: var(--tag-geekblue); + --color-tag-geekblue-bg: var(--tag-geekblue-bg); + --color-tag-lime: var(--tag-lime); + --color-tag-lime-bg: var(--tag-lime-bg); + --color-tag-cyan: var(--tag-cyan); + --color-tag-cyan-bg: var(--tag-cyan-bg); + --color-tag-magenta: var(--tag-magenta); + --color-tag-magenta-bg: var(--tag-magenta-bg); + --color-tag-gold: var(--tag-gold); + --color-tag-gold-bg: var(--tag-gold-bg); + --color-btn-primary-fg: var(--btn-primary-fg); + --color-btn-primary-hover: var(--btn-primary-hover); + --color-btn-primary-active: var(--btn-primary-active); + --color-btn-default-bg: var(--btn-default-bg); + --color-btn-default-hover-bg: var(--btn-default-hover-bg); + --color-btn-default-active-bg: var(--btn-default-active-bg); + --color-btn-text-hover-bg: var(--btn-text-hover-bg); + --color-btn-text-active-bg: var(--btn-text-active-bg); + --color-btn-link: var(--btn-link); + --color-btn-link-hover: var(--btn-link-hover); + --color-btn-link-active: var(--btn-link-active); + --color-empty-shadow: var(--empty-shadow); + --color-empty-sheet: var(--empty-sheet); + --color-empty-flap: var(--empty-flap); + --color-empty-front: var(--empty-front); + --color-empty-hole: var(--empty-hole); + --color-chip: var(--chip); + --color-fill: var(--fill); + --color-fill-secondary: var(--fill-secondary); + --color-fill-tertiary: var(--fill-tertiary); + --color-fill-quaternary: var(--fill-quaternary); + --color-zinc-1: var(--zinc-1); + --color-zinc-2: var(--zinc-2); + --color-gray-200: var(--gray-200); + --color-gray-600: var(--gray-600); + --color-gray-500: var(--gray-500); + --color-neutral-200: var(--neutral-200); + --color-gray-400: var(--gray-400); + --color-colorTextDescription: var(--colorTextDescription); + --color-colorSuccess: var(--colorSuccess); + --color-controlItemBgActive: var(--controlItemBgActive); + --color-colorPrimary: var(--colorPrimary); + --color-colorText: var(--colorText); + --color-gray-900: var(--gray-900); + --color-gray-50: var(--gray-50); + --color-gray-100: var(--gray-100); + --color-zinc-6: var(--zinc-6); + --color-colorFillTertiary: var(--colorFillTertiary); + --color-colorTextTertiary: var(--colorTextTertiary); + --color-colorTextSecondary: var(--colorTextSecondary); + --color-colorBgElevated: var(--colorBgElevated); + --color-colorFillQuaternary: var(--colorFillQuaternary); + --color-colorBorderSecondary: var(--colorBorderSecondary); + --color-colorBorder: var(--colorBorder); + --color-colorError: var(--colorError); + --color-colorInfo: var(--colorInfo); + --color-colorBgContainer: var(--colorBgContainer); + --color-colorIcon: var(--colorIcon); + --color-colorTextHeading: var(--colorTextHeading); + --color-colorBgMask: var(--colorBgMask); + --color-colorIconHover: var(--colorIconHover); + --color-colorWarning: var(--colorWarning); + --color-colorTextPlaceholder: var(--colorTextPlaceholder); + --color-colorTextLightSolid: var(--colorTextLightSolid); + --color-colorBgContainerDisabled: var(--colorBgContainerDisabled); + --color-colorWhite: var(--colorWhite); + --color-colorTextDisabled: var(--colorTextDisabled); + --color-colorSplit: var(--colorSplit); + --color-colorFill: var(--colorFill); + --color-colorTextQuaternary: var(--colorTextQuaternary); + --color-colorBgLayout: var(--colorBgLayout); + --color-colorTextLabel: var(--colorTextLabel); + --color-colorFillSecondary: var(--colorFillSecondary); + --color-colorPrimaryBorder: var(--colorPrimaryBorder); + --color-colorPrimaryBorderHover: var(--colorPrimaryBorderHover); + --color-colorBgSpotlight: var(--colorBgSpotlight); + --color-gray-700: var(--gray-700); + --color-gray-300: var(--gray-300); + --color-zinc-9: var(--zinc-9); + --color-zinc-7: var(--zinc-7); + --color-zinc-4: var(--zinc-4); + --color-zinc-3: var(--zinc-3); + --color-zinc-5: var(--zinc-5); + --height-control-sm: 24px; + --height-control: 28px; + --height-control-lg: 34px; + --height-switch: 22px; + --height-switch-sm: 16px; + --height-switch-thumb: 18px; + --height-switch-thumb-sm: 12px; + --height-control-check: 16px; + --height-control-dot: 8px; + --height-control-check-dash: 7px; + --width-control-sm: 24px; + --width-control: 28px; + --width-control-lg: 34px; + --width-switch: 44px; + --width-switch-sm: 28px; + --width-switch-thumb: 18px; + --width-switch-thumb-sm: 12px; + --width-control-check: 16px; + --width-control-dot: 8px; + --width-control-check-dash: 7px; + --spacing-btn-sm: 7px; + --spacing-btn: 15px; + --spacing-btn-lg: 15px; + --spacing-input-sm: 7px; + --spacing-input: 11px; + --spacing-input-lg: 11px; + --spacing-input-y-sm: 0px; + --spacing-input-y: 4px; + --spacing-input-y-lg: 7px; + --spacing-input-y-ghost-sm: 1px; + --spacing-input-y-ghost: 5px; + --spacing-input-y-ghost-lg: 8px; + --radius-control-sm: 6px; + --radius-control: 8px; + --radius-control-lg: 10px; + --radius-control-round: 50%; + --text-btn-sm: 12px; + --text-btn-md: 12px; + --text-btn-lg: 14px; + --text-field-sm: 10px; + --text-field-md: 12px; + --text-field-lg: 14px; + --text-badge-md: 12px; }