react-native-terra-ui 0.7.2 → 0.8.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 (252) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +1 -1
  3. package/lib/module/components/avatar/Avatar.js +20 -20
  4. package/lib/module/components/avatar/Avatar.js.map +1 -1
  5. package/lib/module/components/button/Button.js +11 -11
  6. package/lib/module/components/button/Button.js.map +1 -1
  7. package/lib/module/components/chip/Chip.js +32 -32
  8. package/lib/module/components/chip/Chip.js.map +1 -1
  9. package/lib/module/components/empty-state/EmptyState.js +3 -3
  10. package/lib/module/components/empty-state/EmptyState.js.map +1 -1
  11. package/lib/module/components/header/variants/LargeTitleHeader/index.js +30 -32
  12. package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
  13. package/lib/module/components/header/variants/TitleHeader/index.js +26 -18
  14. package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
  15. package/lib/module/components/icon/Icon.js +3 -3
  16. package/lib/module/components/page-indicator/hooks/use-page-indicator-colors.js +2 -2
  17. package/lib/module/components/screen/Screen.js +63 -34
  18. package/lib/module/components/screen/Screen.js.map +1 -1
  19. package/lib/module/components/screen/ScreenContext.js +28 -9
  20. package/lib/module/components/screen/ScreenContext.js.map +1 -1
  21. package/lib/module/components/screen/ScreenFlatList.js +4 -162
  22. package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
  23. package/lib/module/components/screen/ScreenList.js +81 -97
  24. package/lib/module/components/screen/ScreenList.js.map +1 -1
  25. package/lib/module/components/screen/ScreenScrollView.js +64 -115
  26. package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
  27. package/lib/module/components/screen/hooks/use-screen-ref.js +18 -0
  28. package/lib/module/components/screen/hooks/use-screen-ref.js.map +1 -0
  29. package/lib/module/components/screen/hooks/use-screen-scroll-handler.js +19 -0
  30. package/lib/module/components/screen/hooks/use-screen-scroll-handler.js.map +1 -0
  31. package/lib/module/components/screen/index.js.map +1 -1
  32. package/lib/module/components/screen/parts/ScreenFrame.js +47 -0
  33. package/lib/module/components/screen/parts/ScreenFrame.js.map +1 -0
  34. package/lib/module/components/screen/parts/ScreenHeaderAppearance.js +43 -0
  35. package/lib/module/components/screen/parts/ScreenHeaderAppearance.js.map +1 -0
  36. package/lib/module/components/screen/parts/ScreenPlaceholder.js +11 -13
  37. package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -1
  38. package/lib/module/components/screen/parts/ScreenSafeArea.android.js +27 -0
  39. package/lib/module/components/screen/parts/ScreenSafeArea.android.js.map +1 -0
  40. package/lib/module/components/screen/parts/ScreenSafeArea.js +26 -0
  41. package/lib/module/components/screen/parts/ScreenSafeArea.js.map +1 -0
  42. package/lib/module/components/screen/utils.js +18 -9
  43. package/lib/module/components/screen/utils.js.map +1 -1
  44. package/lib/module/components/spinner/Spinner.js +2 -2
  45. package/lib/module/components/text/Text.js +1 -1
  46. package/lib/module/components/toast/Toast.js +13 -13
  47. package/lib/module/components/toast/Toast.js.map +1 -1
  48. package/lib/module/components/toolbar/Toolbar.js +2 -2
  49. package/lib/module/context/ThemeProvider.js +22 -39
  50. package/lib/module/context/ThemeProvider.js.map +1 -1
  51. package/lib/module/context/index.js +1 -1
  52. package/lib/module/context/index.js.map +1 -1
  53. package/lib/module/theme/breakpoints.js +1 -1
  54. package/lib/module/theme/define-config.js +21 -0
  55. package/lib/module/theme/define-config.js.map +1 -0
  56. package/lib/module/theme/diagnostics.js +13 -0
  57. package/lib/module/theme/diagnostics.js.map +1 -0
  58. package/lib/module/theme/errors.js +15 -0
  59. package/lib/module/theme/errors.js.map +1 -0
  60. package/lib/module/theme/index.js +7 -1
  61. package/lib/module/theme/index.js.map +1 -1
  62. package/lib/module/theme/legacy-tokens.js +119 -0
  63. package/lib/module/theme/legacy-tokens.js.map +1 -0
  64. package/lib/module/theme/registry.js +122 -130
  65. package/lib/module/theme/registry.js.map +1 -1
  66. package/lib/module/theme/resolve-config.js +346 -0
  67. package/lib/module/theme/resolve-config.js.map +1 -0
  68. package/lib/module/theme/runtime.js +57 -12
  69. package/lib/module/theme/runtime.js.map +1 -1
  70. package/lib/module/theme/screen-margin.js +6 -14
  71. package/lib/module/theme/screen-margin.js.map +1 -1
  72. package/lib/module/theme/screen-padding.js +17 -0
  73. package/lib/module/theme/screen-padding.js.map +1 -0
  74. package/lib/module/theme/selection-store.js +75 -0
  75. package/lib/module/theme/selection-store.js.map +1 -0
  76. package/lib/module/theme/theme.js +81 -36
  77. package/lib/module/theme/theme.js.map +1 -1
  78. package/lib/module/theme/tokens/canonical-color-keys.js +102 -0
  79. package/lib/module/theme/tokens/canonical-color-keys.js.map +1 -0
  80. package/lib/module/theme/tokens/dark.js +72 -88
  81. package/lib/module/theme/tokens/dark.js.map +1 -1
  82. package/lib/module/theme/tokens/light.js +69 -83
  83. package/lib/module/theme/tokens/light.js.map +1 -1
  84. package/lib/module/theme/tokens/primitives.js +14 -14
  85. package/lib/module/theme/tokens/primitives.js.map +1 -1
  86. package/lib/module/theme/tokens/validate.js +238 -0
  87. package/lib/module/theme/tokens/validate.js.map +1 -0
  88. package/lib/module/theme/unistyles-adapter.js +77 -0
  89. package/lib/module/theme/unistyles-adapter.js.map +1 -0
  90. package/lib/module/utils/accent-generator.js +476 -0
  91. package/lib/module/utils/accent-generator.js.map +1 -0
  92. package/lib/module/utils/accent-utils.js +52 -35
  93. package/lib/module/utils/accent-utils.js.map +1 -1
  94. package/lib/module/utils/color-utils.js +224 -7
  95. package/lib/module/utils/color-utils.js.map +1 -1
  96. package/lib/module/utils/deep-merge.js +1 -1
  97. package/lib/module/utils/deep-merge.js.map +1 -1
  98. package/lib/module/utils/resolve-theme-color.js +2 -2
  99. package/lib/typescript/src/components/chip/types.d.ts +1 -1
  100. package/lib/typescript/src/components/empty-state/EmptyState.d.ts +1 -1
  101. package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -1
  102. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +6 -5
  103. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
  104. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +11 -5
  105. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
  106. package/lib/typescript/src/components/icon/Icon.d.ts +2 -2
  107. package/lib/typescript/src/components/screen/Screen.d.ts +45 -16
  108. package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
  109. package/lib/typescript/src/components/screen/ScreenContext.d.ts +42 -26
  110. package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
  111. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +5 -46
  112. package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
  113. package/lib/typescript/src/components/screen/ScreenList.d.ts +62 -50
  114. package/lib/typescript/src/components/screen/ScreenList.d.ts.map +1 -1
  115. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +18 -58
  116. package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
  117. package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts +5 -0
  118. package/lib/typescript/src/components/screen/hooks/use-screen-ref.d.ts.map +1 -0
  119. package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts +53 -0
  120. package/lib/typescript/src/components/screen/hooks/use-screen-scroll-handler.d.ts.map +1 -0
  121. package/lib/typescript/src/components/screen/index.d.ts +2 -2
  122. package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
  123. package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts +6 -0
  124. package/lib/typescript/src/components/screen/parts/ScreenFrame.d.ts.map +1 -0
  125. package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts +21 -0
  126. package/lib/typescript/src/components/screen/parts/ScreenHeaderAppearance.d.ts.map +1 -0
  127. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +6 -8
  128. package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -1
  129. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts +7 -0
  130. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.android.d.ts.map +1 -0
  131. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts +6 -0
  132. package/lib/typescript/src/components/screen/parts/ScreenSafeArea.d.ts.map +1 -0
  133. package/lib/typescript/src/components/screen/types.d.ts +27 -32
  134. package/lib/typescript/src/components/screen/types.d.ts.map +1 -1
  135. package/lib/typescript/src/components/screen/utils.d.ts +9 -8
  136. package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
  137. package/lib/typescript/src/components/text/Text.d.ts +1 -1
  138. package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
  139. package/lib/typescript/src/components/toolbar/Toolbar.d.ts +2 -2
  140. package/lib/typescript/src/context/ThemeProvider.d.ts +29 -12
  141. package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
  142. package/lib/typescript/src/context/index.d.ts +1 -1
  143. package/lib/typescript/src/context/index.d.ts.map +1 -1
  144. package/lib/typescript/src/theme/breakpoints.d.ts +1 -1
  145. package/lib/typescript/src/theme/define-config.d.ts +23 -0
  146. package/lib/typescript/src/theme/define-config.d.ts.map +1 -0
  147. package/lib/typescript/src/theme/diagnostics.d.ts +4 -0
  148. package/lib/typescript/src/theme/diagnostics.d.ts.map +1 -0
  149. package/lib/typescript/src/theme/errors.d.ts +10 -0
  150. package/lib/typescript/src/theme/errors.d.ts.map +1 -0
  151. package/lib/typescript/src/theme/index.d.ts +10 -2
  152. package/lib/typescript/src/theme/index.d.ts.map +1 -1
  153. package/lib/typescript/src/theme/legacy-tokens.d.ts +187 -0
  154. package/lib/typescript/src/theme/legacy-tokens.d.ts.map +1 -0
  155. package/lib/typescript/src/theme/registry.d.ts +12 -17
  156. package/lib/typescript/src/theme/registry.d.ts.map +1 -1
  157. package/lib/typescript/src/theme/resolve-config.d.ts +99 -0
  158. package/lib/typescript/src/theme/resolve-config.d.ts.map +1 -0
  159. package/lib/typescript/src/theme/runtime.d.ts +25 -4
  160. package/lib/typescript/src/theme/runtime.d.ts.map +1 -1
  161. package/lib/typescript/src/theme/screen-margin.d.ts +6 -14
  162. package/lib/typescript/src/theme/screen-margin.d.ts.map +1 -1
  163. package/lib/typescript/src/theme/screen-padding.d.ts +15 -0
  164. package/lib/typescript/src/theme/screen-padding.d.ts.map +1 -0
  165. package/lib/typescript/src/theme/selection-store.d.ts +41 -0
  166. package/lib/typescript/src/theme/selection-store.d.ts.map +1 -0
  167. package/lib/typescript/src/theme/theme.d.ts +54 -1
  168. package/lib/typescript/src/theme/theme.d.ts.map +1 -1
  169. package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts +95 -0
  170. package/lib/typescript/src/theme/tokens/canonical-color-keys.d.ts.map +1 -0
  171. package/lib/typescript/src/theme/tokens/dark.d.ts +56 -63
  172. package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
  173. package/lib/typescript/src/theme/tokens/light.d.ts +56 -71
  174. package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
  175. package/lib/typescript/src/theme/tokens/primitives.d.ts +11 -10
  176. package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
  177. package/lib/typescript/src/theme/tokens/validate.d.ts +34 -0
  178. package/lib/typescript/src/theme/tokens/validate.d.ts.map +1 -0
  179. package/lib/typescript/src/theme/types.d.ts +166 -86
  180. package/lib/typescript/src/theme/types.d.ts.map +1 -1
  181. package/lib/typescript/src/theme/unistyles-adapter.d.ts +33 -0
  182. package/lib/typescript/src/theme/unistyles-adapter.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/accent-generator.d.ts +103 -0
  184. package/lib/typescript/src/utils/accent-generator.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/accent-utils.d.ts +15 -2
  186. package/lib/typescript/src/utils/accent-utils.d.ts.map +1 -1
  187. package/lib/typescript/src/utils/color-utils.d.ts +59 -2
  188. package/lib/typescript/src/utils/color-utils.d.ts.map +1 -1
  189. package/lib/typescript/src/utils/deep-merge.d.ts +1 -0
  190. package/lib/typescript/src/utils/deep-merge.d.ts.map +1 -1
  191. package/lib/typescript/src/utils/resolve-theme-color.d.ts +2 -2
  192. package/package.json +1 -1
  193. package/src/components/avatar/Avatar.tsx +20 -20
  194. package/src/components/button/Button.tsx +11 -11
  195. package/src/components/chip/Chip.tsx +32 -32
  196. package/src/components/chip/types.ts +1 -1
  197. package/src/components/empty-state/EmptyState.tsx +4 -4
  198. package/src/components/header/variants/LargeTitleHeader/index.tsx +31 -30
  199. package/src/components/header/variants/TitleHeader/index.tsx +33 -22
  200. package/src/components/icon/Icon.tsx +4 -4
  201. package/src/components/page-indicator/hooks/use-page-indicator-colors.ts +2 -2
  202. package/src/components/screen/Screen.tsx +89 -43
  203. package/src/components/screen/ScreenContext.tsx +48 -28
  204. package/src/components/screen/ScreenFlatList.tsx +5 -191
  205. package/src/components/screen/ScreenList.tsx +197 -175
  206. package/src/components/screen/ScreenScrollView.tsx +92 -167
  207. package/src/components/screen/hooks/use-screen-ref.ts +24 -0
  208. package/src/components/screen/hooks/use-screen-scroll-handler.ts +31 -0
  209. package/src/components/screen/index.ts +8 -2
  210. package/src/components/screen/parts/ScreenFrame.tsx +49 -0
  211. package/src/components/screen/parts/ScreenHeaderAppearance.tsx +52 -0
  212. package/src/components/screen/parts/ScreenPlaceholder.tsx +16 -16
  213. package/src/components/screen/parts/ScreenSafeArea.android.tsx +22 -0
  214. package/src/components/screen/parts/ScreenSafeArea.tsx +20 -0
  215. package/src/components/screen/types.ts +29 -32
  216. package/src/components/screen/utils.ts +24 -11
  217. package/src/components/spinner/Spinner.tsx +2 -2
  218. package/src/components/text/Text.tsx +2 -2
  219. package/src/components/toast/Toast.tsx +13 -13
  220. package/src/components/toolbar/Toolbar.tsx +4 -4
  221. package/src/context/ThemeProvider.tsx +47 -51
  222. package/src/context/index.ts +1 -1
  223. package/src/theme/breakpoints.ts +1 -1
  224. package/src/theme/define-config.ts +27 -0
  225. package/src/theme/diagnostics.ts +11 -0
  226. package/src/theme/errors.ts +14 -0
  227. package/src/theme/index.ts +40 -1
  228. package/src/theme/legacy-tokens.ts +227 -0
  229. package/src/theme/registry.ts +119 -137
  230. package/src/theme/resolve-config.ts +406 -0
  231. package/src/theme/runtime.ts +52 -14
  232. package/src/theme/screen-margin.ts +6 -25
  233. package/src/theme/screen-padding.ts +26 -0
  234. package/src/theme/selection-store.ts +76 -0
  235. package/src/theme/theme.ts +104 -39
  236. package/src/theme/tokens/canonical-color-keys.ts +107 -0
  237. package/src/theme/tokens/dark.ts +91 -98
  238. package/src/theme/tokens/light.ts +88 -93
  239. package/src/theme/tokens/primitives.ts +14 -14
  240. package/src/theme/tokens/validate.ts +271 -0
  241. package/src/theme/types.ts +184 -95
  242. package/src/theme/unistyles-adapter.ts +75 -0
  243. package/src/utils/accent-generator.ts +530 -0
  244. package/src/utils/accent-utils.ts +66 -39
  245. package/src/utils/color-utils.ts +225 -8
  246. package/src/utils/deep-merge.ts +1 -1
  247. package/src/utils/resolve-theme-color.ts +2 -2
  248. package/lib/module/components/screen/parts/BottomSafeArea.js +0 -62
  249. package/lib/module/components/screen/parts/BottomSafeArea.js.map +0 -1
  250. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +0 -30
  251. package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +0 -1
  252. package/src/components/screen/parts/BottomSafeArea.tsx +0 -59
@@ -1,17 +1,21 @@
1
1
  import { unflatten } from "#utils/unflatten";
2
+ import { withLegacyTokens } from "./legacy-tokens";
2
3
  import { dark } from "./tokens/dark";
3
4
  import { light } from "./tokens/light";
4
5
  import { primitives } from "./tokens/primitives";
5
6
  import type {
7
+ CanonicalThemeColor,
6
8
  ElevationKey,
7
9
  ElevationScale,
8
10
  ElevationStyle,
9
11
  LayoutTokens,
10
12
  OpacityTokens,
11
13
  RadiusKey,
14
+ Scheme,
15
+ ScreenPaddingTokens,
12
16
  SpacingKey,
17
+ TerraColorRegistry,
13
18
  TerraTheme,
14
- ThemeColor,
15
19
  Typography,
16
20
  } from "./types";
17
21
 
@@ -72,8 +76,8 @@ function resolveRefs(
72
76
  * each key, leaving a flat map per category.
73
77
  *
74
78
  * Example:
75
- * { 'color.surface.default': '#fff', 'color.action.bg.primary': '#009966' }
76
- * → { color: { 'surface.default': '#fff', 'action.bg.primary': '#009966' } }
79
+ * { 'color.surface.default': '#fff', 'color.accent.solid.bg': '#009966' }
80
+ * → { color: { 'surface.default': '#fff', 'accent.solid.bg': '#009966' } }
77
81
  */
78
82
  function flattenByCategory(tokens: Record<string, unknown>): Record<string, Record<string, unknown>> {
79
83
  const out: Record<string, Record<string, unknown>> = {};
@@ -97,7 +101,7 @@ function flattenByCategory(tokens: Record<string, unknown>): Record<string, Reco
97
101
  * spacing BASE are authored here (`radius.base`, `spacing.base`); the full
98
102
  * scales are derived via {@link buildRadiusScale} / {@link buildSpacingScale}.
99
103
  * `elevation` excludes `shadowColor`, which is layered on per-scheme from each
100
- * theme's `color.shadow` token (see {@link withShadowColor}).
104
+ * theme's `color.shadow` token (see {@link resolveElevationScale}).
101
105
  */
102
106
  const prim = unflatten<{
103
107
  spacing: { base: number };
@@ -197,49 +201,110 @@ const defaultRadius = buildRadiusScale();
197
201
  const lightByCategory = flattenByCategory(resolveRefs(light, primitives));
198
202
  const darkByCategory = flattenByCategory(resolveRefs(dark, primitives));
199
203
 
204
+ /** Elevation levels as authored: everything except the per-scheme `shadowColor`. */
205
+ export type ThemeSourceElevation = Record<ElevationKey, Omit<ElevationStyle, "shadowColor">>;
206
+
207
+ /**
208
+ * The pre-finalization ("source") form of a theme — what the resolver layers
209
+ * overrides onto. It differs from {@link TerraTheme} only in what is DERIVED at
210
+ * the final boundary instead of authored:
211
+ * - `color` holds canonical (and app-declared custom) keys only; deprecated
212
+ * aliases are materialized by {@link finalizeThemeSource}.
213
+ * - `elevation` levels carry no `shadowColor`; it is derived from `color.shadow`
214
+ * (unless explicitly pinned) at finalization.
215
+ * - `layout.screen` has no `margin` alias; it mirrors `padding` at finalization.
216
+ */
217
+ export interface ThemeSource {
218
+ color: CanonicalThemeColor & TerraColorRegistry;
219
+ spacing: Record<SpacingKey, number>;
220
+ radius: Record<RadiusKey, number>;
221
+ typography: Typography;
222
+ elevation: ThemeSourceElevation;
223
+ opacity: OpacityTokens;
224
+ layout: {
225
+ screen: { padding: ScreenPaddingTokens };
226
+ header: LayoutTokens["header"];
227
+ };
228
+ }
229
+
200
230
  /**
201
- * Fills in each elevation level's `shadowColor` from the theme's `color.shadow`
202
- * token, rather than authoring it redundantly on every level in `light.ts` /
203
- * `dark.ts`.
231
+ * Structural deep clone for token data (plain objects, arrays and primitives —
232
+ * the theme never carries functions or class instances). Used so every source
233
+ * and every finalized theme is independently owned.
204
234
  */
205
- function withShadowColor(
206
- elevation: Record<ElevationKey, Omit<ElevationStyle, "shadowColor">>,
207
- shadowColor: string
235
+ export function cloneTokenTree<T>(value: T): T {
236
+ if (Array.isArray(value)) {
237
+ return value.map((item) => cloneTokenTree(item)) as unknown as T;
238
+ }
239
+ if (typeof value === "object" && value !== null) {
240
+ const out: Record<string, unknown> = {};
241
+ for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
242
+ out[key] = cloneTokenTree(item);
243
+ }
244
+ return out as T;
245
+ }
246
+ return value;
247
+ }
248
+
249
+ /**
250
+ * Builds the built-in default theme for `scheme` in its source form (see
251
+ * {@link ThemeSource}). Returns a FRESH object on every call, so callers may
252
+ * layer onto or mutate it without affecting the exported defaults or later
253
+ * calls. This is the immutable starting point of the resolution pipeline:
254
+ * `defaults → shared → scheme → accent`.
255
+ */
256
+ export function createDefaultThemeSource(scheme: Scheme): ThemeSource {
257
+ const byCategory = scheme === "light" ? lightByCategory : darkByCategory;
258
+ return cloneTokenTree({
259
+ color: byCategory.color as unknown as ThemeSource["color"],
260
+ elevation: prim.elevation,
261
+ spacing: defaultSpacing,
262
+ radius: defaultRadius,
263
+ typography: prim.typography,
264
+ opacity: prim.opacity,
265
+ layout: prim.layout,
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Resolves each elevation level's `shadowColor`: an explicitly pinned
271
+ * per-level color wins, otherwise the theme's `color.shadow` token is used —
272
+ * rather than authoring it redundantly on every level in `light.ts` / `dark.ts`.
273
+ */
274
+ export function resolveElevationScale(
275
+ elevation: ThemeSourceElevation,
276
+ shadowColor: string,
277
+ pinnedShadowColors?: Partial<Record<ElevationKey, string>>
208
278
  ): ElevationScale {
209
279
  const out = {} as ElevationScale;
210
280
  for (const key of Object.keys(elevation) as ElevationKey[]) {
211
- out[key] = { ...elevation[key], shadowColor };
281
+ out[key] = { ...elevation[key], shadowColor: pinnedShadowColors?.[key] ?? shadowColor };
212
282
  }
213
283
  return out;
214
284
  }
215
285
 
216
- // color: flat map — accessed as theme.color['action.bg.primary.hover']
217
- // elevation: shared shape from primitives, shadowColor layered on per scheme
218
- const lightTokens = {
219
- color: lightByCategory.color as unknown as ThemeColor,
220
- elevation: withShadowColor(prim.elevation, (lightByCategory.color as unknown as ThemeColor).shadow),
221
- };
222
- const darkTokens = {
223
- color: darkByCategory.color as unknown as ThemeColor,
224
- elevation: withShadowColor(prim.elevation, (darkByCategory.color as unknown as ThemeColor).shadow),
225
- };
286
+ /**
287
+ * Turns a {@link ThemeSource} into a complete, independently owned
288
+ * {@link TerraTheme}: derives elevation shadow colors (honouring pinned levels)
289
+ * and materializes every compatibility alias. Neither `source` nor the returned
290
+ * theme share object references with each other.
291
+ */
292
+ export function finalizeThemeSource(
293
+ source: ThemeSource,
294
+ pinnedShadowColors?: Partial<Record<ElevationKey, string>>
295
+ ): TerraTheme {
296
+ const fresh = cloneTokenTree(source);
297
+ return withLegacyTokens({
298
+ color: fresh.color,
299
+ elevation: resolveElevationScale(fresh.elevation, fresh.color.shadow, pinnedShadowColors),
300
+ spacing: fresh.spacing,
301
+ radius: fresh.radius,
302
+ typography: fresh.typography,
303
+ opacity: fresh.opacity,
304
+ layout: fresh.layout,
305
+ });
306
+ }
226
307
 
227
- export const defaultLightTheme: TerraTheme = {
228
- color: lightTokens.color,
229
- elevation: lightTokens.elevation,
230
- spacing: defaultSpacing,
231
- radius: defaultRadius,
232
- typography: prim.typography,
233
- opacity: prim.opacity,
234
- layout: prim.layout,
235
- };
308
+ export const defaultLightTheme: TerraTheme = finalizeThemeSource(createDefaultThemeSource("light"));
236
309
 
237
- export const defaultDarkTheme: TerraTheme = {
238
- color: darkTokens.color,
239
- elevation: darkTokens.elevation,
240
- spacing: defaultSpacing,
241
- radius: defaultRadius,
242
- typography: prim.typography,
243
- opacity: prim.opacity,
244
- layout: prim.layout,
245
- };
310
+ export const defaultDarkTheme: TerraTheme = finalizeThemeSource(createDefaultThemeSource("dark"));
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Runtime list of the built-in canonical colour keys.
3
+ *
4
+ * `CanonicalThemeColor` (types.ts) is the authoritative schema, but it is a
5
+ * type and cannot be iterated at runtime. This object mirrors it key-for-key
6
+ * so the raw-data validator can check `light.ts` / `dark.ts` against the real
7
+ * schema instead of a hand-maintained regex. The `satisfies` clause makes the
8
+ * mirror self-checking: a key missing here fails on the `Record<…>`
9
+ * requirement, and a key that is not in `CanonicalThemeColor` fails the
10
+ * excess-property check.
11
+ *
12
+ * Type-only import — this file stays free of runtime imports like the other
13
+ * token data.
14
+ */
15
+ import type { CanonicalThemeColor } from "../types";
16
+
17
+ const CANONICAL_COLOR_KEY_MAP = {
18
+ // surfaces
19
+ "surface.canvas": true,
20
+ "surface.default": true,
21
+ "surface.raised": true,
22
+ "surface.sunken": true,
23
+ // text / icons
24
+ "text.primary": true,
25
+ "text.secondary": true,
26
+ "text.tertiary": true,
27
+ "text.disabled": true,
28
+ "text.inverse": true,
29
+ "text.link": true,
30
+ // borders and focus
31
+ "border.subtle": true,
32
+ "border.default": true,
33
+ "border.strong": true,
34
+ "focus.ring": true,
35
+ // effects
36
+ backdrop: true,
37
+ shadow: true,
38
+ // accent
39
+ "accent.text": true,
40
+ "accent.border": true,
41
+ "accent.solid.bg": true,
42
+ "accent.solid.bg.hover": true,
43
+ "accent.solid.bg.pressed": true,
44
+ "accent.solid.bg.disabled": true,
45
+ "accent.solid.fg": true,
46
+ "accent.soft.bg": true,
47
+ "accent.soft.bg.hover": true,
48
+ "accent.soft.bg.pressed": true,
49
+ "accent.soft.bg.disabled": true,
50
+ "accent.soft.fg": true,
51
+ // neutral
52
+ "neutral.solid.bg": true,
53
+ "neutral.solid.fg": true,
54
+ "neutral.soft.bg": true,
55
+ "neutral.soft.bg.hover": true,
56
+ "neutral.soft.bg.pressed": true,
57
+ "neutral.soft.bg.disabled": true,
58
+ "neutral.soft.fg": true,
59
+ // status — success
60
+ "success.text": true,
61
+ "success.solid.bg": true,
62
+ "success.solid.fg": true,
63
+ "success.solid.border": true,
64
+ "success.soft.bg": true,
65
+ "success.soft.fg": true,
66
+ "success.soft.border": true,
67
+ // status — warning
68
+ "warning.text": true,
69
+ "warning.solid.bg": true,
70
+ "warning.solid.fg": true,
71
+ "warning.solid.border": true,
72
+ "warning.soft.bg": true,
73
+ "warning.soft.fg": true,
74
+ "warning.soft.border": true,
75
+ // status — danger
76
+ "danger.text": true,
77
+ "danger.solid.bg": true,
78
+ "danger.solid.fg": true,
79
+ "danger.solid.border": true,
80
+ "danger.soft.bg": true,
81
+ "danger.soft.fg": true,
82
+ "danger.soft.border": true,
83
+ // status — info
84
+ "info.text": true,
85
+ "info.solid.bg": true,
86
+ "info.solid.fg": true,
87
+ "info.solid.border": true,
88
+ "info.soft.bg": true,
89
+ "info.soft.fg": true,
90
+ "info.soft.border": true,
91
+ } as const satisfies Record<keyof CanonicalThemeColor, true>;
92
+
93
+ /**
94
+ * Every built-in canonical colour key (without the `color.` category prefix
95
+ * used by the raw token files). Exhaustive and extra-free by construction —
96
+ * see {@link CANONICAL_COLOR_KEY_MAP}.
97
+ */
98
+ export const CANONICAL_COLOR_KEYS: readonly (keyof CanonicalThemeColor)[] = Object.keys(
99
+ CANONICAL_COLOR_KEY_MAP
100
+ ) as (keyof CanonicalThemeColor)[];
101
+
102
+ /** Type-level guard that the map's literal keys and the schema's keys are the same set. */
103
+ type MissingCanonicalKey = Exclude<keyof CanonicalThemeColor, keyof typeof CANONICAL_COLOR_KEY_MAP>;
104
+ type ExtraCanonicalKey = Exclude<keyof typeof CANONICAL_COLOR_KEY_MAP, keyof CanonicalThemeColor>;
105
+ export type CanonicalColorKeysAreExhaustive = [MissingCanonicalKey, ExtraCanonicalKey] extends [never, never]
106
+ ? true
107
+ : { missing: MissingCanonicalKey; extra: ExtraCanonicalKey };
@@ -1,108 +1,101 @@
1
1
  /**
2
- * Dark-scheme color tokens flat, dotted-key data (no imports, no types).
3
- *
4
- * Convention: [category].[context].[property?].[variant].[state]
5
- *
6
- * GENERATED-STYLE SOURCE: intended to be replaced by the Figma / design-sync export.
2
+ * Dark semantic colors. See docs/design-tokens.md for naming and references.
3
+ * Generated-style source; naming and contrast updates await the next design-sync export.
7
4
  */
8
5
  export const dark = {
9
- // ── color: page background ────────────────────────────────────────────────
10
- "color.background": "#040506",
11
-
12
- // ── color: shadow (used as elevation.*.shadowColor) ───────────────────────
13
- "color.shadow": "#FFFFFF",
14
-
15
- // ── color: surfaces (bg implied — no property segment) ──────────────────
16
- "color.surface.default": "#0C0F12",
17
- "color.surface.raised": "#101418",
18
- "color.surface.sunken": "#080A0C",
19
- "color.surface.overlay": "rgba(0, 0, 0, 0.72)",
20
- "color.surface.accent": "{palette.emerald.500}",
21
- "color.surface.accent.subtle": "{palette.emerald.950}",
22
-
23
- // ── color: text / icons (fg implied — no property segment) ──────────────
24
- "color.text.default": "{palette.neutral.50}",
25
- // Off-ramp hexes (like light's) rather than neutral.400/500/600: the ramp
26
- // steps are tuned for dark text on white, and reused as light text they land
27
- // too dim. Set so `subtle` clears AA 4.5:1 on `surface.raised` — the lightest
28
- // surface, so the worst case — which keeps the muted/subtle/disabled spacing
29
- // proportional to light's.
30
- "color.text.muted": "#b8b8be",
31
- "color.text.subtle": "#8d8d96",
6
+ // Surfaces
7
+ "color.surface.canvas": "#080a0d",
8
+ "color.surface.default": "#14191f",
9
+ "color.surface.raised": "#1f262e",
10
+ "color.surface.sunken": "#0f1318",
11
+
12
+ // Text
13
+ "color.text.primary": "{palette.neutral.50}",
14
+ // Custom grays preserve supporting-text emphasis on dark surfaces.
15
+ "color.text.secondary": "#b8b8be",
16
+ "color.text.tertiary": "#8d8d96",
32
17
  "color.text.disabled": "#606069",
33
18
  "color.text.inverse": "{palette.neutral.950}",
34
19
  "color.text.link": "{palette.blue.300}",
35
- "color.text.accent": "{palette.emerald.400}",
36
- "color.text.on-accent": "{palette.neutral.950}",
37
- "color.text.on-accent-subtle": "{palette.emerald.300}",
38
20
 
39
- // ── color: borders (border-color implied — no property segment) ──────────
21
+ // Borders and focus
40
22
  "color.border.subtle": "{palette.neutral.800}",
41
23
  "color.border.default": "{palette.neutral.700}",
42
- "color.border.strong": "{palette.neutral.600}",
43
- "color.border.focus": "{palette.blue.400}",
44
- "color.border.accent": "{palette.emerald.500}",
45
-
46
- // ── color: interactive actions (bg + fg → property required) ────────────
47
- "color.action.bg.primary": "{palette.emerald.500}",
48
- "color.action.bg.primary.hover": "{palette.emerald.400}",
49
- "color.action.bg.primary.active": "{palette.emerald.300}",
50
- "color.action.bg.primary.disabled": "{palette.slate.700}",
51
- "color.action.fg.primary": "{palette.slate.950}",
52
-
53
- "color.action.bg.primary.subtle": "{palette.emerald.950}",
54
- "color.action.bg.primary.subtle.hover": "{palette.emerald.900}",
55
- "color.action.bg.primary.subtle.active": "{palette.emerald.800}",
56
- "color.action.bg.primary.subtle.disabled": "{palette.neutral.900}",
57
- "color.action.fg.primary.subtle": "{palette.emerald.300}",
58
-
59
- // `secondary`'s resting fill/text — used by Avatar/Chip's `secondary` color
60
- // and Button/Toast's neutral-emphasis look; sized for real separation
61
- // against `color.background` (neutral.950).
62
- "color.action.bg.secondary": "{palette.neutral.600}",
63
- "color.action.fg.secondary": "{palette.neutral.50}",
64
- // Translucent, like light's — and like the status schemes' `/30` fills above.
65
- // As opaque neutral.800 this was exactly `surface.raised`, so a soft
66
- // secondary Chip/Button vanished on a raised card (1.00:1), and its opaque
67
- // neutral.700 hover was indistinguishable from the resting fill there. An
68
- // overlay lightens whatever it sits on, so the fill separates on all three
69
- // surfaces (1.49-1.59:1, matching the success/warning chips).
70
- "color.action.bg.secondary.subtle": "{palette.neutral.400}/25",
71
- "color.action.bg.secondary.subtle.hover": "{palette.neutral.400}/35",
72
- "color.action.bg.secondary.subtle.active": "{palette.neutral.400}/45",
73
- "color.action.bg.secondary.subtle.disabled": "{palette.neutral.900}",
74
- "color.action.fg.secondary.subtle": "{palette.neutral.200}",
75
-
76
- // ── color: status (bg + fg + border → property required) ────────────────
77
- "color.status.success": "{palette.green.600}",
78
- "color.status.success.bg": "{palette.green.600}",
79
- "color.status.success.bg.subtle": "{palette.green.600}/30",
80
- "color.status.success.border": "{palette.green.700}",
81
- "color.status.success.border.subtle": "{palette.green.800}",
82
- "color.status.success.fg": "{palette.white}",
83
- "color.status.success.fg.subtle": "{palette.green.200}",
84
-
85
- "color.status.warning": "{palette.yellow.600}",
86
- "color.status.warning.bg": "{palette.yellow.600}",
87
- "color.status.warning.bg.subtle": "{palette.yellow.600}/30",
88
- "color.status.warning.border": "{palette.yellow.700}",
89
- "color.status.warning.border.subtle": "{palette.yellow.800}",
90
- "color.status.warning.fg": "{palette.white}",
91
- "color.status.warning.fg.subtle": "{palette.yellow.200}",
92
-
93
- "color.status.danger": "{palette.red.700}",
94
- "color.status.danger.bg": "{palette.red.700}",
95
- "color.status.danger.bg.subtle": "{palette.red.700}/30",
96
- "color.status.danger.border": "{palette.red.800}",
97
- "color.status.danger.border.subtle": "{palette.red.900}",
98
- "color.status.danger.fg": "{palette.white}",
99
- "color.status.danger.fg.subtle": "{palette.red.200}",
100
-
101
- "color.status.info": "{palette.sky.500}",
102
- "color.status.info.bg": "{palette.sky.500}",
103
- "color.status.info.bg.subtle": "{palette.sky.950}",
104
- "color.status.info.border": "{palette.sky.800}",
105
- "color.status.info.border.subtle": "{palette.sky.900}",
106
- "color.status.info.fg": "{palette.slate.950}",
107
- "color.status.info.fg.subtle": "{palette.sky.200}",
24
+ "color.border.strong": "{palette.neutral.500}",
25
+ "color.focus.ring": "{palette.blue.400}",
26
+
27
+ // Effects
28
+ "color.backdrop": "rgba(0, 0, 0, 0.72)",
29
+ "color.shadow": "{palette.black}",
30
+
31
+ // Accent
32
+ "color.accent.text": "{palette.emerald.400}",
33
+ "color.accent.border": "{palette.emerald.500}",
34
+
35
+ "color.accent.solid.bg": "{palette.emerald.500}",
36
+ "color.accent.solid.bg.hover": "{palette.emerald.400}",
37
+ "color.accent.solid.bg.pressed": "{palette.emerald.300}",
38
+ "color.accent.solid.bg.disabled": "{palette.slate.700}",
39
+ "color.accent.solid.fg": "{palette.slate.950}",
40
+
41
+ "color.accent.soft.bg": "{palette.emerald.950}",
42
+ "color.accent.soft.bg.hover": "{palette.emerald.900}",
43
+ "color.accent.soft.bg.pressed": "{palette.emerald.800}",
44
+ "color.accent.soft.bg.disabled": "{palette.neutral.900}",
45
+ "color.accent.soft.fg": "{palette.emerald.300}",
46
+
47
+ // Neutral
48
+ "color.neutral.solid.bg": "{palette.neutral.600}",
49
+ "color.neutral.solid.fg": "{palette.neutral.50}",
50
+
51
+ // Translucent fills keep soft controls distinct from surrounding surfaces.
52
+ "color.neutral.soft.bg": "{palette.neutral.400}/25",
53
+ "color.neutral.soft.bg.hover": "{palette.neutral.400}/35",
54
+ "color.neutral.soft.bg.pressed": "{palette.neutral.400}/45",
55
+ "color.neutral.soft.bg.disabled": "{palette.neutral.900}",
56
+ "color.neutral.soft.fg": "{palette.neutral.200}",
57
+
58
+ // Success
59
+ "color.success.text": "{palette.green.300}",
60
+
61
+ "color.success.solid.bg": "{palette.green.600}",
62
+ "color.success.solid.fg": "{palette.slate.950}",
63
+ "color.success.solid.border": "{palette.green.700}",
64
+
65
+ "color.success.soft.bg": "{palette.green.600}/30",
66
+ "color.success.soft.fg": "{palette.green.200}",
67
+ "color.success.soft.border": "{palette.green.800}",
68
+
69
+ // Warning
70
+ "color.warning.text": "{palette.yellow.300}",
71
+
72
+ "color.warning.solid.bg": "{palette.yellow.600}",
73
+ "color.warning.solid.fg": "{palette.slate.950}",
74
+ "color.warning.solid.border": "{palette.yellow.700}",
75
+
76
+ "color.warning.soft.bg": "{palette.yellow.600}/30",
77
+ "color.warning.soft.fg": "{palette.yellow.200}",
78
+ "color.warning.soft.border": "{palette.yellow.800}",
79
+
80
+ // Danger
81
+ "color.danger.text": "{palette.red.300}",
82
+
83
+ "color.danger.solid.bg": "{palette.red.700}",
84
+ "color.danger.solid.fg": "{palette.white}",
85
+ "color.danger.solid.border": "{palette.red.800}",
86
+
87
+ "color.danger.soft.bg": "{palette.red.700}/30",
88
+ "color.danger.soft.fg": "{palette.red.200}",
89
+ "color.danger.soft.border": "{palette.red.900}",
90
+
91
+ // Info
92
+ "color.info.text": "{palette.sky.300}",
93
+
94
+ "color.info.solid.bg": "{palette.sky.500}",
95
+ "color.info.solid.fg": "{palette.slate.950}",
96
+ "color.info.solid.border": "{palette.sky.800}",
97
+
98
+ "color.info.soft.bg": "{palette.sky.950}",
99
+ "color.info.soft.fg": "{palette.sky.200}",
100
+ "color.info.soft.border": "{palette.sky.900}",
108
101
  };