@ankhorage/surface 0.1.4 → 0.1.6

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 (292) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +23 -184
  3. package/dist/components/badge/Badge.js.map +1 -1
  4. package/dist/components/badge/index.js.map +1 -1
  5. package/dist/components/badge/types.js.map +1 -1
  6. package/dist/components/button/Button.js.map +1 -1
  7. package/dist/components/button/index.js.map +1 -1
  8. package/dist/components/button/types.js.map +1 -1
  9. package/dist/components/card/Card.js.map +1 -1
  10. package/dist/components/card/index.js.map +1 -1
  11. package/dist/components/card/types.js.map +1 -1
  12. package/dist/components/checkbox/Checkbox.js.map +1 -1
  13. package/dist/components/checkbox/index.js.map +1 -1
  14. package/dist/components/checkbox/types.js.map +1 -1
  15. package/dist/components/drawer/Drawer.js.map +1 -1
  16. package/dist/components/drawer/index.js.map +1 -1
  17. package/dist/components/drawer/types.js.map +1 -1
  18. package/dist/components/field/Field.js.map +1 -1
  19. package/dist/components/field/index.js.map +1 -1
  20. package/dist/components/field/types.js.map +1 -1
  21. package/dist/components/helper-text/HelperText.js.map +1 -1
  22. package/dist/components/helper-text/index.js.map +1 -1
  23. package/dist/components/helper-text/types.js.map +1 -1
  24. package/dist/components/icon-button/IconButton.js.map +1 -1
  25. package/dist/components/icon-button/index.js.map +1 -1
  26. package/dist/components/icon-button/types.js.map +1 -1
  27. package/dist/components/label/Label.js.map +1 -1
  28. package/dist/components/label/index.js.map +1 -1
  29. package/dist/components/label/types.js.map +1 -1
  30. package/dist/components/list-item/ListItem.js.map +1 -1
  31. package/dist/components/list-item/index.js.map +1 -1
  32. package/dist/components/list-item/types.js.map +1 -1
  33. package/dist/components/menu/Menu.js.map +1 -1
  34. package/dist/components/menu/index.js.map +1 -1
  35. package/dist/components/menu/navigation.js.map +1 -1
  36. package/dist/components/menu/types.js.map +1 -1
  37. package/dist/components/modal/Modal.js.map +1 -1
  38. package/dist/components/modal/index.js.map +1 -1
  39. package/dist/components/modal/types.js.map +1 -1
  40. package/dist/components/radio/Radio.js.map +1 -1
  41. package/dist/components/radio/index.js.map +1 -1
  42. package/dist/components/radio/types.js.map +1 -1
  43. package/dist/components/switch/Switch.js.map +1 -1
  44. package/dist/components/switch/index.js.map +1 -1
  45. package/dist/components/switch/types.js.map +1 -1
  46. package/dist/components/tabs/Tab.js.map +1 -1
  47. package/dist/components/tabs/TabList.js.map +1 -1
  48. package/dist/components/tabs/TabPanel.js.map +1 -1
  49. package/dist/components/tabs/Tabs.js.map +1 -1
  50. package/dist/components/tabs/a11y.js.map +1 -1
  51. package/dist/components/tabs/context.js.map +1 -1
  52. package/dist/components/tabs/index.js.map +1 -1
  53. package/dist/components/tabs/navigation.js.map +1 -1
  54. package/dist/components/tabs/types.js.map +1 -1
  55. package/dist/components/text-input/TextInput.js.map +1 -1
  56. package/dist/components/text-input/index.js.map +1 -1
  57. package/dist/components/text-input/types.js.map +1 -1
  58. package/dist/components/textarea/Textarea.js.map +1 -1
  59. package/dist/components/textarea/index.js.map +1 -1
  60. package/dist/components/textarea/types.js.map +1 -1
  61. package/dist/components/toast/Toast.js.map +1 -1
  62. package/dist/components/toast/ToastProvider.js.map +1 -1
  63. package/dist/components/toast/index.js.map +1 -1
  64. package/dist/components/toast/types.js.map +1 -1
  65. package/dist/components/tooltip/Tooltip.js.map +1 -1
  66. package/dist/components/tooltip/index.js.map +1 -1
  67. package/dist/components/tooltip/types.js.map +1 -1
  68. package/dist/context/FontContext.js.map +1 -1
  69. package/dist/context/TranslationContext.js.map +1 -1
  70. package/dist/core/responsive/ResponsiveProvider.js.map +1 -1
  71. package/dist/core/responsive/breakpoints.js.map +1 -1
  72. package/dist/core/responsive/getBreakpointFromWidth.js.map +1 -1
  73. package/dist/core/responsive/index.js.map +1 -1
  74. package/dist/core/responsive/resolve.js.map +1 -1
  75. package/dist/core/responsive/types.js.map +1 -1
  76. package/dist/core/responsive/useBreakpoint.js.map +1 -1
  77. package/dist/examples/DocsExamples.js.map +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/internal/focus/FocusScope.js.map +1 -1
  80. package/dist/internal/focus/useFocusManager.js.map +1 -1
  81. package/dist/internal/overlay/OverlayProvider.js.map +1 -1
  82. package/dist/internal/overlay/Portal.js.map +1 -1
  83. package/dist/internal/overlay/useOverlayStack.js.map +1 -1
  84. package/dist/internal/resolvers/index.js.map +1 -1
  85. package/dist/internal/resolvers/resolveControlSize.js.map +1 -1
  86. package/dist/internal/resolvers/resolveFieldPresentation.js.map +1 -1
  87. package/dist/internal/resolvers/resolveFieldState.js.map +1 -1
  88. package/dist/internal/resolvers/resolveFocusRingStyles.js.map +1 -1
  89. package/dist/internal/resolvers/resolveIconSize.js.map +1 -1
  90. package/dist/internal/resolvers/resolveIndicatorSize.js.map +1 -1
  91. package/dist/internal/resolvers/resolveInteractiveColors.js.map +1 -1
  92. package/dist/internal/resolvers/resolveInteractiveState.js.map +1 -1
  93. package/dist/internal/resolvers/resolveOverlayAnimation.js.map +1 -1
  94. package/dist/internal/resolvers/resolveOverlayZIndex.js.map +1 -1
  95. package/dist/internal/resolvers/resolveSelectionControlBehavior.js.map +1 -1
  96. package/dist/internal/resolvers/resolveSelectionControlColors.js.map +1 -1
  97. package/dist/internal/resolvers/resolveTextColor.js.map +1 -1
  98. package/dist/internal/resolvers/resolveTextStyles.js.map +1 -1
  99. package/dist/internal/resolvers/resolveTone.js.map +1 -1
  100. package/dist/internal/useControllableState.js.map +1 -1
  101. package/dist/layout/Box.js.map +1 -1
  102. package/dist/layout/Center.js.map +1 -1
  103. package/dist/layout/Container.js.map +1 -1
  104. package/dist/layout/Divider.js.map +1 -1
  105. package/dist/layout/Grid.js.map +1 -1
  106. package/dist/layout/Inline.js.map +1 -1
  107. package/dist/layout/Show.js.map +1 -1
  108. package/dist/layout/Spacer.js.map +1 -1
  109. package/dist/layout/Stack.js.map +1 -1
  110. package/dist/layout/Surface.js.map +1 -1
  111. package/dist/layout/Template.js.map +1 -1
  112. package/dist/layout/helpers.js.map +1 -1
  113. package/dist/layout/index.js.map +1 -1
  114. package/dist/primitives/button-base/ButtonBase.js.map +1 -1
  115. package/dist/primitives/button-base/index.js.map +1 -1
  116. package/dist/primitives/button-base/types.js.map +1 -1
  117. package/dist/primitives/heading/Heading.js.map +1 -1
  118. package/dist/primitives/heading/index.js.map +1 -1
  119. package/dist/primitives/heading/resolveHeadingStyle.js.map +1 -1
  120. package/dist/primitives/heading/types.js.map +1 -1
  121. package/dist/primitives/icon/Icon.js.map +1 -1
  122. package/dist/primitives/icon/index.js.map +1 -1
  123. package/dist/primitives/icon/resolveExpoIconComponent.js.map +1 -1
  124. package/dist/primitives/text/Text.js.map +1 -1
  125. package/dist/primitives/text/index.js.map +1 -1
  126. package/dist/primitives/text/types.js.map +1 -1
  127. package/dist/theme/ThemeContext.js.map +1 -1
  128. package/dist/theme/colorEngine.js.map +1 -1
  129. package/dist/theme/createTheme.js.map +1 -1
  130. package/dist/theme/index.js.map +1 -1
  131. package/dist/theme/resolveToken.js.map +1 -1
  132. package/dist/theme/types.js.map +1 -1
  133. package/dist/utils/deepEqual.js.map +1 -1
  134. package/dist/utils/deepMerge.js.map +1 -1
  135. package/package.json +4 -1
  136. package/src/components/badge/Badge.tsx +47 -0
  137. package/src/components/badge/index.ts +2 -0
  138. package/src/components/badge/types.ts +13 -0
  139. package/src/components/button/Button.tsx +104 -0
  140. package/src/components/button/index.ts +2 -0
  141. package/src/components/button/types.ts +26 -0
  142. package/src/components/card/Card.tsx +81 -0
  143. package/src/components/card/index.ts +2 -0
  144. package/src/components/card/types.ts +11 -0
  145. package/src/components/checkbox/Checkbox.tsx +111 -0
  146. package/src/components/checkbox/index.ts +2 -0
  147. package/src/components/checkbox/types.ts +19 -0
  148. package/src/components/drawer/Drawer.tsx +92 -0
  149. package/src/components/drawer/index.ts +2 -0
  150. package/src/components/drawer/types.ts +10 -0
  151. package/src/components/field/Field.tsx +43 -0
  152. package/src/components/field/index.ts +2 -0
  153. package/src/components/field/types.ts +13 -0
  154. package/src/components/helper-text/HelperText.tsx +12 -0
  155. package/src/components/helper-text/index.ts +2 -0
  156. package/src/components/helper-text/types.ts +9 -0
  157. package/src/components/icon-button/IconButton.tsx +60 -0
  158. package/src/components/icon-button/index.ts +2 -0
  159. package/src/components/icon-button/types.ts +19 -0
  160. package/src/components/label/Label.tsx +17 -0
  161. package/src/components/label/index.ts +2 -0
  162. package/src/components/label/types.ts +10 -0
  163. package/src/components/list-item/ListItem.tsx +72 -0
  164. package/src/components/list-item/index.ts +2 -0
  165. package/src/components/list-item/types.ts +11 -0
  166. package/src/components/menu/Menu.tsx +180 -0
  167. package/src/components/menu/index.ts +2 -0
  168. package/src/components/menu/navigation.test.ts +21 -0
  169. package/src/components/menu/navigation.ts +34 -0
  170. package/src/components/menu/types.ts +16 -0
  171. package/src/components/modal/Modal.tsx +87 -0
  172. package/src/components/modal/index.ts +2 -0
  173. package/src/components/modal/types.ts +9 -0
  174. package/src/components/radio/Radio.tsx +116 -0
  175. package/src/components/radio/index.ts +2 -0
  176. package/src/components/radio/types.ts +19 -0
  177. package/src/components/switch/Switch.tsx +116 -0
  178. package/src/components/switch/index.ts +2 -0
  179. package/src/components/switch/types.ts +19 -0
  180. package/src/components/tabs/Tab.tsx +82 -0
  181. package/src/components/tabs/TabList.tsx +51 -0
  182. package/src/components/tabs/TabPanel.tsx +29 -0
  183. package/src/components/tabs/Tabs.tsx +67 -0
  184. package/src/components/tabs/a11y.test.ts +15 -0
  185. package/src/components/tabs/a11y.ts +15 -0
  186. package/src/components/tabs/context.tsx +31 -0
  187. package/src/components/tabs/index.ts +5 -0
  188. package/src/components/tabs/navigation.test.ts +21 -0
  189. package/src/components/tabs/navigation.ts +32 -0
  190. package/src/components/tabs/types.ts +27 -0
  191. package/src/components/text-input/TextInput.tsx +116 -0
  192. package/src/components/text-input/index.ts +2 -0
  193. package/src/components/text-input/types.ts +32 -0
  194. package/src/components/textarea/Textarea.tsx +15 -0
  195. package/src/components/textarea/index.ts +2 -0
  196. package/src/components/textarea/types.ts +5 -0
  197. package/src/components/toast/Toast.tsx +54 -0
  198. package/src/components/toast/ToastProvider.tsx +114 -0
  199. package/src/components/toast/index.ts +3 -0
  200. package/src/components/toast/types.ts +16 -0
  201. package/src/components/tooltip/Tooltip.tsx +109 -0
  202. package/src/components/tooltip/index.ts +2 -0
  203. package/src/components/tooltip/types.ts +9 -0
  204. package/src/context/FontContext.tsx +59 -0
  205. package/src/context/TranslationContext.tsx +54 -0
  206. package/src/core/responsive/ResponsiveProvider.tsx +31 -0
  207. package/src/core/responsive/breakpoints.ts +9 -0
  208. package/src/core/responsive/getBreakpointFromWidth.test.ts +15 -0
  209. package/src/core/responsive/getBreakpointFromWidth.ts +10 -0
  210. package/src/core/responsive/index.ts +6 -0
  211. package/src/core/responsive/resolve.test.ts +25 -0
  212. package/src/core/responsive/resolve.ts +24 -0
  213. package/src/core/responsive/types.ts +10 -0
  214. package/src/core/responsive/useBreakpoint.ts +9 -0
  215. package/src/examples/DocsExamples.tsx +116 -0
  216. package/src/index.test.ts +64 -0
  217. package/src/index.ts +55 -0
  218. package/src/internal/focus/FocusScope.tsx +66 -0
  219. package/src/internal/focus/useFocusManager.test.ts +44 -0
  220. package/src/internal/focus/useFocusManager.ts +142 -0
  221. package/src/internal/overlay/OverlayProvider.tsx +74 -0
  222. package/src/internal/overlay/Portal.tsx +38 -0
  223. package/src/internal/overlay/useOverlayStack.test.ts +31 -0
  224. package/src/internal/overlay/useOverlayStack.ts +61 -0
  225. package/src/internal/resolvers/index.ts +15 -0
  226. package/src/internal/resolvers/resolveControlSize.test.ts +25 -0
  227. package/src/internal/resolvers/resolveControlSize.ts +45 -0
  228. package/src/internal/resolvers/resolveFieldPresentation.test.ts +31 -0
  229. package/src/internal/resolvers/resolveFieldPresentation.ts +30 -0
  230. package/src/internal/resolvers/resolveFieldState.test.ts +22 -0
  231. package/src/internal/resolvers/resolveFieldState.ts +36 -0
  232. package/src/internal/resolvers/resolveFocusRingStyles.ts +14 -0
  233. package/src/internal/resolvers/resolveIconSize.ts +6 -0
  234. package/src/internal/resolvers/resolveIndicatorSize.test.ts +19 -0
  235. package/src/internal/resolvers/resolveIndicatorSize.ts +47 -0
  236. package/src/internal/resolvers/resolveInteractiveColors.test.ts +57 -0
  237. package/src/internal/resolvers/resolveInteractiveColors.ts +134 -0
  238. package/src/internal/resolvers/resolveInteractiveState.test.ts +14 -0
  239. package/src/internal/resolvers/resolveInteractiveState.ts +15 -0
  240. package/src/internal/resolvers/resolveOverlayAnimation.test.ts +15 -0
  241. package/src/internal/resolvers/resolveOverlayAnimation.ts +24 -0
  242. package/src/internal/resolvers/resolveOverlayZIndex.test.ts +15 -0
  243. package/src/internal/resolvers/resolveOverlayZIndex.ts +13 -0
  244. package/src/internal/resolvers/resolveSelectionControlBehavior.test.ts +52 -0
  245. package/src/internal/resolvers/resolveSelectionControlBehavior.ts +23 -0
  246. package/src/internal/resolvers/resolveSelectionControlColors.test.ts +44 -0
  247. package/src/internal/resolvers/resolveSelectionControlColors.ts +81 -0
  248. package/src/internal/resolvers/resolveTextColor.test.ts +23 -0
  249. package/src/internal/resolvers/resolveTextColor.ts +40 -0
  250. package/src/internal/resolvers/resolveTextStyles.test.ts +27 -0
  251. package/src/internal/resolvers/resolveTextStyles.ts +95 -0
  252. package/src/internal/resolvers/resolveTone.ts +19 -0
  253. package/src/internal/useControllableState.ts +28 -0
  254. package/src/layout/Box.tsx +79 -0
  255. package/src/layout/Center.tsx +22 -0
  256. package/src/layout/Container.tsx +43 -0
  257. package/src/layout/Divider.tsx +26 -0
  258. package/src/layout/Grid.tsx +83 -0
  259. package/src/layout/Inline.tsx +9 -0
  260. package/src/layout/Show.tsx +15 -0
  261. package/src/layout/Spacer.tsx +22 -0
  262. package/src/layout/Stack.tsx +67 -0
  263. package/src/layout/Surface.tsx +70 -0
  264. package/src/layout/Template.tsx +85 -0
  265. package/src/layout/helpers.test.ts +71 -0
  266. package/src/layout/helpers.ts +208 -0
  267. package/src/layout/index.ts +22 -0
  268. package/src/primitives/button-base/ButtonBase.tsx +81 -0
  269. package/src/primitives/button-base/index.ts +2 -0
  270. package/src/primitives/button-base/types.ts +16 -0
  271. package/src/primitives/heading/Heading.tsx +60 -0
  272. package/src/primitives/heading/index.ts +2 -0
  273. package/src/primitives/heading/resolveHeadingStyle.test.ts +31 -0
  274. package/src/primitives/heading/resolveHeadingStyle.ts +17 -0
  275. package/src/primitives/heading/types.ts +13 -0
  276. package/src/primitives/icon/Icon.tsx +40 -0
  277. package/src/primitives/icon/index.ts +2 -0
  278. package/src/primitives/icon/resolveExpoIconComponent.test.ts +29 -0
  279. package/src/primitives/icon/resolveExpoIconComponent.ts +20 -0
  280. package/src/primitives/text/Text.tsx +66 -0
  281. package/src/primitives/text/index.ts +2 -0
  282. package/src/primitives/text/types.ts +18 -0
  283. package/src/theme/ThemeContext.tsx +95 -0
  284. package/src/theme/colorEngine.test.ts +114 -0
  285. package/src/theme/colorEngine.ts +480 -0
  286. package/src/theme/createTheme.ts +121 -0
  287. package/src/theme/index.ts +5 -0
  288. package/src/theme/resolveToken.ts +32 -0
  289. package/src/theme/types.ts +188 -0
  290. package/src/utils/deepEqual.ts +34 -0
  291. package/src/utils/deepMerge.test.ts +117 -0
  292. package/src/utils/deepMerge.ts +29 -0
@@ -0,0 +1,121 @@
1
+ import { generatePalette } from './colorEngine';
2
+ import type { AnkhTheme, FontWeight, ThemeConfig, ThemeTokens } from './types';
3
+
4
+ export const DEFAULT_TOKENS: Omit<ThemeTokens, 'colors' | 'scales' | 'semantics'> = {
5
+ spacing: {
6
+ none: 0,
7
+ xs: 4,
8
+ s: 8,
9
+ m: 16,
10
+ l: 24,
11
+ xl: 32,
12
+ xxl: 48,
13
+ },
14
+ radii: {
15
+ none: 0,
16
+ s: 4,
17
+ m: 8,
18
+ l: 16,
19
+ full: 9999,
20
+ },
21
+ typography: {
22
+ headings: {
23
+ 1: { size: 32, lineHeight: 40, weight: 'bold' },
24
+ 2: { size: 24, lineHeight: 32, weight: 'bold' },
25
+ 3: { size: 20, lineHeight: 28, weight: 'bold' },
26
+ 4: { size: 18, lineHeight: 24, weight: 'semiBold' },
27
+ 5: { size: 16, lineHeight: 22, weight: 'semiBold' },
28
+ 6: { size: 14, lineHeight: 20, weight: 'semiBold' },
29
+ },
30
+ sizes: {
31
+ xs: 12,
32
+ s: 14,
33
+ m: 16,
34
+ l: 18,
35
+ xl: 20,
36
+ xxl: 24,
37
+ '3xl': 30,
38
+ h1: 32,
39
+ h2: 24,
40
+ h3: 20,
41
+ h4: 18,
42
+ h5: 16,
43
+ h6: 14,
44
+ },
45
+ weights: {
46
+ thin: '100',
47
+ extraLight: '200',
48
+ light: '300',
49
+ regular: '400',
50
+ medium: '500',
51
+ semiBold: '600',
52
+ bold: '700',
53
+ extraBold: '800',
54
+ black: '900',
55
+ },
56
+ fonts: {
57
+ normal: {} as Record<FontWeight, string | undefined>,
58
+ italic: {} as Record<FontWeight, string | undefined>,
59
+ },
60
+ },
61
+ shadows: {
62
+ soft: 2,
63
+ medium: 4,
64
+ hard: 8,
65
+ },
66
+ };
67
+
68
+ export const DEFAULT_CONFIG: ThemeConfig = {
69
+ id: 'default',
70
+ name: 'Default',
71
+ light: {
72
+ primaryColor: '#3B82F6',
73
+ harmony: 'monochromatic',
74
+ systemTone: 'neutral',
75
+ },
76
+ dark: {
77
+ primaryColor: '#3B82F6',
78
+ harmony: 'monochromatic',
79
+ systemTone: 'neutral',
80
+ },
81
+ };
82
+
83
+ /**
84
+ * Normalizes font family name to PascalCase for consistent key generation
85
+ * matches GoogleFontsPlugin logic.
86
+ */
87
+ function toPascalCase(str: string) {
88
+ return str
89
+ .split(/[- ]+/)
90
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
91
+ .join('');
92
+ }
93
+
94
+ export function createTheme(
95
+ config: ThemeConfig = DEFAULT_CONFIG,
96
+ mode: 'light' | 'dark' = 'light',
97
+ activeFontId?: string | null,
98
+ ): AnkhTheme {
99
+ const { colors, scales, semantics } = generatePalette(config, mode);
100
+
101
+ const theme = {
102
+ ...DEFAULT_TOKENS,
103
+ colors: colors as AnkhTheme['colors'],
104
+ scales,
105
+ semantics,
106
+ config,
107
+ };
108
+
109
+ if (activeFontId) {
110
+ const familyName = toPascalCase(activeFontId);
111
+ const weights: FontWeight[] = ['100', '200', '300', '400', '500', '600', '700', '800', '900'];
112
+
113
+ weights.forEach((w) => {
114
+ // Convention from GoogleFonts plugin: Family_WeightRegular
115
+ theme.typography.fonts.normal[w] = `${familyName}_${w}Regular`;
116
+ theme.typography.fonts.italic[w] = `${familyName}_${w}Italic`;
117
+ });
118
+ }
119
+
120
+ return theme;
121
+ }
@@ -0,0 +1,5 @@
1
+ export * from './colorEngine';
2
+ export * from './createTheme';
3
+ export * from './resolveToken';
4
+ export * from './ThemeContext';
5
+ export * from './types';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 1. Define the Conditional Logic
3
+ * If TValue is a key of TMap, return the map's value type.
4
+ * Otherwise, return TValue itself.
5
+ */
6
+ type ResolvedToken<TMap, TValue> = TValue extends keyof TMap ? TMap[TValue] : TValue;
7
+
8
+ /**
9
+ * 2. The Generic Function
10
+ */
11
+ export function resolveToken<
12
+ TMap extends Record<string | number, unknown>,
13
+ TValue extends string | number | undefined,
14
+ >(map: TMap, value: TValue): ResolvedToken<TMap, TValue> {
15
+ // Undefined check
16
+ if (value === undefined) {
17
+ return undefined as ResolvedToken<TMap, TValue>;
18
+ }
19
+
20
+ // Runtime check: Is 'value' a key in 'map'?
21
+ if (
22
+ (typeof value === 'string' || typeof value === 'number') &&
23
+ Object.prototype.hasOwnProperty.call(map, value)
24
+ ) {
25
+ // Return the mapped token
26
+ // We cast to the specific conditional type, strictly safer than 'any'
27
+ return map[value as keyof TMap] as ResolvedToken<TMap, TValue>;
28
+ }
29
+
30
+ // Fallback: Return raw value
31
+ return value as ResolvedToken<TMap, TValue>;
32
+ }
@@ -0,0 +1,188 @@
1
+ import type { ThemeConfig as ContractsThemeConfig } from '@ankhorage/contracts';
2
+
3
+ export type { ColorHarmony, SystemTone, ThemeConfig, ThemeModeConfig } from '@ankhorage/contracts';
4
+
5
+ export type ColorTone = 'jewel' | 'earth' | 'fluorescent' | 'grayscale' | 'pastel';
6
+
7
+ export interface ColorScale {
8
+ [key: number]: string;
9
+ 50: string;
10
+ 100: string;
11
+ 200: string;
12
+ 300: string;
13
+ 400: string;
14
+ 500: string;
15
+ 600: string;
16
+ 700: string;
17
+ 800: string;
18
+ 900: string;
19
+ 950: string;
20
+ }
21
+
22
+ export interface NeutralSemantics {
23
+ bg: string;
24
+ bgSubtle: string;
25
+ surface: string;
26
+ surfaceHover: string;
27
+ surfaceActive: string;
28
+ border: string;
29
+ borderStrong: string;
30
+ divider: string;
31
+ text: string;
32
+ textMuted: string;
33
+ textSubtle: string;
34
+ }
35
+
36
+ export interface RoleSemantics {
37
+ base: string;
38
+ hover: string;
39
+ strong: string;
40
+ softBg: string;
41
+ softHover: string;
42
+ softActive: string;
43
+ outline: string;
44
+ onSolidText: string;
45
+ }
46
+
47
+ export interface SurfaceSemantics {
48
+ default: string;
49
+ subtle: string;
50
+ raised: string;
51
+ }
52
+
53
+ export interface ContentSemantics {
54
+ default: string;
55
+ muted: string;
56
+ subtle: string;
57
+ inverse: string;
58
+ }
59
+
60
+ export interface BorderSemantics {
61
+ default: string;
62
+ strong: string;
63
+ focus: string;
64
+ }
65
+
66
+ export interface ActionSemantics {
67
+ primary: RoleSemantics;
68
+ neutral: RoleSemantics;
69
+ danger: RoleSemantics;
70
+ }
71
+
72
+ export interface ThemeSemantics {
73
+ neutral: NeutralSemantics;
74
+ brand: RoleSemantics;
75
+ secondary: RoleSemantics;
76
+ accent: RoleSemantics;
77
+ highlight: RoleSemantics;
78
+ danger: RoleSemantics;
79
+ success: RoleSemantics;
80
+ warning: RoleSemantics;
81
+ surface: SurfaceSemantics;
82
+ content: ContentSemantics;
83
+ border: BorderSemantics;
84
+ action: ActionSemantics;
85
+ }
86
+
87
+ export type FontWeight =
88
+ | '100'
89
+ | '200'
90
+ | '300'
91
+ | '400'
92
+ | '500'
93
+ | '600'
94
+ | '700'
95
+ | '800'
96
+ | '900'
97
+ | 'bold'
98
+ | 'normal';
99
+
100
+ export interface ThemeTokens {
101
+ colors: {
102
+ primary: string;
103
+ secondary: string;
104
+ accent: string;
105
+ highlight: string;
106
+ background: string;
107
+ surface: string;
108
+ text: string;
109
+ textSecondary: string;
110
+ border: string;
111
+ error: string;
112
+ success: string;
113
+ warning: string;
114
+ [key: string]: string;
115
+ };
116
+ scales: Record<string, ColorScale>;
117
+ semantics: ThemeSemantics;
118
+ spacing: {
119
+ none: 0;
120
+ xs: number;
121
+ s: number;
122
+ m: number;
123
+ l: number;
124
+ xl: number;
125
+ xxl: number;
126
+ [key: string]: number;
127
+ };
128
+ radii: {
129
+ none: 0;
130
+ s: number;
131
+ m: number;
132
+ l: number;
133
+ full: number;
134
+ };
135
+ typography: {
136
+ headings: Record<
137
+ 1 | 2 | 3 | 4 | 5 | 6,
138
+ {
139
+ size: number;
140
+ lineHeight: number;
141
+ weight: 'regular' | 'medium' | 'semiBold' | 'bold';
142
+ }
143
+ >;
144
+ sizes: {
145
+ xs: number;
146
+ s: number;
147
+ m: number;
148
+ l: number;
149
+ xl: number;
150
+ xxl: number;
151
+ '3xl': number;
152
+ h1: number;
153
+ h2: number;
154
+ h3: number;
155
+ h4: number;
156
+ h5: number;
157
+ h6: number;
158
+ [key: string]: number;
159
+ };
160
+ weights: {
161
+ thin: FontWeight;
162
+ extraLight: FontWeight;
163
+ light: FontWeight;
164
+ regular: FontWeight;
165
+ medium: FontWeight;
166
+ semiBold: FontWeight;
167
+ bold: FontWeight;
168
+ extraBold: FontWeight;
169
+ black: FontWeight;
170
+ };
171
+ fonts: {
172
+ /** Map of weight -> fontFamily name for normal style */
173
+ normal: Record<FontWeight, string | undefined>;
174
+ /** Map of weight -> fontFamily name for italic style */
175
+ italic: Record<FontWeight, string | undefined>;
176
+ };
177
+ };
178
+ shadows: {
179
+ soft: number;
180
+ medium: number;
181
+ hard: number;
182
+ [key: string]: number;
183
+ };
184
+ }
185
+
186
+ export interface AnkhTheme extends ThemeTokens {
187
+ config: ContractsThemeConfig;
188
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Simple deep equality check for JSON-serializable objects.
3
+ * Efficient enough for UI manifests.
4
+ */
5
+ export const isDeepEqual = (a: unknown, b: unknown): boolean => {
6
+ if (a === b) return true;
7
+
8
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
9
+ if (Array.isArray(a)) {
10
+ if (!Array.isArray(b) || a.length !== b.length) return false;
11
+ for (let i = 0; i < a.length; i++) {
12
+ if (!isDeepEqual(a[i], b[i])) return false;
13
+ }
14
+ return true;
15
+ }
16
+
17
+ if (Array.isArray(b)) return false;
18
+
19
+ const keysA = Object.keys(a);
20
+ const keysB = Object.keys(b);
21
+ if (keysA.length !== keysB.length) return false;
22
+
23
+ const objA = a as Record<string, unknown>;
24
+ const objB = b as Record<string, unknown>;
25
+
26
+ for (const key of keysA) {
27
+ if (!Object.prototype.hasOwnProperty.call(objB, key)) return false;
28
+ if (!isDeepEqual(objA[key], objB[key])) return false;
29
+ }
30
+ return true;
31
+ }
32
+
33
+ return Number.isNaN(a) && Number.isNaN(b);
34
+ };
@@ -0,0 +1,117 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { deepMerge } from './deepMerge';
4
+
5
+ type UnknownObject = Record<string, unknown>;
6
+
7
+ describe('deepMerge', () => {
8
+ it('should merge top-level properties', () => {
9
+ const target: UnknownObject = { a: 1, b: 2 };
10
+ const source: UnknownObject = { b: 3, c: 4 };
11
+ const result = deepMerge(target, source);
12
+ expect(result).toEqual({ a: 1, b: 3, c: 4 });
13
+ });
14
+
15
+ it('should deeply merge nested objects', () => {
16
+ const target: UnknownObject = {
17
+ theme: {
18
+ colors: { primary: 'blue', background: 'white' },
19
+ spacing: { m: 10 },
20
+ },
21
+ };
22
+ const source: UnknownObject = {
23
+ theme: {
24
+ colors: { primary: 'red' },
25
+ spacing: { l: 20 },
26
+ },
27
+ };
28
+ const result = deepMerge(target, source);
29
+ expect(result).toEqual({
30
+ theme: {
31
+ colors: { primary: 'red', background: 'white' },
32
+ spacing: { m: 10, l: 20 },
33
+ },
34
+ });
35
+ });
36
+
37
+ it('should handle undefined in source by keeping target value', () => {
38
+ const target = { a: 1 };
39
+ const source = { a: undefined };
40
+ const result = deepMerge(target, source);
41
+ expect(result).toEqual({ a: 1 });
42
+ });
43
+
44
+ it('should handle null in source by overwriting target value', () => {
45
+ const target: UnknownObject = { a: { b: 1 } };
46
+ const source: UnknownObject = { a: null };
47
+ const result = deepMerge(target, source);
48
+ expect(result).toEqual({ a: null });
49
+ });
50
+
51
+ it('should not merge arrays, but overwrite them', () => {
52
+ const target = { list: [1, 2] };
53
+ const source = { list: [3] };
54
+ const result = deepMerge(target, source);
55
+ expect(result).toEqual({ list: [3] });
56
+ });
57
+
58
+ it('should handle merging an object into a non-object target value', () => {
59
+ const target: UnknownObject = { a: 1 };
60
+ const source: UnknownObject = { a: { b: 2 } };
61
+ const result = deepMerge(target, source);
62
+ expect(result).toEqual({ a: { b: 2 } });
63
+ });
64
+
65
+ it('should handle merging a non-object into an object target value', () => {
66
+ const target: UnknownObject = { a: { b: 1 } };
67
+ const source: UnknownObject = { a: 2 };
68
+ const result = deepMerge(target, source);
69
+ expect(result).toEqual({ a: 2 });
70
+ });
71
+
72
+ it('should return a new object and not mutate target', () => {
73
+ const target = { a: { b: 1 } };
74
+ const source = { a: { b: 2 } };
75
+ const result = deepMerge(target, source);
76
+ expect(result).not.toBe(target);
77
+ expect(result.a).not.toBe(target.a);
78
+ expect(target.a.b).toBe(1);
79
+ });
80
+
81
+ it('should handle complex ThemeConfig structures', () => {
82
+ const target: UnknownObject = {
83
+ id: 'theme-1',
84
+ name: 'Default',
85
+ light: {
86
+ primaryColor: 'blue',
87
+ harmony: 'monochromatic',
88
+ systemTone: 'neutral',
89
+ },
90
+ dark: {
91
+ primaryColor: 'blue',
92
+ harmony: 'monochromatic',
93
+ systemTone: 'neutral',
94
+ },
95
+ };
96
+ const source: UnknownObject = {
97
+ light: {
98
+ primaryColor: 'red',
99
+ },
100
+ };
101
+ const result = deepMerge(target, source);
102
+ expect(result).toEqual({
103
+ id: 'theme-1',
104
+ name: 'Default',
105
+ light: {
106
+ primaryColor: 'red',
107
+ harmony: 'monochromatic',
108
+ systemTone: 'neutral',
109
+ },
110
+ dark: {
111
+ primaryColor: 'blue',
112
+ harmony: 'monochromatic',
113
+ systemTone: 'neutral',
114
+ },
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Performs a deep merge of `source` into `target`.
3
+ * Specifically handles the nested structure of ThemeConfig (light/dark objects).
4
+ */
5
+ export function deepMerge<T extends object>(target: T, source: Partial<T>): T {
6
+ const result = { ...target };
7
+
8
+ (Object.keys(source) as (keyof T)[]).forEach((key) => {
9
+ const sourceValue = source[key];
10
+ const targetValue = target[key];
11
+
12
+ if (
13
+ sourceValue &&
14
+ typeof sourceValue === 'object' &&
15
+ !Array.isArray(sourceValue) &&
16
+ targetValue &&
17
+ typeof targetValue === 'object' &&
18
+ !Array.isArray(targetValue)
19
+ ) {
20
+ // Recursively merge nested objects
21
+ result[key] = deepMerge(targetValue as object, sourceValue as object) as T[keyof T];
22
+ } else if (sourceValue !== undefined) {
23
+ // Direct assignment for non-object values or if target doesn't have an object to merge with
24
+ result[key] = sourceValue as T[keyof T];
25
+ }
26
+ });
27
+
28
+ return result;
29
+ }