@ankhorage/zora 2.10.0 → 2.12.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 (180) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/dist/components/button/Button.d.ts.map +1 -1
  4. package/dist/components/button/Button.js +4 -2
  5. package/dist/components/button/Button.js.map +1 -1
  6. package/dist/components/button/meta.d.ts +3 -6
  7. package/dist/components/button/meta.d.ts.map +1 -1
  8. package/dist/components/button/meta.js +9 -15
  9. package/dist/components/button/meta.js.map +1 -1
  10. package/dist/components/button/resolveButtonThemeRecipe.d.ts +9 -0
  11. package/dist/components/button/resolveButtonThemeRecipe.d.ts.map +1 -0
  12. package/dist/components/button/resolveButtonThemeRecipe.js +30 -0
  13. package/dist/components/button/resolveButtonThemeRecipe.js.map +1 -0
  14. package/dist/components/button/themeRecipeMeta.d.ts +4 -10
  15. package/dist/components/button/themeRecipeMeta.d.ts.map +1 -1
  16. package/dist/components/button/themeRecipeMeta.js +5 -10
  17. package/dist/components/button/themeRecipeMeta.js.map +1 -1
  18. package/dist/components/card/Card.d.ts.map +1 -1
  19. package/dist/components/card/Card.js +8 -4
  20. package/dist/components/card/Card.js.map +1 -1
  21. package/dist/components/card/meta.d.ts +23 -4
  22. package/dist/components/card/meta.d.ts.map +1 -1
  23. package/dist/components/card/meta.js +14 -15
  24. package/dist/components/card/meta.js.map +1 -1
  25. package/dist/components/card/resolveCardThemeRecipe.d.ts +15 -0
  26. package/dist/components/card/resolveCardThemeRecipe.d.ts.map +1 -0
  27. package/dist/components/card/resolveCardThemeRecipe.js +22 -0
  28. package/dist/components/card/resolveCardThemeRecipe.js.map +1 -0
  29. package/dist/components/card/themeRecipeMeta.d.ts +1 -1
  30. package/dist/components/card/themeRecipeMeta.js +1 -1
  31. package/dist/components/card/themeRecipeMeta.js.map +1 -1
  32. package/dist/components/card/types.d.ts +2 -0
  33. package/dist/components/card/types.d.ts.map +1 -1
  34. package/dist/components/card/types.js.map +1 -1
  35. package/dist/components/heading/Heading.d.ts.map +1 -1
  36. package/dist/components/heading/Heading.js +14 -15
  37. package/dist/components/heading/Heading.js.map +1 -1
  38. package/dist/components/heading/meta.d.ts +6 -1
  39. package/dist/components/heading/meta.d.ts.map +1 -1
  40. package/dist/components/heading/meta.js +13 -22
  41. package/dist/components/heading/meta.js.map +1 -1
  42. package/dist/components/heading/resolveHeadingThemeRecipe.d.ts +11 -0
  43. package/dist/components/heading/resolveHeadingThemeRecipe.d.ts.map +1 -0
  44. package/dist/components/heading/resolveHeadingThemeRecipe.js +46 -0
  45. package/dist/components/heading/resolveHeadingThemeRecipe.js.map +1 -0
  46. package/dist/components/heading/themeRecipeMeta.d.ts +39 -0
  47. package/dist/components/heading/themeRecipeMeta.d.ts.map +1 -0
  48. package/dist/components/heading/themeRecipeMeta.js +32 -0
  49. package/dist/components/heading/themeRecipeMeta.js.map +1 -0
  50. package/dist/components/text/Text.d.ts +0 -1
  51. package/dist/components/text/Text.d.ts.map +1 -1
  52. package/dist/components/text/Text.js +14 -16
  53. package/dist/components/text/Text.js.map +1 -1
  54. package/dist/components/text/meta.d.ts +6 -5
  55. package/dist/components/text/meta.d.ts.map +1 -1
  56. package/dist/components/text/meta.js +13 -25
  57. package/dist/components/text/meta.js.map +1 -1
  58. package/dist/components/text/resolveTextThemeRecipe.d.ts +11 -0
  59. package/dist/components/text/resolveTextThemeRecipe.d.ts.map +1 -0
  60. package/dist/components/text/resolveTextThemeRecipe.js +46 -0
  61. package/dist/components/text/resolveTextThemeRecipe.js.map +1 -0
  62. package/dist/components/text/themeRecipeMeta.d.ts +15 -7
  63. package/dist/components/text/themeRecipeMeta.d.ts.map +1 -1
  64. package/dist/components/text/themeRecipeMeta.js +12 -11
  65. package/dist/components/text/themeRecipeMeta.js.map +1 -1
  66. package/dist/foundation/meta.d.ts +4 -4
  67. package/dist/layout/screen/meta.d.ts +1 -1
  68. package/dist/layout/screen-section/meta.d.ts +1 -1
  69. package/dist/metadata/allowedChildren.d.ts +3 -3
  70. package/dist/metadata/allowedChildren.d.ts.map +1 -1
  71. package/dist/metadata/allowedChildren.js +1 -0
  72. package/dist/metadata/allowedChildren.js.map +1 -1
  73. package/dist/metadata/componentMeta.d.ts.map +1 -1
  74. package/dist/metadata/componentMeta.js +2 -0
  75. package/dist/metadata/componentMeta.js.map +1 -1
  76. package/dist/metadata/themeRecipeMeta.d.ts.map +1 -1
  77. package/dist/metadata/themeRecipeMeta.js +2 -0
  78. package/dist/metadata/themeRecipeMeta.js.map +1 -1
  79. package/dist/metadata/themeRecipeTypes.d.ts +3 -3
  80. package/dist/metadata/themeRecipeTypes.d.ts.map +1 -1
  81. package/dist/metadata/themeRecipeTypes.js.map +1 -1
  82. package/dist/metadata/types.d.ts +1 -0
  83. package/dist/metadata/types.d.ts.map +1 -1
  84. package/dist/metadata/types.js.map +1 -1
  85. package/dist/patterns/message-bubble/meta.d.ts +1 -1
  86. package/dist/patterns/notice/meta.d.ts +1 -1
  87. package/dist/patterns/panel/Panel.d.ts +0 -2
  88. package/dist/patterns/panel/Panel.d.ts.map +1 -1
  89. package/dist/patterns/panel/Panel.js +6 -4
  90. package/dist/patterns/panel/Panel.js.map +1 -1
  91. package/dist/patterns/panel/meta.d.ts +23 -4
  92. package/dist/patterns/panel/meta.d.ts.map +1 -1
  93. package/dist/patterns/panel/meta.js +14 -15
  94. package/dist/patterns/panel/meta.js.map +1 -1
  95. package/dist/patterns/panel/themeRecipeMeta.d.ts +1 -1
  96. package/dist/patterns/panel/themeRecipeMeta.js +1 -1
  97. package/dist/patterns/panel/themeRecipeMeta.js.map +1 -1
  98. package/dist/patterns/panel/types.d.ts +2 -0
  99. package/dist/patterns/panel/types.d.ts.map +1 -1
  100. package/dist/patterns/panel/types.js.map +1 -1
  101. package/dist/patterns/post-card/meta.d.ts +1 -1
  102. package/dist/patterns/scanner/meta.d.ts +2 -2
  103. package/dist/registry.d.ts.map +1 -1
  104. package/dist/registry.js +2 -0
  105. package/dist/registry.js.map +1 -1
  106. package/dist/theme/ThemeModeToggle.d.ts +1 -1
  107. package/dist/theme/ThemeModeToggle.d.ts.map +1 -1
  108. package/dist/theme/ThemeModeToggle.js +2 -2
  109. package/dist/theme/ThemeModeToggle.js.map +1 -1
  110. package/dist/theme/ThemeModeToggle.meta.d.ts +28 -0
  111. package/dist/theme/ThemeModeToggle.meta.d.ts.map +1 -0
  112. package/dist/theme/ThemeModeToggle.meta.js +28 -0
  113. package/dist/theme/ThemeModeToggle.meta.js.map +1 -0
  114. package/dist/theme/ThemeModeToggleProps.d.ts +1 -1
  115. package/dist/theme/ThemeModeToggleProps.d.ts.map +1 -1
  116. package/dist/theme/ThemeModeToggleProps.js.map +1 -1
  117. package/dist/theme/ZoraProvider.d.ts +4 -12
  118. package/dist/theme/ZoraProvider.d.ts.map +1 -1
  119. package/dist/theme/ZoraProvider.js +5 -14
  120. package/dist/theme/ZoraProvider.js.map +1 -1
  121. package/dist/theme/ZoraThemeRuntimeContext.d.ts +1 -2
  122. package/dist/theme/ZoraThemeRuntimeContext.d.ts.map +1 -1
  123. package/dist/theme/ZoraThemeRuntimeContext.js +0 -1
  124. package/dist/theme/ZoraThemeRuntimeContext.js.map +1 -1
  125. package/dist/theme/ZoraThemeScope.d.ts.map +1 -1
  126. package/dist/theme/ZoraThemeScope.js +4 -10
  127. package/dist/theme/ZoraThemeScope.js.map +1 -1
  128. package/dist/theme/resolveZoraThemeRecipe.d.ts +16 -0
  129. package/dist/theme/resolveZoraThemeRecipe.d.ts.map +1 -0
  130. package/dist/theme/resolveZoraThemeRecipe.js +56 -0
  131. package/dist/theme/resolveZoraThemeRecipe.js.map +1 -0
  132. package/dist/theme/useZoraThemeRecipe.d.ts +2 -0
  133. package/dist/theme/useZoraThemeRecipe.d.ts.map +1 -0
  134. package/dist/theme/useZoraThemeRecipe.js +7 -0
  135. package/dist/theme/useZoraThemeRecipe.js.map +1 -0
  136. package/package.json +4 -4
  137. package/src/components/button/Button.tsx +4 -2
  138. package/src/components/button/meta.ts +10 -15
  139. package/src/components/button/resolveButtonThemeRecipe.test.ts +25 -0
  140. package/src/components/button/resolveButtonThemeRecipe.ts +41 -0
  141. package/src/components/button/themeRecipeMeta.ts +5 -10
  142. package/src/components/card/Card.tsx +13 -7
  143. package/src/components/card/meta.ts +15 -15
  144. package/src/components/card/resolveCardThemeRecipe.test.ts +28 -0
  145. package/src/components/card/resolveCardThemeRecipe.ts +33 -0
  146. package/src/components/card/themeRecipeMeta.ts +1 -1
  147. package/src/components/card/types.ts +2 -0
  148. package/src/components/contentFallback.test.ts +4 -4
  149. package/src/components/heading/Heading.tsx +18 -28
  150. package/src/components/heading/meta.ts +14 -22
  151. package/src/components/heading/resolveHeadingThemeRecipe.ts +55 -0
  152. package/src/components/heading/themeRecipeMeta.ts +33 -0
  153. package/src/components/text/Text.tsx +16 -25
  154. package/src/components/text/meta.ts +14 -25
  155. package/src/components/text/resolveTextThemeRecipe.ts +48 -0
  156. package/src/components/text/themeRecipeMeta.ts +12 -11
  157. package/src/metadata/allowedChildren.ts +1 -0
  158. package/src/metadata/authoringMeta.test.ts +18 -44
  159. package/src/metadata/componentMeta.test.ts +8 -0
  160. package/src/metadata/componentMeta.ts +2 -0
  161. package/src/metadata/themeRecipeMeta.test.ts +41 -39
  162. package/src/metadata/themeRecipeMeta.ts +2 -0
  163. package/src/metadata/themeRecipeTypes.ts +3 -3
  164. package/src/metadata/types.ts +3 -9
  165. package/src/patterns/panel/Panel.tsx +14 -4
  166. package/src/patterns/panel/meta.ts +15 -15
  167. package/src/patterns/panel/themeRecipeMeta.ts +1 -1
  168. package/src/patterns/panel/types.ts +2 -0
  169. package/src/registry.test.ts +5 -3
  170. package/src/registry.ts +2 -0
  171. package/src/theme/ThemeModeToggle.meta.ts +29 -0
  172. package/src/theme/ThemeModeToggle.tsx +7 -1
  173. package/src/theme/ThemeModeToggleProps.ts +4 -1
  174. package/src/theme/ZoraProvider.tsx +10 -13
  175. package/src/theme/ZoraThemeRuntimeContext.tsx +1 -3
  176. package/src/theme/ZoraThemeScope.tsx +5 -25
  177. package/src/theme/resolveZoraThemeRecipe.test.ts +72 -0
  178. package/src/theme/resolveZoraThemeRecipe.ts +91 -0
  179. package/src/theme/themeConfigScope.test.ts +25 -0
  180. package/src/theme/useZoraThemeRecipe.ts +7 -0
@@ -0,0 +1,72 @@
1
+ import type { ThemeConfig } from '@ankhorage/contracts';
2
+ import { describe, expect, test } from 'bun:test';
3
+
4
+ import { resolveZoraThemeRecipe } from './resolveZoraThemeRecipe';
5
+
6
+ const baseConfig: ThemeConfig = {
7
+ id: 'test',
8
+ name: 'Test',
9
+ light: { primaryColor: '#3B82F6', harmony: 'monochromatic' },
10
+ dark: { primaryColor: '#3B82F6', harmony: 'monochromatic' },
11
+ tokens: { spacing: { hero: 64 } },
12
+ };
13
+
14
+ function runtimeTheme(config: ThemeConfig) {
15
+ return {
16
+ config,
17
+ colors: { primary: '#3B82F6' },
18
+ spacing: { m: 16, l: 24, hero: 64 },
19
+ radii: { m: 8, l: 16 },
20
+ shadows: { soft: 2 },
21
+ typography: {
22
+ sizes: { m: 16 },
23
+ weights: { regular: '400' },
24
+ headings: { 1: { size: 32 } },
25
+ },
26
+ };
27
+ }
28
+
29
+ function withRecipes(recipes: ThemeConfig['recipes']): ThemeConfig {
30
+ return { ...baseConfig, recipes };
31
+ }
32
+
33
+ describe('resolveZoraThemeRecipe', () => {
34
+ test('merges metadata defaults with persisted known fields', () => {
35
+ const theme = runtimeTheme(
36
+ withRecipes({ components: { Card: { tone: 'outline', padding: 'hero' } } }),
37
+ );
38
+ expect(resolveZoraThemeRecipe(theme, 'Card')).toEqual({
39
+ tone: 'outline',
40
+ padding: 'hero',
41
+ radius: 'l',
42
+ compact: false,
43
+ });
44
+ });
45
+
46
+ test('ignores stale unknown persisted fields without guessing metadata', () => {
47
+ const theme = runtimeTheme(
48
+ withRecipes({ components: { Button: { variant: 'soft', retiredField: 'legacy' } } }),
49
+ );
50
+ expect(resolveZoraThemeRecipe(theme, 'Button')).toEqual({
51
+ color: 'primary',
52
+ variant: 'soft',
53
+ size: 'l',
54
+ });
55
+ });
56
+
57
+ test('rejects invalid known choices and token references', () => {
58
+ const choiceTheme = runtimeTheme(withRecipes({ components: { Button: { variant: 'neon' } } }));
59
+ expect(() => resolveZoraThemeRecipe(choiceTheme, 'Button')).toThrow(
60
+ 'Invalid theme recipe choice',
61
+ );
62
+
63
+ const tokenTheme = runtimeTheme(withRecipes({ components: { Card: { padding: 'missing' } } }));
64
+ expect(() => resolveZoraThemeRecipe(tokenTheme, 'Card')).toThrow('Unknown spacing token');
65
+ });
66
+
67
+ test('rejects code requests for unknown recipe names', () => {
68
+ expect(() => resolveZoraThemeRecipe(runtimeTheme(baseConfig), 'Missing')).toThrow(
69
+ 'Unknown ZORA theme recipe',
70
+ );
71
+ });
72
+ });
@@ -0,0 +1,91 @@
1
+ import type { ThemeConfig, ThemeRecipeOverrideValue } from '@ankhorage/contracts';
2
+
3
+ import { ZORA_THEME_RECIPE_META } from '../metadata/themeRecipeMeta';
4
+ import type { ZoraThemeRecipeFieldMeta, ZoraThemeTokenFamily } from '../metadata/themeRecipeTypes';
5
+
6
+ interface ZoraThemeRecipeRuntimeTheme {
7
+ readonly config: ThemeConfig;
8
+ readonly colors: object;
9
+ readonly spacing: object;
10
+ readonly radii: object;
11
+ readonly shadows: object;
12
+ readonly typography: {
13
+ readonly sizes: object;
14
+ readonly weights: object;
15
+ readonly headings: object;
16
+ };
17
+ }
18
+
19
+ const recipeMetaByName = new Map(Object.entries(ZORA_THEME_RECIPE_META));
20
+
21
+ export function resolveZoraThemeRecipe(
22
+ theme: ZoraThemeRecipeRuntimeTheme,
23
+ recipeName: string,
24
+ ): Readonly<Record<string, ThemeRecipeOverrideValue>> {
25
+ const meta = recipeMetaByName.get(recipeName);
26
+ if (!meta) throw new RangeError(`Unknown ZORA theme recipe: ${recipeName}.`);
27
+
28
+ const recipes =
29
+ meta.kind === 'component' ? theme.config.recipes?.components : theme.config.recipes?.patterns;
30
+ const overrides = new Map(Object.entries(recipes ?? {})).get(recipeName);
31
+ const overrideByField = new Map(Object.entries(overrides ?? {}));
32
+ const resolved: [string, ThemeRecipeOverrideValue][] = [];
33
+
34
+ for (const [fieldName, fieldMeta] of Object.entries(meta.fields)) {
35
+ const override = overrideByField.get(fieldName);
36
+ if (override !== undefined) {
37
+ validateFieldValue(theme, recipeName, fieldName, fieldMeta, override);
38
+ resolved.push([fieldName, override]);
39
+ } else if (fieldMeta.default !== undefined) {
40
+ resolved.push([fieldName, fieldMeta.default]);
41
+ }
42
+ }
43
+
44
+ return Object.fromEntries(resolved);
45
+ }
46
+
47
+ function validateFieldValue(
48
+ theme: ZoraThemeRecipeRuntimeTheme,
49
+ recipeName: string,
50
+ fieldName: string,
51
+ meta: ZoraThemeRecipeFieldMeta,
52
+ value: ThemeRecipeOverrideValue,
53
+ ): void {
54
+ if (meta.type === 'boolean') {
55
+ if (typeof value !== 'boolean') {
56
+ throw new TypeError(`Theme recipe ${recipeName}.${fieldName} must be boolean.`);
57
+ }
58
+ return;
59
+ }
60
+ if (typeof value !== 'string') {
61
+ throw new TypeError(`Theme recipe ${recipeName}.${fieldName} must be a string.`);
62
+ }
63
+ if (meta.type === 'choice' && !meta.options.includes(value)) {
64
+ throw new RangeError(`Invalid theme recipe choice for ${recipeName}.${fieldName}: ${value}.`);
65
+ }
66
+ if (meta.type === 'token' && !hasThemeToken(theme, meta.tokenFamily, value)) {
67
+ throw new RangeError(
68
+ `Unknown ${meta.tokenFamily} token for ${recipeName}.${fieldName}: ${value}.`,
69
+ );
70
+ }
71
+ }
72
+
73
+ function hasThemeToken(
74
+ theme: ZoraThemeRecipeRuntimeTheme,
75
+ family: ZoraThemeTokenFamily,
76
+ token: string,
77
+ ): boolean {
78
+ if (family === 'colors') return hasOwn(theme.colors, token);
79
+ if (family === 'spacing') return hasOwn(theme.spacing, token);
80
+ if (family === 'radii') return hasOwn(theme.radii, token);
81
+ if (family === 'shadows') return hasOwn(theme.shadows, token);
82
+ return (
83
+ hasOwn(theme.typography.sizes, token) ||
84
+ hasOwn(theme.typography.weights, token) ||
85
+ hasOwn(theme.typography.headings, token)
86
+ );
87
+ }
88
+
89
+ function hasOwn(value: object, key: PropertyKey): boolean {
90
+ return Object.prototype.hasOwnProperty.call(value, key);
91
+ }
@@ -0,0 +1,25 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ import { describe, expect, test } from 'bun:test';
5
+
6
+ const themeDir = import.meta.dir;
7
+
8
+ describe('canonical ThemeConfig scope propagation', () => {
9
+ test('provider accepts the canonical ThemeConfig without duplicating live config state', () => {
10
+ const provider = readFileSync(join(themeDir, 'ZoraProvider.tsx'), 'utf8');
11
+ const context = readFileSync(join(themeDir, 'ZoraThemeRuntimeContext.tsx'), 'utf8');
12
+ expect(provider).toContain('themeConfig?: ThemeConfig');
13
+ expect(provider).toContain('themeConfig ?? createZoraThemeConfig(theme)');
14
+ expect(context).not.toContain('ThemeConfig');
15
+ expect(context).not.toContain('sourceTheme');
16
+ });
17
+
18
+ test('nested scopes derive from the current Surface config authority', () => {
19
+ const scope = readFileSync(join(themeDir, 'ZoraThemeScope.tsx'), 'utf8');
20
+ expect(scope).toContain('const sourceConfig = parentSurface.theme.config;');
21
+ expect(scope).toContain('createTheme(sourceConfig');
22
+ expect(scope).not.toContain('createZoraThemeConfig');
23
+ expect(scope).not.toContain('sourceTheme');
24
+ });
25
+ });
@@ -0,0 +1,7 @@
1
+ import { resolveZoraThemeRecipe } from './resolveZoraThemeRecipe';
2
+ import { useZoraTheme } from './useZoraTheme';
3
+
4
+ export function useZoraThemeRecipe(recipeName: string) {
5
+ const { theme } = useZoraTheme();
6
+ return resolveZoraThemeRecipe(theme, recipeName);
7
+ }