From f05e7e7d332000386b70f0409ba76c4ace378a7a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 17 Oct 2026 13:56:01 +0000 Subject: [PATCH] feat(with-lingui): mirror theme.config SSOT Preserve VITE_BRAND_PRIMARY override while reading shared seeds from theme.config. Co-authored-by: zuiidea --- apps/with-lingui/src/hooks/tokenBuilders.ts | 46 +++++------------ apps/with-lingui/src/theme/theme.config.ts | 57 +++++++++++++++++++++ 2 files changed, 71 insertions(+), 32 deletions(-) create mode 100644 apps/with-lingui/src/theme/theme.config.ts diff --git a/apps/with-lingui/src/hooks/tokenBuilders.ts b/apps/with-lingui/src/hooks/tokenBuilders.ts index 1d67d58..7ee5582 100644 --- a/apps/with-lingui/src/hooks/tokenBuilders.ts +++ b/apps/with-lingui/src/hooks/tokenBuilders.ts @@ -1,34 +1,16 @@ import { theme } from "antd"; import type { ConfigProviderProps, ThemeConfig } from "antd"; +import { readBrandPrimary, themeConfig } from "@/theme/theme.config"; -export function readBrandPrimary(): string { - const raw = import.meta.env.VITE_BRAND_PRIMARY as string | undefined; - if (raw && /^#[0-9a-fA-F]{6}$/.test(raw.trim())) { - return raw.trim(); - } - return "#1677ff"; -} +const { fontFamily, borderRadius, borderRadiusSM, borderRadiusLG, colorBgLayoutLight } = themeConfig.token; const BRAND_PRIMARY = readBrandPrimary(); -export const SHARED_DESIGN_TOKENS = { - fontFamily: - "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif", - - borderRadius: 8, - borderRadiusSM: 6, - borderRadiusLG: 12, -} as const; - -export const CONTROL_COMPONENTS = { - Button: { - primaryShadow: "none", - defaultShadow: "none", - dangerShadow: "none", - }, - Input: { - activeShadow: "none", - }, +const sharedSeed = { + fontFamily, + borderRadius, + borderRadiusSM, + borderRadiusLG, } as const; export function buildTableTokens(cfg: ThemeConfig) { @@ -46,7 +28,7 @@ export const MENU_LIGHT = { itemHoverBg: "rgba(0, 0, 0, 0.04)", itemHoverColor: "rgba(0, 0, 0, 0.88)", itemActiveBg: "rgba(0, 0, 0, 0.08)", - itemBorderRadius: SHARED_DESIGN_TOKENS.borderRadiusSM, + itemBorderRadius: borderRadiusSM, itemMarginInline: 8, horizontalItemSelectedColor: "rgba(0, 0, 0, 0.88)", horizontalItemHoverColor: "rgba(0, 0, 0, 0.88)", @@ -59,7 +41,7 @@ export const MENU_DARK = { itemHoverBg: "rgba(255, 255, 255, 0.06)", itemHoverColor: "rgba(255, 255, 255, 0.85)", itemActiveBg: "rgba(255, 255, 255, 0.1)", - itemBorderRadius: SHARED_DESIGN_TOKENS.borderRadiusSM, + itemBorderRadius: borderRadiusSM, itemMarginInline: 8, horizontalItemSelectedColor: "rgba(255, 255, 255, 0.85)", horizontalItemHoverColor: "rgba(255, 255, 255, 0.85)", @@ -67,11 +49,11 @@ export const MENU_DARK = { export function buildLightThemeConfig(): ConfigProviderProps { const lightSeed: ThemeConfig["token"] = { - colorBgLayout: "#ffffff", + colorBgLayout: colorBgLayoutLight, colorPrimary: BRAND_PRIMARY, colorLink: BRAND_PRIMARY, colorInfo: BRAND_PRIMARY, - ...SHARED_DESIGN_TOKENS, + ...sharedSeed, }; return { @@ -79,7 +61,7 @@ export function buildLightThemeConfig(): ConfigProviderProps { algorithm: theme.defaultAlgorithm, token: lightSeed, components: { - ...CONTROL_COMPONENTS, + ...themeConfig.controlComponents, Table: buildTableTokens({ algorithm: theme.defaultAlgorithm, token: lightSeed, @@ -95,7 +77,7 @@ export function buildDarkThemeConfig(): ConfigProviderProps { colorPrimary: BRAND_PRIMARY, colorLink: BRAND_PRIMARY, colorInfo: BRAND_PRIMARY, - ...SHARED_DESIGN_TOKENS, + ...sharedSeed, }; return { @@ -103,7 +85,7 @@ export function buildDarkThemeConfig(): ConfigProviderProps { algorithm: theme.darkAlgorithm, token: darkSeed, components: { - ...CONTROL_COMPONENTS, + ...themeConfig.controlComponents, Table: buildTableTokens({ algorithm: theme.darkAlgorithm, token: darkSeed, diff --git a/apps/with-lingui/src/theme/theme.config.ts b/apps/with-lingui/src/theme/theme.config.ts new file mode 100644 index 0000000..d27afe5 --- /dev/null +++ b/apps/with-lingui/src/theme/theme.config.ts @@ -0,0 +1,57 @@ +import type { ThemeConfig } from "antd"; + +type ThemeTokenSeeds = { + fontFamily: string; + borderRadius: number; + borderRadiusSM: number; + borderRadiusLG: number; + /** Light layout background only; dark uses algorithm default */ + colorBgLayoutLight: string; + /** Optional brand primary; omit to use Ant Design default / env override */ + colorPrimary?: string; +}; + +/** + * Project theme seeds. Only place for intentional brand/theme literals. + * Customize per project here (e.g. uncomment colorPrimary), or set `VITE_BRAND_PRIMARY`. + */ +export const themeConfig = { + token: { + fontFamily: + "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif", + borderRadius: 8, + borderRadiusSM: 6, + borderRadiusLG: 12, + colorBgLayoutLight: "#ffffff", + // colorPrimary: "#1677ff", + } satisfies ThemeTokenSeeds, + controlComponents: { + Button: { + primaryShadow: "none", + defaultShadow: "none", + dangerShadow: "none", + }, + Input: { + activeShadow: "none", + }, + } satisfies NonNullable, +} as const; + +/** + * Resolve brand primary: `VITE_BRAND_PRIMARY` wins, then optional `themeConfig.token.colorPrimary`, + * then Ant Design-like default `#1677ff`. + */ +export function readBrandPrimary(): string { + const raw = import.meta.env.VITE_BRAND_PRIMARY as string | undefined; + if (raw && /^#[0-9a-fA-F]{6}$/.test(raw.trim())) { + return raw.trim(); + } + const staticPrimary = + "colorPrimary" in themeConfig.token + ? (themeConfig.token as ThemeTokenSeeds).colorPrimary + : undefined; + if (typeof staticPrimary === "string" && staticPrimary.length > 0) { + return staticPrimary; + } + return "#1677ff"; +}