@ankhorage/surface 0.2.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/examples/DocsExamples.d.ts.map +1 -1
  3. package/dist/examples/DocsExamples.js +0 -2
  4. package/dist/examples/DocsExamples.js.map +1 -1
  5. package/dist/internal/resolvers/resolveControlSize.d.ts +2 -2
  6. package/dist/internal/resolvers/resolveControlSize.d.ts.map +1 -1
  7. package/dist/internal/resolvers/resolveControlSize.js.map +1 -1
  8. package/dist/internal/resolvers/resolveIconSize.d.ts +2 -2
  9. package/dist/internal/resolvers/resolveIconSize.d.ts.map +1 -1
  10. package/dist/internal/resolvers/resolveIconSize.js.map +1 -1
  11. package/dist/internal/resolvers/resolveInteractiveColors.d.ts +3 -3
  12. package/dist/internal/resolvers/resolveInteractiveColors.d.ts.map +1 -1
  13. package/dist/internal/resolvers/resolveInteractiveColors.js.map +1 -1
  14. package/dist/internal/resolvers/resolveSelectionControlColors.d.ts +2 -2
  15. package/dist/internal/resolvers/resolveSelectionControlColors.d.ts.map +1 -1
  16. package/dist/internal/resolvers/resolveSelectionControlColors.js.map +1 -1
  17. package/dist/internal/resolvers/resolveTextColor.d.ts +3 -3
  18. package/dist/internal/resolvers/resolveTextColor.d.ts.map +1 -1
  19. package/dist/internal/resolvers/resolveTextColor.js.map +1 -1
  20. package/dist/internal/resolvers/resolveTextStyles.d.ts +3 -3
  21. package/dist/internal/resolvers/resolveTextStyles.d.ts.map +1 -1
  22. package/dist/internal/resolvers/resolveTextStyles.js.map +1 -1
  23. package/dist/internal/resolvers/resolveTone.d.ts +2 -2
  24. package/dist/internal/resolvers/resolveTone.d.ts.map +1 -1
  25. package/dist/internal/resolvers/resolveTone.js.map +1 -1
  26. package/dist/layout/Container.d.ts +2 -2
  27. package/dist/layout/Container.d.ts.map +1 -1
  28. package/dist/layout/Container.js.map +1 -1
  29. package/dist/layout/helpers.d.ts +9 -9
  30. package/dist/layout/helpers.d.ts.map +1 -1
  31. package/dist/layout/helpers.js.map +1 -1
  32. package/dist/primitives/heading/resolveHeadingStyle.d.ts +2 -2
  33. package/dist/primitives/heading/resolveHeadingStyle.d.ts.map +1 -1
  34. package/dist/primitives/heading/resolveHeadingStyle.js.map +1 -1
  35. package/dist/primitives/icon/Icon.d.ts +3 -3
  36. package/dist/primitives/icon/Icon.d.ts.map +1 -1
  37. package/dist/primitives/icon/Icon.js.map +1 -1
  38. package/dist/theme/ThemeContext.d.ts +3 -3
  39. package/dist/theme/ThemeContext.d.ts.map +1 -1
  40. package/dist/theme/ThemeContext.js.map +1 -1
  41. package/dist/theme/colorEngine.d.ts +10 -11
  42. package/dist/theme/colorEngine.d.ts.map +1 -1
  43. package/dist/theme/colorEngine.js +102 -412
  44. package/dist/theme/colorEngine.js.map +1 -1
  45. package/dist/theme/createTheme.d.ts +3 -3
  46. package/dist/theme/createTheme.d.ts.map +1 -1
  47. package/dist/theme/createTheme.js +2 -4
  48. package/dist/theme/createTheme.js.map +1 -1
  49. package/dist/theme/types.d.ts +5 -17
  50. package/dist/theme/types.d.ts.map +1 -1
  51. package/dist/theme/types.js.map +1 -1
  52. package/package.json +3 -4
  53. package/src/examples/DocsExamples.tsx +0 -2
  54. package/src/internal/resolvers/resolveControlSize.ts +5 -2
  55. package/src/internal/resolvers/resolveIconSize.ts +2 -2
  56. package/src/internal/resolvers/resolveInteractiveColors.ts +3 -3
  57. package/src/internal/resolvers/resolveSelectionControlColors.ts +2 -2
  58. package/src/internal/resolvers/resolveTextColor.ts +3 -3
  59. package/src/internal/resolvers/resolveTextStyles.ts +6 -6
  60. package/src/internal/resolvers/resolveTone.ts +2 -2
  61. package/src/layout/Container.tsx +2 -2
  62. package/src/layout/helpers.test.ts +2 -2
  63. package/src/layout/helpers.ts +12 -9
  64. package/src/primitives/heading/resolveHeadingStyle.ts +2 -2
  65. package/src/primitives/icon/Icon.tsx +3 -3
  66. package/src/theme/ThemeContext.tsx +2 -2
  67. package/src/theme/colorEngine.test.ts +158 -154
  68. package/src/theme/colorEngine.ts +128 -477
  69. package/src/theme/createTheme.ts +6 -8
  70. package/src/theme/types.ts +15 -18
  71. package/src/utils/deepMerge.test.ts +0 -4
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../../src/primitives/icon/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,SAAS,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,SAAS,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;IAC3C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,QAAqB,EACrB,IAAU,EACV,KAAc,EACd,KAAK,EACL,MAAM,GACP,EAAE,SAAS;;;;;;8CAaX"}
1
+ {"version":3,"file":"Icon.d.ts","sourceRoot":"","sources":["../../../src/primitives/icon/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,YAAY,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,YAAY,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC;IAC9C,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,IAAI,CAAC,EACnB,IAAI,EACJ,QAAqB,EACrB,IAAU,EACV,KAAc,EACd,KAAK,EACL,MAAM,GACP,EAAE,SAAS;;;;;;8CAaX"}
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.js","sourceRoot":"","sources":["../../../src/primitives/icon/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAkC,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAatE,MAAM,UAAU,IAAI,CAAC,EACnB,IAAI,EACJ,QAAQ,GAAG,UAAU,EACrB,IAAI,GAAG,GAAG,EACV,KAAK,GAAG,MAAM,EACd,KAAK,EACL,MAAM,GACI;IACV,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC7B,MAAM,aAAa,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,YAAY,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzF,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAExD,OAAO,KAAK,CAAC,aAAa,CAAC,aAAa,EAAE;QACxC,KAAK,EAAE,aAAa;QACpB,IAAI;QACJ,IAAI,EAAE,YAAY;QAClB,KAAK;QACL,MAAM;KACP,CAAC,CAAC;AACL,CAAC","sourcesContent":["import React from 'react';\nimport { type StyleProp, type TextStyle } from 'react-native';\n\nimport { resolveToken } from '../../theme/resolveToken';\nimport { useTheme } from '../../theme/ThemeContext';\nimport type { AnkhTheme } from '../../theme/types';\nimport { resolveExpoIconComponent } from './resolveExpoIconComponent';\n\nexport type IconProvider = string;\n\nexport interface IconProps {\n name: string;\n provider?: IconProvider;\n size?: keyof AnkhTheme['spacing'] | number;\n color?: keyof AnkhTheme['colors'] | string;\n style?: StyleProp<TextStyle>;\n testID?: string;\n}\n\nexport function Icon({\n name,\n provider = 'Ionicons',\n size = 'm',\n color = 'text',\n style,\n testID,\n}: IconProps) {\n const { theme } = useTheme();\n const IconComponent = resolveExpoIconComponent(provider);\n const resolvedSize = typeof size === 'number' ? size : resolveToken(theme.spacing, size);\n const resolvedColor = resolveToken(theme.colors, color);\n\n return React.createElement(IconComponent, {\n color: resolvedColor,\n name,\n size: resolvedSize,\n style,\n testID,\n });\n}\n"]}
1
+ {"version":3,"file":"Icon.js","sourceRoot":"","sources":["../../../src/primitives/icon/Icon.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAkC,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAatE,MAAM,UAAU,IAAI,CAAC,EACnB,IAAI,EACJ,QAAQ,GAAG,UAAU,EACrB,IAAI,GAAG,GAAG,EACV,KAAK,GAAG,MAAM,EACd,KAAK,EACL,MAAM,GACI;IACV,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC7B,MAAM,aAAa,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,YAAY,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACzF,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAExD,OAAO,KAAK,CAAC,aAAa,CAAC,aAAa,EAAE;QACxC,KAAK,EAAE,aAAa;QACpB,IAAI;QACJ,IAAI,EAAE,YAAY;QAClB,KAAK;QACL,MAAM;KACP,CAAC,CAAC;AACL,CAAC","sourcesContent":["import React from 'react';\nimport { type StyleProp, type TextStyle } from 'react-native';\n\nimport { resolveToken } from '../../theme/resolveToken';\nimport { useTheme } from '../../theme/ThemeContext';\nimport type { SurfaceTheme } from '../../theme/types';\nimport { resolveExpoIconComponent } from './resolveExpoIconComponent';\n\nexport type IconProvider = string;\n\nexport interface IconProps {\n name: string;\n provider?: IconProvider;\n size?: keyof SurfaceTheme['spacing'] | number;\n color?: keyof SurfaceTheme['colors'] | string;\n style?: StyleProp<TextStyle>;\n testID?: string;\n}\n\nexport function Icon({\n name,\n provider = 'Ionicons',\n size = 'm',\n color = 'text',\n style,\n testID,\n}: IconProps) {\n const { theme } = useTheme();\n const IconComponent = resolveExpoIconComponent(provider);\n const resolvedSize = typeof size === 'number' ? size : resolveToken(theme.spacing, size);\n const resolvedColor = resolveToken(theme.colors, color);\n\n return React.createElement(IconComponent, {\n color: resolvedColor,\n name,\n size: resolvedSize,\n style,\n testID,\n });\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
- import type { AnkhTheme, ThemeConfig } from './types';
2
+ import type { SurfaceTheme, ThemeConfig } from './types';
3
3
  export declare const ThemeContext: React.Context<{
4
- theme: AnkhTheme;
4
+ theme: SurfaceTheme;
5
5
  mode: "light" | "dark";
6
6
  setThemeConfig: (config: Partial<ThemeConfig>) => void;
7
7
  setMode: (mode: "light" | "dark") => void;
@@ -13,7 +13,7 @@ export declare const ThemeProvider: ({ children, initialConfig, initialMode, }:
13
13
  initialMode?: "light" | "dark";
14
14
  }) => React.JSX.Element;
15
15
  export declare const useTheme: () => {
16
- theme: AnkhTheme;
16
+ theme: SurfaceTheme;
17
17
  mode: "light" | "dark";
18
18
  setThemeConfig: (config: Partial<ThemeConfig>) => void;
19
19
  setMode: (mode: "light" | "dark") => void;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeContext.d.ts","sourceRoot":"","sources":["../../src/theme/ThemeContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAQlE,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAItD,eAAO,MAAM,YAAY;WAChB,SAAS;UACV,OAAO,GAAG,MAAM;oBACN,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI;aAC7C,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI;mBAC1B,OAAO;EAWtB,CAAC;AAEH,eAAO,MAAM,aAAa,GAAI,2CAI3B;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC,sBA0CA,CAAC;AAEF,eAAO,MAAM,QAAQ;WArEZ,SAAS;UACV,OAAO,GAAG,MAAM;oBACN,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI;aAC7C,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI;mBAC1B,OAAO;CAmEvB,CAAC;AAEF,eAAO,MAAM,cAAc,iBAvEA,OAAO,CAAC,WAAW,CAAC,KAAK,IA0EnD,CAAC;AAEF,eAAO,MAAM,YAAY;;oBA3EP,OAAO,GAAG,MAAM,KAAK,IAAI;CA8E1C,CAAC"}
1
+ {"version":3,"file":"ThemeContext.d.ts","sourceRoot":"","sources":["../../src/theme/ThemeContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAC;AAQlE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAIzD,eAAO,MAAM,YAAY;WAChB,YAAY;UACb,OAAO,GAAG,MAAM;oBACN,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI;aAC7C,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI;mBAC1B,OAAO;EAWtB,CAAC;AAEH,eAAO,MAAM,aAAa,GAAI,2CAI3B;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC,sBA0CA,CAAC;AAEF,eAAO,MAAM,QAAQ;WArEZ,YAAY;UACb,OAAO,GAAG,MAAM;oBACN,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI;aAC7C,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI;mBAC1B,OAAO;CAmEvB,CAAC;AAEF,eAAO,MAAM,cAAc,iBAvEA,OAAO,CAAC,WAAW,CAAC,KAAK,IA0EnD,CAAC;AAEF,eAAO,MAAM,YAAY;;oBA3EP,OAAO,GAAG,MAAM,KAAK,IAAI;CA8E1C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeContext.js","sourceRoot":"","sources":["../../src/theme/ThemeContext.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { createContext, useContext, useMemo } from 'react';\n\nimport { useFontContext } from '../context/FontContext';\nimport { ResponsiveProvider } from '../core/responsive/ResponsiveProvider';\nimport { OverlayProvider } from '../internal/overlay/OverlayProvider';\nimport { isDeepEqual } from '../utils/deepEqual';\nimport { deepMerge } from '../utils/deepMerge';\nimport { createTheme } from './createTheme';\nimport type { AnkhTheme, ThemeConfig } from './types';\n\nconst defaultTheme = createTheme();\n\nexport const ThemeContext = createContext<{\n theme: AnkhTheme;\n mode: 'light' | 'dark';\n setThemeConfig: (config: Partial<ThemeConfig>) => void;\n setMode: (mode: 'light' | 'dark') => void;\n _hasProvider?: boolean;\n}>({\n theme: defaultTheme,\n mode: 'light',\n setThemeConfig: () => {\n /* fallback */\n },\n setMode: () => {\n /* fallback */\n },\n _hasProvider: false,\n});\n\nexport const ThemeProvider = ({\n children,\n initialConfig,\n initialMode = 'light',\n}: {\n children: React.ReactNode;\n initialConfig?: Partial<ThemeConfig>;\n initialMode?: 'light' | 'dark';\n}) => {\n const [config, setConfig] = React.useState<ThemeConfig>(() =>\n initialConfig ? deepMerge(defaultTheme.config, initialConfig) : defaultTheme.config,\n );\n const [mode, setMode] = React.useState<'light' | 'dark'>(initialMode);\n const { activeFontId } = useFontContext();\n\n // Keep state in sync with prop for real-time Studio updates\n React.useEffect(() => {\n if (initialConfig) {\n setConfig((prev) => {\n const merged = deepMerge(prev, initialConfig);\n if (isDeepEqual(prev, merged)) return prev;\n return merged;\n });\n }\n }, [initialConfig]);\n\n const theme = useMemo(\n () => createTheme(config, mode, activeFontId),\n [config, mode, activeFontId],\n );\n\n const value = useMemo(\n () => ({\n theme,\n mode,\n setThemeConfig: (newConfig: Partial<ThemeConfig>) =>\n setConfig((prev) => deepMerge(prev, newConfig)),\n setMode,\n _hasProvider: true,\n }),\n [theme, mode],\n );\n\n return (\n <ResponsiveProvider>\n <ThemeContext.Provider value={value}>\n <OverlayProvider>{children}</OverlayProvider>\n </ThemeContext.Provider>\n </ResponsiveProvider>\n );\n};\n\nexport const useTheme = () => {\n return useContext(ThemeContext);\n};\n\nexport const useThemeConfig = () => {\n const { setThemeConfig } = useTheme();\n return setThemeConfig;\n};\n\nexport const useThemeMode = () => {\n const { mode, setMode } = useTheme();\n return { mode, setMode };\n};\n"]}
1
+ {"version":3,"file":"ThemeContext.js","sourceRoot":"","sources":["../../src/theme/ThemeContext.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { createContext, useContext, useMemo } from 'react';\n\nimport { useFontContext } from '../context/FontContext';\nimport { ResponsiveProvider } from '../core/responsive/ResponsiveProvider';\nimport { OverlayProvider } from '../internal/overlay/OverlayProvider';\nimport { isDeepEqual } from '../utils/deepEqual';\nimport { deepMerge } from '../utils/deepMerge';\nimport { createTheme } from './createTheme';\nimport type { SurfaceTheme, ThemeConfig } from './types';\n\nconst defaultTheme = createTheme();\n\nexport const ThemeContext = createContext<{\n theme: SurfaceTheme;\n mode: 'light' | 'dark';\n setThemeConfig: (config: Partial<ThemeConfig>) => void;\n setMode: (mode: 'light' | 'dark') => void;\n _hasProvider?: boolean;\n}>({\n theme: defaultTheme,\n mode: 'light',\n setThemeConfig: () => {\n /* fallback */\n },\n setMode: () => {\n /* fallback */\n },\n _hasProvider: false,\n});\n\nexport const ThemeProvider = ({\n children,\n initialConfig,\n initialMode = 'light',\n}: {\n children: React.ReactNode;\n initialConfig?: Partial<ThemeConfig>;\n initialMode?: 'light' | 'dark';\n}) => {\n const [config, setConfig] = React.useState<ThemeConfig>(() =>\n initialConfig ? deepMerge(defaultTheme.config, initialConfig) : defaultTheme.config,\n );\n const [mode, setMode] = React.useState<'light' | 'dark'>(initialMode);\n const { activeFontId } = useFontContext();\n\n // Keep state in sync with prop for real-time Studio updates\n React.useEffect(() => {\n if (initialConfig) {\n setConfig((prev) => {\n const merged = deepMerge(prev, initialConfig);\n if (isDeepEqual(prev, merged)) return prev;\n return merged;\n });\n }\n }, [initialConfig]);\n\n const theme = useMemo(\n () => createTheme(config, mode, activeFontId),\n [config, mode, activeFontId],\n );\n\n const value = useMemo(\n () => ({\n theme,\n mode,\n setThemeConfig: (newConfig: Partial<ThemeConfig>) =>\n setConfig((prev) => deepMerge(prev, newConfig)),\n setMode,\n _hasProvider: true,\n }),\n [theme, mode],\n );\n\n return (\n <ResponsiveProvider>\n <ThemeContext.Provider value={value}>\n <OverlayProvider>{children}</OverlayProvider>\n </ThemeContext.Provider>\n </ResponsiveProvider>\n );\n};\n\nexport const useTheme = () => {\n return useContext(ThemeContext);\n};\n\nexport const useThemeConfig = () => {\n const { setThemeConfig } = useTheme();\n return setThemeConfig;\n};\n\nexport const useThemeMode = () => {\n const { mode, setMode } = useTheme();\n return { mode, setMode };\n};\n"]}
@@ -1,16 +1,15 @@
1
- import type { ColorScale, ThemeConfig, ThemeSemantics } from './types';
2
- interface OklchColor {
3
- mode: 'oklch';
4
- l: number;
5
- c: number;
6
- h?: number;
7
- }
8
- export declare const SCALE_STEPS: readonly [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
9
- export declare function generateColorScale(baseColor: OklchColor, isDark: boolean): ColorScale;
1
+ import type { GeneratedThemeModeColors, GeneratedThemeSwatches, HexColor, SemanticColorReferenceMap, SemanticColorToken } from '@ankhorage/color-theory';
2
+ import type { ThemeConfig } from '@ankhorage/contracts';
3
+ import type { ThemeSemantics } from './types';
4
+ /**
5
+ * Surface semantic resolver: maps color-theory SemanticColorToken references
6
+ * to hex values from the generated swatches.
7
+ */
8
+ export type SurfaceSemanticColors = Record<SemanticColorToken, HexColor>;
9
+ export declare function resolveSemanticColors(generated: GeneratedThemeModeColors, references: SemanticColorReferenceMap): SurfaceSemanticColors;
10
10
  export declare function generatePalette(config: ThemeConfig, mode?: 'light' | 'dark'): {
11
11
  colors: Record<string, string>;
12
- scales: Record<string, ColorScale>;
12
+ swatches: GeneratedThemeSwatches;
13
13
  semantics: ThemeSemantics;
14
14
  };
15
- export {};
16
15
  //# sourceMappingURL=colorEngine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"colorEngine.d.ts","sourceRoot":"","sources":["../../src/theme/colorEngine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,UAAU,EAMV,WAAW,EACX,cAAc,EACf,MAAM,SAAS,CAAC;AAIjB,UAAU,UAAU;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,eAAO,MAAM,WAAW,iEAAkE,CAAC;AAmH3F,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,UAAU,CAqBrF;AAwJD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,OAAO,GAAG,MAAgB,GAC/B;IACD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,SAAS,EAAE,cAAc,CAAC;CAC3B,CA0OA"}
1
+ {"version":3,"file":"colorEngine.d.ts","sourceRoot":"","sources":["../../src/theme/colorEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,wBAAwB,EACxB,sBAAsB,EACtB,QAAQ,EACR,yBAAyB,EACzB,kBAAkB,EACnB,MAAM,yBAAyB,CAAC;AAOjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAOV,cAAc,EACf,MAAM,SAAS,CAAC;AAOjB;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AAEzE,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,wBAAwB,EACnC,UAAU,EAAE,yBAAyB,GACpC,qBAAqB,CAUvB;AA8DD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,OAAO,GAAG,MAAgB,GAC/B;IACD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,SAAS,EAAE,cAAc,CAAC;CAC3B,CAsFA"}
@@ -1,418 +1,94 @@
1
- import { formatHex, modeOklch, oklch, useMode } from 'culori';
2
- useMode(modeOklch);
3
- export const SCALE_STEPS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
4
- /**
5
- * Deterministic Lightness Curves (OKLCH L)
6
- */
7
- const LIGHTNESS_CURVES = {
8
- light: [0.98, 0.95, 0.9, 0.82, 0.72, 0.62, 0.52, 0.42, 0.32, 0.22, 0.15],
9
- dark: [0.12, 0.16, 0.2, 0.26, 0.34, 0.62, 0.7, 0.78, 0.86, 0.92, 0.96],
10
- };
11
- /**
12
- * Lightness anchors per internal palette kind
13
- */
14
- const ROLE_PALETTE_LIGHTNESS_ANCHORS = {
15
- grayscale: 0.62,
16
- neutral: 0.62,
17
- pastel: 0.7,
18
- earth: 0.55,
19
- mineral: 0.58,
20
- muted: 0.6,
21
- jewel: 0.62,
22
- fluorescent: 0.65,
23
- obsidian: 0.12,
24
- vaporwave: 0.7,
25
- monochromeAccent: 0.62,
26
- };
27
- /**
28
- * Chroma curve per step to prevent "tinted whites" and "glow"
29
- * Peak energy at 500, falloff at extremes.
30
- */
31
- const CHROMA_BY_STEP = [0.1, 0.18, 0.3, 0.45, 0.7, 1.0, 0.92, 0.8, 0.6, 0.4, 0.25];
32
- /**
33
- * Deterministic Chroma Anchors (OKLCH C)
34
- */
35
- const ROLE_PALETTE_CHROMA_ANCHORS = {
36
- grayscale: 0,
37
- neutral: 0.02,
38
- pastel: 0.08,
39
- earth: 0.05,
40
- mineral: 0.07,
41
- muted: 0.04,
42
- jewel: 0.16,
43
- fluorescent: 0.26,
44
- obsidian: 0.01,
45
- vaporwave: 0.2,
46
- monochromeAccent: 0.02,
47
- };
48
- /**
49
- * Chroma Hierarchy Rule
50
- */
51
- const CHROMA_HIERARCHY = {
52
- primary: 1.0,
53
- secondary: 0.7,
54
- accent: 0.4,
55
- surface: 0.1,
56
- };
57
- /**
58
- * Semantic Step Mapping
59
- */
60
- const SEMANTIC_STEPS = {
61
- light: {
62
- bg: 0, // 50
63
- bgSubtle: 1, // 100
64
- surface: 1, // 100
65
- surfaceHover: 2, // 200
66
- surfaceActive: 3, // 300
67
- border: 3, // 300
68
- borderStrong: 4, // 400
69
- divider: 2, // 200
70
- text: 9, // 900
71
- textMuted: 7, // 700
72
- textSubtle: 6, // 600
73
- solid: 5, // 500
74
- softBg: 1, // 100
75
- softHover: 2, // 200
76
- softActive: 3, // 300
77
- outline: 4, // 400
78
- },
79
- dark: {
80
- bg: 0, // 50
81
- bgSubtle: 1, // 100
82
- surface: 1, // 100
83
- surfaceHover: 2, // 200
84
- surfaceActive: 3, // 300
85
- border: 3, // 300
86
- borderStrong: 4, // 400
87
- divider: 2, // 200
88
- text: 10, // 950
89
- textMuted: 8, // 800
90
- textSubtle: 7, // 700
91
- solid: 5, // 500
92
- softBg: 1, // 100
93
- softHover: 2, // 200
94
- softActive: 3, // 300
95
- outline: 4, // 400
96
- },
97
- };
98
- export function generateColorScale(baseColor, isDark) {
99
- const scale = {};
100
- const curve = isDark ? LIGHTNESS_CURVES.dark : LIGHTNESS_CURVES.light;
101
- // Dark-mode chroma rule: reduce chroma by 25%
102
- const chromaMultiplier = isDark ? 0.75 : 1.0;
103
- const targetChroma = baseColor.c * chromaMultiplier;
104
- SCALE_STEPS.forEach((step, index) => {
105
- const lightness = curve[index];
106
- const chromaByStep = CHROMA_BY_STEP[index];
107
- if (lightness === undefined || chromaByStep === undefined) {
108
- return;
1
+ import { generateColorSwatch, generateThemeModeColors, getReadableForeground, parseHexColorOrThrow, } from '@ankhorage/color-theory';
2
+ // Fixed hex values for semantic status colors (not theme-generated)
3
+ const DANGER_HEX = parseHexColorOrThrow('#ef4444');
4
+ const SUCCESS_HEX = parseHexColorOrThrow('#22c55e');
5
+ const WARNING_HEX = parseHexColorOrThrow('#f59e0b');
6
+ export function resolveSemanticColors(generated, references) {
7
+ return Object.fromEntries(Object.entries(references).map(([token, ref]) => {
8
+ const swatch = generated.swatches[ref.role];
9
+ if (!swatch) {
10
+ throw new Error(`Missing swatch for role '${ref.role}' (token: '${token}')`);
109
11
  }
110
- // Apply chroma falloff curve per step
111
- const stepChroma = targetChroma * chromaByStep;
112
- const stepColor = { ...baseColor, l: lightness, c: stepChroma };
113
- scale[step] = formatHex(stepColor);
114
- });
115
- return scale;
12
+ return [token, swatch[ref.step]];
13
+ }));
116
14
  }
117
- /**
118
- * Deterministic Harmony Hues
119
- */
120
- function getHarmonyHues(baseHue, mode) {
121
- const h = (baseHue + 360) % 360;
122
- switch (mode) {
123
- case 'monochromatic':
124
- return [h];
125
- case 'analogous':
126
- return [h, (h - 30 + 360) % 360, (h + 30) % 360];
127
- case 'complementary':
128
- return [h, (h + 180) % 360];
129
- case 'splitComplementary':
130
- return [h, (h + 150) % 360, (h + 210) % 360];
131
- case 'triadic':
132
- return [h, (h + 120) % 360, (h + 240) % 360];
133
- case 'tetradic':
134
- return [h, (h + 90) % 360, (h + 180) % 360, (h + 270) % 360];
135
- default:
136
- return [h];
15
+ function buildNeutralSemantics(neutralSwatch, isDark) {
16
+ if (isDark) {
17
+ return {
18
+ bg: neutralSwatch[950],
19
+ bgSubtle: neutralSwatch[900],
20
+ surface: neutralSwatch[900],
21
+ surfaceHover: neutralSwatch[800],
22
+ surfaceActive: neutralSwatch[700],
23
+ border: neutralSwatch[800],
24
+ borderStrong: neutralSwatch[600],
25
+ divider: neutralSwatch[800],
26
+ text: neutralSwatch[50],
27
+ textMuted: neutralSwatch[200],
28
+ textSubtle: neutralSwatch[300],
29
+ };
137
30
  }
31
+ return {
32
+ bg: neutralSwatch[50],
33
+ bgSubtle: neutralSwatch[100],
34
+ surface: neutralSwatch[100],
35
+ surfaceHover: neutralSwatch[200],
36
+ surfaceActive: neutralSwatch[300],
37
+ border: neutralSwatch[200],
38
+ borderStrong: neutralSwatch[300],
39
+ divider: neutralSwatch[200],
40
+ text: neutralSwatch[900],
41
+ textMuted: neutralSwatch[700],
42
+ textSubtle: neutralSwatch[600],
43
+ };
138
44
  }
139
- function getColorToneRolePalette(colorTone) {
140
- switch (colorTone) {
141
- case 'pastel':
142
- return {
143
- bg: 'pastel',
144
- surface: 'pastel',
145
- primary: 'jewel',
146
- secondary: 'jewel',
147
- accent: 'jewel',
148
- highlight: 'fluorescent',
149
- };
150
- case 'earth':
151
- return {
152
- bg: 'earth',
153
- surface: 'earth',
154
- primary: 'mineral',
155
- secondary: 'mineral',
156
- accent: 'jewel',
157
- highlight: 'jewel',
158
- };
159
- case 'mineral':
160
- return {
161
- bg: 'mineral',
162
- surface: 'mineral',
163
- primary: 'jewel',
164
- secondary: 'mineral',
165
- accent: 'jewel',
166
- highlight: 'fluorescent',
167
- };
168
- case 'muted':
169
- return {
170
- bg: 'muted',
171
- surface: 'muted',
172
- primary: 'jewel',
173
- secondary: 'muted',
174
- accent: 'jewel',
175
- highlight: 'fluorescent',
176
- };
177
- case 'jewel':
178
- return {
179
- bg: 'grayscale',
180
- surface: 'grayscale',
181
- primary: 'jewel',
182
- secondary: 'jewel',
183
- accent: 'jewel',
184
- highlight: 'fluorescent',
185
- };
186
- case 'fluorescent':
187
- return {
188
- bg: 'obsidian',
189
- surface: 'obsidian',
190
- primary: 'fluorescent',
191
- secondary: 'jewel',
192
- accent: 'fluorescent',
193
- highlight: 'fluorescent',
194
- };
195
- case 'obsidian':
196
- return {
197
- bg: 'obsidian',
198
- surface: 'obsidian',
199
- primary: 'fluorescent',
200
- secondary: 'jewel',
201
- accent: 'fluorescent',
202
- highlight: 'fluorescent',
203
- };
204
- case 'vaporwave':
205
- return {
206
- bg: 'pastel',
207
- surface: 'pastel',
208
- primary: 'fluorescent',
209
- secondary: 'jewel',
210
- accent: 'fluorescent',
211
- highlight: 'fluorescent',
212
- };
213
- case 'monochromeAccent':
214
- return {
215
- bg: 'grayscale',
216
- surface: 'grayscale',
217
- primary: 'jewel',
218
- secondary: 'grayscale',
219
- accent: 'jewel',
220
- highlight: 'fluorescent',
221
- };
222
- default: // neutral
223
- return {
224
- bg: 'grayscale',
225
- surface: 'grayscale',
226
- primary: 'jewel',
227
- secondary: 'pastel',
228
- accent: 'jewel',
229
- highlight: 'fluorescent',
230
- };
45
+ function buildRoleSemantics(swatch, isDark) {
46
+ const base = swatch[500];
47
+ const { foreground: onSolidText } = getReadableForeground(base);
48
+ if (isDark) {
49
+ return {
50
+ base,
51
+ hover: swatch[400],
52
+ strong: swatch[300],
53
+ softBg: swatch[900],
54
+ softHover: swatch[800],
55
+ softActive: swatch[700],
56
+ outline: swatch[500],
57
+ onSolidText,
58
+ };
231
59
  }
232
- }
233
- /**
234
- * Simple OKLCH L-based contrast check
235
- */
236
- function getBestContrast(solidHex, neutral50, neutral950) {
237
- const solid = oklch(solidHex);
238
- const n50 = oklch(neutral50);
239
- const n950 = oklch(neutral950);
240
- if (!solid || !n50 || !n950)
241
- return neutral50;
242
- const diff50 = Math.abs(solid.l - n50.l);
243
- const diff950 = Math.abs(solid.l - n950.l);
244
- return diff50 >= diff950 ? neutral50 : neutral950;
60
+ return {
61
+ base,
62
+ hover: swatch[600],
63
+ strong: swatch[700],
64
+ softBg: swatch[100],
65
+ softHover: swatch[200],
66
+ softActive: swatch[300],
67
+ outline: swatch[400],
68
+ onSolidText,
69
+ };
245
70
  }
246
71
  export function generatePalette(config, mode = 'light') {
247
72
  const modeConfig = mode === 'dark' ? config.dark : config.light;
248
- const { primaryColor, harmony, colorTone } = modeConfig;
249
- let base = oklch(primaryColor);
250
- if (!base) {
251
- console.warn(`[colorEngine] Invalid primary color: "${primaryColor}". Falling back to default blue.`);
252
- base = oklch('#3B82F6');
253
- }
254
- const baseHue = base?.h ?? 0;
255
- const hues = getHarmonyHues(baseHue, harmony);
256
- const rolePalette = getColorToneRolePalette(colorTone);
257
- // 1. Resolve Chromas
258
- const getC = (t) => ROLE_PALETTE_CHROMA_ANCHORS[t];
259
- const getHue = (index, fallback) => hues[index] ?? fallback;
260
- const primaryChroma = getC(rolePalette.primary);
261
- const secondaryChroma = getC(rolePalette.secondary) * CHROMA_HIERARCHY.secondary;
262
- const tertiaryChroma = getC(rolePalette.accent) * CHROMA_HIERARCHY.accent;
263
- const highlightChroma = getC(rolePalette.highlight);
264
- const surfaceChroma = Math.min(0.02, getC(rolePalette.bg) * CHROMA_HIERARCHY.surface);
265
- // 2. Stable Role Assignment
266
- let pHue = getHue(0, baseHue);
267
- let sHue = getHue(0, baseHue);
268
- let aHue = getHue(0, baseHue);
269
- let hHue = harmony === 'tetradic' ? getHue(3, getHue(0, baseHue)) : (getHue(0, baseHue) + 60) % 360; // Yellow-ish offset if no tetradic
270
- switch (harmony) {
271
- case 'complementary':
272
- pHue = getHue(0, pHue);
273
- aHue = getHue(1, pHue);
274
- sHue = (getHue(0, pHue) + 30) % 360;
275
- break;
276
- case 'splitComplementary':
277
- case 'triadic':
278
- pHue = getHue(0, pHue);
279
- sHue = getHue(1, pHue);
280
- aHue = getHue(2, sHue);
281
- break;
282
- case 'tetradic':
283
- pHue = getHue(0, pHue);
284
- sHue = getHue(1, pHue);
285
- aHue = getHue(2, sHue);
286
- hHue = getHue(3, aHue);
287
- break;
288
- case 'analogous':
289
- pHue = getHue(1, pHue);
290
- sHue = getHue(0, pHue);
291
- aHue = getHue(2, sHue);
292
- break;
293
- }
294
- // 3. Build Bases with tuned lightness
295
- const getL = (t) => ROLE_PALETTE_LIGHTNESS_ANCHORS[t];
296
- const primaryBase = {
297
- mode: 'oklch',
298
- l: getL(rolePalette.primary),
299
- c: primaryChroma,
300
- h: pHue,
301
- };
302
- const secondaryBase = {
303
- mode: 'oklch',
304
- l: getL(rolePalette.secondary),
305
- c: secondaryChroma,
306
- h: sHue,
307
- };
308
- const accentBase = {
309
- mode: 'oklch',
310
- l: getL(rolePalette.accent),
311
- c: tertiaryChroma,
312
- h: aHue,
313
- };
314
- const highlightBase = {
315
- mode: 'oklch',
316
- l: getL(rolePalette.highlight),
317
- c: highlightChroma,
318
- h: hHue,
319
- };
320
- const dangerBase = {
321
- mode: 'oklch',
322
- l: 0.6,
323
- c: 0.2,
324
- h: 25,
325
- };
326
- const successBase = {
327
- mode: 'oklch',
328
- l: 0.6,
329
- c: 0.2,
330
- h: 145,
331
- };
332
- const warningBase = {
333
- mode: 'oklch',
334
- l: 0.75,
335
- c: 0.15,
336
- h: 85,
337
- };
338
- const neutralBase = surfaceChroma === 0
339
- ? { mode: 'oklch', l: 0.62, c: 0 }
340
- : { mode: 'oklch', l: 0.62, c: surfaceChroma, h: 260 };
341
- // 4. Generate Scales
342
73
  const isDark = mode === 'dark';
343
- const scales = {
344
- primary: generateColorScale(primaryBase, isDark),
345
- secondary: generateColorScale(secondaryBase, isDark),
346
- accent: generateColorScale(accentBase, isDark),
347
- highlight: generateColorScale(highlightBase, isDark),
348
- neutral: generateColorScale(neutralBase, isDark),
349
- danger: generateColorScale(dangerBase, isDark),
350
- success: generateColorScale(successBase, isDark),
351
- warning: generateColorScale(warningBase, isDark),
352
- };
353
- // 5. Mappings
354
- const steps = isDark ? SEMANTIC_STEPS.dark : SEMANTIC_STEPS.light;
355
- const getStep = (s, idx) => {
356
- const key = SCALE_STEPS[idx] ?? SCALE_STEPS[SCALE_STEPS.length - 1] ?? 950;
357
- return s[key];
358
- };
359
- const getNeutralMapping = (scale) => ({
360
- bg: getStep(scale, steps.bg),
361
- bgSubtle: getStep(scale, steps.bgSubtle),
362
- surface: getStep(scale, steps.surface),
363
- surfaceHover: getStep(scale, steps.surfaceHover),
364
- surfaceActive: getStep(scale, steps.surfaceActive),
365
- border: getStep(scale, steps.border),
366
- borderStrong: getStep(scale, steps.borderStrong),
367
- divider: getStep(scale, steps.divider),
368
- text: getStep(scale, steps.text),
369
- textMuted: getStep(scale, steps.textMuted),
370
- textSubtle: getStep(scale, steps.textSubtle),
371
- });
372
- const getColorMapping = (scale, neutralScale) => {
373
- const solid = getStep(scale, steps.solid);
374
- const softChromaLimit = 0.08;
375
- // Chroma capping for soft tokens
376
- const getSoftStep = (idx) => {
377
- const hex = getStep(scale, idx);
378
- const color = oklch(hex);
379
- if (color && color.c > softChromaLimit) {
380
- return formatHex({ ...color, c: softChromaLimit });
381
- }
382
- return hex;
383
- };
384
- return {
385
- base: solid,
386
- hover: getStep(scale, 6), // 600
387
- strong: getStep(scale, 7), // 700
388
- softBg: getSoftStep(steps.softBg),
389
- softHover: getSoftStep(steps.softHover),
390
- softActive: getSoftStep(steps.softActive),
391
- outline: getStep(scale, steps.outline),
392
- onSolidText: getBestContrast(solid, getStep(neutralScale, 0), getStep(neutralScale, 10)),
393
- };
394
- };
395
- const neutral = getNeutralMapping(scales.neutral);
396
- const brand = getColorMapping(scales.primary, scales.neutral);
397
- const neutralAction = getColorMapping(scales.neutral, scales.neutral);
398
- const danger = getColorMapping(scales.danger, scales.neutral);
399
- const success = getColorMapping(scales.success, scales.neutral);
400
- const warning = getColorMapping(scales.warning, scales.neutral);
401
- const colors = {
402
- primary: getStep(scales.primary, steps.solid),
403
- secondary: getStep(scales.secondary, steps.solid),
404
- accent: getStep(scales.accent, steps.solid),
405
- highlight: getStep(scales.highlight, steps.solid),
406
- background: neutral.bg,
407
- surface: neutral.surface,
408
- text: neutral.text,
409
- textSecondary: neutral.textMuted,
410
- border: neutral.border,
411
- error: getStep(scales.danger, steps.solid),
412
- success: getStep(scales.success, steps.solid),
413
- warning: getStep(scales.warning, steps.solid),
414
- };
415
- const surface = {
74
+ // Throws deterministically on invalid primary color
75
+ parseHexColorOrThrow(modeConfig.primaryColor);
76
+ const generated = generateThemeModeColors(modeConfig);
77
+ const { swatches } = generated;
78
+ const neutralSwatch = swatches.neutral;
79
+ const neutral = buildNeutralSemantics(neutralSwatch, isDark);
80
+ const brand = buildRoleSemantics(swatches.primary, isDark);
81
+ // Fallback to primary swatch for missing ordinal roles
82
+ const secondarySwatch = swatches.secondary ?? swatches.primary;
83
+ const tertiarySwatch = swatches.tertiary ?? swatches.primary;
84
+ const quaternarySwatch = swatches.quaternary ?? swatches.primary;
85
+ const dangerSwatch = generateColorSwatch(DANGER_HEX).swatch;
86
+ const successSwatch = generateColorSwatch(SUCCESS_HEX).swatch;
87
+ const warningSwatch = generateColorSwatch(WARNING_HEX).swatch;
88
+ const danger = buildRoleSemantics(dangerSwatch, isDark);
89
+ const success = buildRoleSemantics(successSwatch, isDark);
90
+ const warning = buildRoleSemantics(warningSwatch, isDark);
91
+ const surfaceSemantics = {
416
92
  default: neutral.surface,
417
93
  subtle: neutral.bgSubtle,
418
94
  raised: neutral.surface,
@@ -430,22 +106,36 @@ export function generatePalette(config, mode = 'light') {
430
106
  };
431
107
  const action = {
432
108
  primary: brand,
433
- neutral: neutralAction,
109
+ neutral: buildRoleSemantics(neutralSwatch, isDark),
434
110
  danger,
435
111
  };
112
+ const colors = {
113
+ primary: brand.base,
114
+ secondary: secondarySwatch[500],
115
+ accent: tertiarySwatch[500],
116
+ highlight: quaternarySwatch[500],
117
+ background: neutral.bg,
118
+ surface: neutral.surface,
119
+ text: neutral.text,
120
+ textSecondary: neutral.textMuted,
121
+ border: neutral.border,
122
+ error: danger.base,
123
+ success: success.base,
124
+ warning: warning.base,
125
+ };
436
126
  return {
437
127
  colors,
438
- scales,
128
+ swatches,
439
129
  semantics: {
440
130
  neutral,
441
131
  brand,
442
- secondary: getColorMapping(scales.secondary, scales.neutral),
443
- accent: getColorMapping(scales.accent, scales.neutral),
444
- highlight: getColorMapping(scales.highlight, scales.neutral),
132
+ secondary: buildRoleSemantics(secondarySwatch, isDark),
133
+ accent: buildRoleSemantics(tertiarySwatch, isDark),
134
+ highlight: buildRoleSemantics(quaternarySwatch, isDark),
445
135
  danger,
446
136
  success,
447
137
  warning,
448
- surface,
138
+ surface: surfaceSemantics,
449
139
  content,
450
140
  border,
451
141
  action,