@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.
- package/CHANGELOG.md +12 -0
- package/README.md +2 -2
- package/dist/components/button/Button.d.ts.map +1 -1
- package/dist/components/button/Button.js +4 -2
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/button/meta.d.ts +3 -6
- package/dist/components/button/meta.d.ts.map +1 -1
- package/dist/components/button/meta.js +9 -15
- package/dist/components/button/meta.js.map +1 -1
- package/dist/components/button/resolveButtonThemeRecipe.d.ts +9 -0
- package/dist/components/button/resolveButtonThemeRecipe.d.ts.map +1 -0
- package/dist/components/button/resolveButtonThemeRecipe.js +30 -0
- package/dist/components/button/resolveButtonThemeRecipe.js.map +1 -0
- package/dist/components/button/themeRecipeMeta.d.ts +4 -10
- package/dist/components/button/themeRecipeMeta.d.ts.map +1 -1
- package/dist/components/button/themeRecipeMeta.js +5 -10
- package/dist/components/button/themeRecipeMeta.js.map +1 -1
- package/dist/components/card/Card.d.ts.map +1 -1
- package/dist/components/card/Card.js +8 -4
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/card/meta.d.ts +23 -4
- package/dist/components/card/meta.d.ts.map +1 -1
- package/dist/components/card/meta.js +14 -15
- package/dist/components/card/meta.js.map +1 -1
- package/dist/components/card/resolveCardThemeRecipe.d.ts +15 -0
- package/dist/components/card/resolveCardThemeRecipe.d.ts.map +1 -0
- package/dist/components/card/resolveCardThemeRecipe.js +22 -0
- package/dist/components/card/resolveCardThemeRecipe.js.map +1 -0
- package/dist/components/card/themeRecipeMeta.d.ts +1 -1
- package/dist/components/card/themeRecipeMeta.js +1 -1
- package/dist/components/card/themeRecipeMeta.js.map +1 -1
- package/dist/components/card/types.d.ts +2 -0
- package/dist/components/card/types.d.ts.map +1 -1
- package/dist/components/card/types.js.map +1 -1
- package/dist/components/heading/Heading.d.ts.map +1 -1
- package/dist/components/heading/Heading.js +14 -15
- package/dist/components/heading/Heading.js.map +1 -1
- package/dist/components/heading/meta.d.ts +6 -1
- package/dist/components/heading/meta.d.ts.map +1 -1
- package/dist/components/heading/meta.js +13 -22
- package/dist/components/heading/meta.js.map +1 -1
- package/dist/components/heading/resolveHeadingThemeRecipe.d.ts +11 -0
- package/dist/components/heading/resolveHeadingThemeRecipe.d.ts.map +1 -0
- package/dist/components/heading/resolveHeadingThemeRecipe.js +46 -0
- package/dist/components/heading/resolveHeadingThemeRecipe.js.map +1 -0
- package/dist/components/heading/themeRecipeMeta.d.ts +39 -0
- package/dist/components/heading/themeRecipeMeta.d.ts.map +1 -0
- package/dist/components/heading/themeRecipeMeta.js +32 -0
- package/dist/components/heading/themeRecipeMeta.js.map +1 -0
- package/dist/components/text/Text.d.ts +0 -1
- package/dist/components/text/Text.d.ts.map +1 -1
- package/dist/components/text/Text.js +14 -16
- package/dist/components/text/Text.js.map +1 -1
- package/dist/components/text/meta.d.ts +6 -5
- package/dist/components/text/meta.d.ts.map +1 -1
- package/dist/components/text/meta.js +13 -25
- package/dist/components/text/meta.js.map +1 -1
- package/dist/components/text/resolveTextThemeRecipe.d.ts +11 -0
- package/dist/components/text/resolveTextThemeRecipe.d.ts.map +1 -0
- package/dist/components/text/resolveTextThemeRecipe.js +46 -0
- package/dist/components/text/resolveTextThemeRecipe.js.map +1 -0
- package/dist/components/text/themeRecipeMeta.d.ts +15 -7
- package/dist/components/text/themeRecipeMeta.d.ts.map +1 -1
- package/dist/components/text/themeRecipeMeta.js +12 -11
- package/dist/components/text/themeRecipeMeta.js.map +1 -1
- package/dist/foundation/meta.d.ts +4 -4
- package/dist/layout/screen/meta.d.ts +1 -1
- package/dist/layout/screen-section/meta.d.ts +1 -1
- package/dist/metadata/allowedChildren.d.ts +3 -3
- package/dist/metadata/allowedChildren.d.ts.map +1 -1
- package/dist/metadata/allowedChildren.js +1 -0
- package/dist/metadata/allowedChildren.js.map +1 -1
- package/dist/metadata/componentMeta.d.ts.map +1 -1
- package/dist/metadata/componentMeta.js +2 -0
- package/dist/metadata/componentMeta.js.map +1 -1
- package/dist/metadata/themeRecipeMeta.d.ts.map +1 -1
- package/dist/metadata/themeRecipeMeta.js +2 -0
- package/dist/metadata/themeRecipeMeta.js.map +1 -1
- package/dist/metadata/themeRecipeTypes.d.ts +3 -3
- package/dist/metadata/themeRecipeTypes.d.ts.map +1 -1
- package/dist/metadata/themeRecipeTypes.js.map +1 -1
- package/dist/metadata/types.d.ts +1 -0
- package/dist/metadata/types.d.ts.map +1 -1
- package/dist/metadata/types.js.map +1 -1
- package/dist/patterns/message-bubble/meta.d.ts +1 -1
- package/dist/patterns/notice/meta.d.ts +1 -1
- package/dist/patterns/panel/Panel.d.ts +0 -2
- package/dist/patterns/panel/Panel.d.ts.map +1 -1
- package/dist/patterns/panel/Panel.js +6 -4
- package/dist/patterns/panel/Panel.js.map +1 -1
- package/dist/patterns/panel/meta.d.ts +23 -4
- package/dist/patterns/panel/meta.d.ts.map +1 -1
- package/dist/patterns/panel/meta.js +14 -15
- package/dist/patterns/panel/meta.js.map +1 -1
- package/dist/patterns/panel/themeRecipeMeta.d.ts +1 -1
- package/dist/patterns/panel/themeRecipeMeta.js +1 -1
- package/dist/patterns/panel/themeRecipeMeta.js.map +1 -1
- package/dist/patterns/panel/types.d.ts +2 -0
- package/dist/patterns/panel/types.d.ts.map +1 -1
- package/dist/patterns/panel/types.js.map +1 -1
- package/dist/patterns/post-card/meta.d.ts +1 -1
- package/dist/patterns/scanner/meta.d.ts +2 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -1
- package/dist/theme/ThemeModeToggle.d.ts +1 -1
- package/dist/theme/ThemeModeToggle.d.ts.map +1 -1
- package/dist/theme/ThemeModeToggle.js +2 -2
- package/dist/theme/ThemeModeToggle.js.map +1 -1
- package/dist/theme/ThemeModeToggle.meta.d.ts +28 -0
- package/dist/theme/ThemeModeToggle.meta.d.ts.map +1 -0
- package/dist/theme/ThemeModeToggle.meta.js +28 -0
- package/dist/theme/ThemeModeToggle.meta.js.map +1 -0
- package/dist/theme/ThemeModeToggleProps.d.ts +1 -1
- package/dist/theme/ThemeModeToggleProps.d.ts.map +1 -1
- package/dist/theme/ThemeModeToggleProps.js.map +1 -1
- package/dist/theme/ZoraProvider.d.ts +4 -12
- package/dist/theme/ZoraProvider.d.ts.map +1 -1
- package/dist/theme/ZoraProvider.js +5 -14
- package/dist/theme/ZoraProvider.js.map +1 -1
- package/dist/theme/ZoraThemeRuntimeContext.d.ts +1 -2
- package/dist/theme/ZoraThemeRuntimeContext.d.ts.map +1 -1
- package/dist/theme/ZoraThemeRuntimeContext.js +0 -1
- package/dist/theme/ZoraThemeRuntimeContext.js.map +1 -1
- package/dist/theme/ZoraThemeScope.d.ts.map +1 -1
- package/dist/theme/ZoraThemeScope.js +4 -10
- package/dist/theme/ZoraThemeScope.js.map +1 -1
- package/dist/theme/resolveZoraThemeRecipe.d.ts +16 -0
- package/dist/theme/resolveZoraThemeRecipe.d.ts.map +1 -0
- package/dist/theme/resolveZoraThemeRecipe.js +56 -0
- package/dist/theme/resolveZoraThemeRecipe.js.map +1 -0
- package/dist/theme/useZoraThemeRecipe.d.ts +2 -0
- package/dist/theme/useZoraThemeRecipe.d.ts.map +1 -0
- package/dist/theme/useZoraThemeRecipe.js +7 -0
- package/dist/theme/useZoraThemeRecipe.js.map +1 -0
- package/package.json +4 -4
- package/src/components/button/Button.tsx +4 -2
- package/src/components/button/meta.ts +10 -15
- package/src/components/button/resolveButtonThemeRecipe.test.ts +25 -0
- package/src/components/button/resolveButtonThemeRecipe.ts +41 -0
- package/src/components/button/themeRecipeMeta.ts +5 -10
- package/src/components/card/Card.tsx +13 -7
- package/src/components/card/meta.ts +15 -15
- package/src/components/card/resolveCardThemeRecipe.test.ts +28 -0
- package/src/components/card/resolveCardThemeRecipe.ts +33 -0
- package/src/components/card/themeRecipeMeta.ts +1 -1
- package/src/components/card/types.ts +2 -0
- package/src/components/contentFallback.test.ts +4 -4
- package/src/components/heading/Heading.tsx +18 -28
- package/src/components/heading/meta.ts +14 -22
- package/src/components/heading/resolveHeadingThemeRecipe.ts +55 -0
- package/src/components/heading/themeRecipeMeta.ts +33 -0
- package/src/components/text/Text.tsx +16 -25
- package/src/components/text/meta.ts +14 -25
- package/src/components/text/resolveTextThemeRecipe.ts +48 -0
- package/src/components/text/themeRecipeMeta.ts +12 -11
- package/src/metadata/allowedChildren.ts +1 -0
- package/src/metadata/authoringMeta.test.ts +18 -44
- package/src/metadata/componentMeta.test.ts +8 -0
- package/src/metadata/componentMeta.ts +2 -0
- package/src/metadata/themeRecipeMeta.test.ts +41 -39
- package/src/metadata/themeRecipeMeta.ts +2 -0
- package/src/metadata/themeRecipeTypes.ts +3 -3
- package/src/metadata/types.ts +3 -9
- package/src/patterns/panel/Panel.tsx +14 -4
- package/src/patterns/panel/meta.ts +15 -15
- package/src/patterns/panel/themeRecipeMeta.ts +1 -1
- package/src/patterns/panel/types.ts +2 -0
- package/src/registry.test.ts +5 -3
- package/src/registry.ts +2 -0
- package/src/theme/ThemeModeToggle.meta.ts +29 -0
- package/src/theme/ThemeModeToggle.tsx +7 -1
- package/src/theme/ThemeModeToggleProps.ts +4 -1
- package/src/theme/ZoraProvider.tsx +10 -13
- package/src/theme/ZoraThemeRuntimeContext.tsx +1 -3
- package/src/theme/ZoraThemeScope.tsx +5 -25
- package/src/theme/resolveZoraThemeRecipe.test.ts +72 -0
- package/src/theme/resolveZoraThemeRecipe.ts +91 -0
- package/src/theme/themeConfigScope.test.ts +25 -0
- package/src/theme/useZoraThemeRecipe.ts +7 -0
|
@@ -2,70 +2,44 @@ import { describe, expect, test } from 'bun:test';
|
|
|
2
2
|
|
|
3
3
|
import { ZORA_COMPONENT_META, type ZoraComponentPropAuthoring } from './index';
|
|
4
4
|
|
|
5
|
+
const componentMetaByName = new Map(Object.entries(ZORA_COMPONENT_META));
|
|
6
|
+
|
|
5
7
|
describe('ZORA component prop authoring authority', () => {
|
|
6
|
-
test('supports
|
|
8
|
+
test('supports instance and theme authority with explicit instance override capability', () => {
|
|
7
9
|
const values: readonly ZoraComponentPropAuthoring[] = [
|
|
8
10
|
{ authority: 'instance' },
|
|
9
11
|
{ authority: 'theme', scope: 'global' },
|
|
10
|
-
{ authority: 'theme', scope: 'component' },
|
|
11
|
-
{ authority: 'theme', scope: 'pattern' },
|
|
12
|
+
{ authority: 'theme', scope: 'component', allowInstanceOverride: true },
|
|
13
|
+
{ authority: 'theme', scope: 'pattern', allowInstanceOverride: true },
|
|
12
14
|
];
|
|
13
|
-
|
|
14
|
-
expect(values).toEqual([
|
|
15
|
-
{ authority: 'instance' },
|
|
16
|
-
{ authority: 'theme', scope: 'global' },
|
|
17
|
-
{ authority: 'theme', scope: 'component' },
|
|
18
|
-
{ authority: 'theme', scope: 'pattern' },
|
|
19
|
-
]);
|
|
15
|
+
expect(values).toHaveLength(4);
|
|
20
16
|
});
|
|
21
17
|
|
|
22
|
-
test('
|
|
23
|
-
expect(ZORA_COMPONENT_META.Button.props.children?.authoring).toEqual({
|
|
24
|
-
authority: 'instance',
|
|
25
|
-
});
|
|
18
|
+
test('keeps representative content and semantics instance-owned', () => {
|
|
19
|
+
expect(ZORA_COMPONENT_META.Button.props.children?.authoring).toEqual({ authority: 'instance' });
|
|
26
20
|
expect(ZORA_COMPONENT_META.Card.props.title?.authoring).toEqual({ authority: 'instance' });
|
|
27
|
-
expect(ZORA_COMPONENT_META.Heading.props.text?.authoring).toEqual({ authority: 'instance' });
|
|
28
21
|
expect(ZORA_COMPONENT_META.Heading.props.level?.authoring).toEqual({ authority: 'instance' });
|
|
29
22
|
expect(ZORA_COMPONENT_META.Text.props.text?.authoring).toEqual({ authority: 'instance' });
|
|
30
|
-
expect(ZORA_COMPONENT_META.Panel.props.description?.authoring).toEqual({
|
|
31
|
-
authority: 'instance',
|
|
32
|
-
});
|
|
33
23
|
});
|
|
34
24
|
|
|
35
|
-
test('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
expect(ZORA_COMPONENT_META.Heading.props.size?.authoring).toEqual({
|
|
45
|
-
authority: 'theme',
|
|
46
|
-
scope: 'component',
|
|
47
|
-
});
|
|
48
|
-
expect(ZORA_COMPONENT_META.Text.props.variant?.authoring).toEqual({
|
|
49
|
-
authority: 'theme',
|
|
50
|
-
scope: 'component',
|
|
51
|
-
});
|
|
52
|
-
expect(ZORA_COMPONENT_META.Panel.props.compact?.authoring).toEqual({
|
|
53
|
-
authority: 'theme',
|
|
54
|
-
scope: 'pattern',
|
|
55
|
-
});
|
|
25
|
+
test('marks recipe props theme-owned while allowing explicit instance overrides', () => {
|
|
26
|
+
for (const name of ['Button', 'Card', 'Heading', 'Text', 'Panel']) {
|
|
27
|
+
const meta = componentMetaByName.get(name);
|
|
28
|
+
expect(meta, name).toBeDefined();
|
|
29
|
+
for (const prop of Object.values(meta?.props ?? {})) {
|
|
30
|
+
if (prop.authoring?.authority !== 'theme') continue;
|
|
31
|
+
expect(prop.authoring.allowInstanceOverride, name).toBe(true);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
56
34
|
});
|
|
57
35
|
|
|
58
|
-
test('keeps Heading blueprint content useful
|
|
36
|
+
test('keeps Heading blueprint semantic content useful', () => {
|
|
59
37
|
expect(ZORA_COMPONENT_META.Heading.blueprint?.defaultProps).toEqual({
|
|
60
38
|
text: 'Heading',
|
|
61
39
|
level: 2,
|
|
62
40
|
});
|
|
63
41
|
});
|
|
64
42
|
|
|
65
|
-
test('treats absent authoring metadata as not authorable', () => {
|
|
66
|
-
expect(ZORA_COMPONENT_META.Progress.props.value?.authoring).toBeUndefined();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
43
|
test('does not introduce a system authority', () => {
|
|
70
44
|
expect(JSON.stringify(ZORA_COMPONENT_META)).not.toContain('"authority":"system"');
|
|
71
45
|
});
|
|
@@ -28,6 +28,7 @@ describe('ZORA_COMPONENT_META registry coverage', () => {
|
|
|
28
28
|
.map((item) => item.split(' as ')[0].trim())
|
|
29
29
|
.filter((name) => /^[A-Z][A-Za-z0-9]+$/.test(name))
|
|
30
30
|
.filter((name) => !['ToastProvider'].includes(name));
|
|
31
|
+
componentExports.push('ThemeModeToggle');
|
|
31
32
|
|
|
32
33
|
for (const name of componentExports) {
|
|
33
34
|
expect(
|
|
@@ -118,6 +119,7 @@ describe('ZORA_COMPONENT_META invariants', () => {
|
|
|
118
119
|
'EmptyState',
|
|
119
120
|
'Hero',
|
|
120
121
|
'Button',
|
|
122
|
+
'ThemeModeToggle',
|
|
121
123
|
'Input',
|
|
122
124
|
'Textarea',
|
|
123
125
|
'Text',
|
|
@@ -170,6 +172,12 @@ describe('ZORA_COMPONENT_META invariants', () => {
|
|
|
170
172
|
}
|
|
171
173
|
});
|
|
172
174
|
|
|
175
|
+
test('ThemeModeToggle is a direct manifest leaf with serializable defaults', () => {
|
|
176
|
+
expect(ZORA_COMPONENT_META.ThemeModeToggle.directManifestNode).toBe(true);
|
|
177
|
+
expect(ZORA_COMPONENT_META.ThemeModeToggle.allowedChildren).toEqual([]);
|
|
178
|
+
expect(ZORA_COMPONENT_META.ThemeModeToggle.blueprint?.defaultProps).toEqual({ size: 'm' });
|
|
179
|
+
});
|
|
180
|
+
|
|
173
181
|
test('Progress is a direct manifest leaf with serializable defaults', () => {
|
|
174
182
|
expect(ZORA_COMPONENT_META.Progress.directManifestNode).toBe(true);
|
|
175
183
|
expect(ZORA_COMPONENT_META.Progress.allowedChildren).toEqual([]);
|
|
@@ -92,6 +92,7 @@ import { timelineMeta } from '../patterns/timeline/meta';
|
|
|
92
92
|
import { treeItemMeta, treeViewMeta } from '../patterns/tree-view/meta';
|
|
93
93
|
import { zoraDrawerContentMeta } from '../patterns/zora-drawer-content/meta';
|
|
94
94
|
import { zoraTabBarMeta } from '../patterns/zora-tab-bar/meta';
|
|
95
|
+
import { themeModeToggleMeta } from '../theme/ThemeModeToggle.meta';
|
|
95
96
|
import type { ZoraComponentMetaRegistry } from './types';
|
|
96
97
|
|
|
97
98
|
export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
|
|
@@ -150,6 +151,7 @@ export const ZORA_COMPONENT_META: ZoraComponentMetaRegistry = {
|
|
|
150
151
|
ToastProvider: toastProviderMeta,
|
|
151
152
|
Toolbar: toolbarMeta,
|
|
152
153
|
ToolbarAction: toolbarActionMeta,
|
|
154
|
+
ThemeModeToggle: themeModeToggleMeta,
|
|
153
155
|
AppShell: appShellMeta,
|
|
154
156
|
Screen: screenMeta,
|
|
155
157
|
ScreenSection: screenSectionMeta,
|
|
@@ -3,65 +3,67 @@ import { describe, expect, test } from 'bun:test';
|
|
|
3
3
|
import { ZORA_COMPONENT_META, ZORA_THEME_RECIPE_META, ZORA_THEME_TOKEN_FAMILIES } from './index';
|
|
4
4
|
|
|
5
5
|
const tokenFamilies = new Set<string>(ZORA_THEME_TOKEN_FAMILIES);
|
|
6
|
-
const
|
|
7
|
-
['Button', 'component'],
|
|
8
|
-
['Card', 'component'],
|
|
9
|
-
['Text', 'component'],
|
|
10
|
-
['Panel', 'pattern'],
|
|
11
|
-
]);
|
|
6
|
+
const recipeMetaByName = new Map(Object.entries(ZORA_THEME_RECIPE_META));
|
|
12
7
|
|
|
13
8
|
describe('ZORA_THEME_RECIPE_META', () => {
|
|
14
|
-
test('exports
|
|
15
|
-
expect(Object.keys(ZORA_THEME_RECIPE_META)).toEqual([
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
test('exports every current component/pattern theme-authority recipe', () => {
|
|
10
|
+
expect(Object.keys(ZORA_THEME_RECIPE_META)).toEqual([
|
|
11
|
+
'Button',
|
|
12
|
+
'Card',
|
|
13
|
+
'Heading',
|
|
14
|
+
'Text',
|
|
15
|
+
'Panel',
|
|
16
|
+
]);
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
for (const [name, componentMeta] of Object.entries(ZORA_COMPONENT_META)) {
|
|
19
|
+
const themeProps = Object.entries(componentMeta.props).filter(
|
|
20
|
+
([, prop]) => prop.authoring?.authority === 'theme' && prop.authoring.scope !== 'global',
|
|
21
|
+
);
|
|
22
|
+
if (themeProps.length === 0) continue;
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
expect(
|
|
26
|
-
expect(
|
|
24
|
+
const recipe = recipeMetaByName.get(name);
|
|
25
|
+
expect(recipe, `${name} has theme props but no theme recipe`).toBeDefined();
|
|
26
|
+
expect(recipe?.kind).toBe(componentMeta.category === 'pattern' ? 'pattern' : 'component');
|
|
27
|
+
const fields = new Map(Object.entries(recipe?.fields ?? {}));
|
|
28
|
+
for (const [propName] of themeProps) {
|
|
29
|
+
expect(
|
|
30
|
+
fields.get(propName),
|
|
31
|
+
`${name}.${propName} is missing from its recipe`,
|
|
32
|
+
).toBeDefined();
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
|
-
|
|
29
|
-
expect(ZORA_COMPONENT_META.Button.category).toBe('component');
|
|
30
|
-
expect(ZORA_COMPONENT_META.Card.category).toBe('component');
|
|
31
|
-
expect(ZORA_COMPONENT_META.Text.category).toBe('component');
|
|
32
|
-
expect(ZORA_COMPONENT_META.Panel.category).toBe('pattern');
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
test('references only canonical token families and valid
|
|
37
|
+
test('references only canonical token families and valid declared defaults', () => {
|
|
36
38
|
for (const meta of Object.values(ZORA_THEME_RECIPE_META)) {
|
|
37
39
|
for (const field of Object.values(meta.fields)) {
|
|
38
40
|
if (field.type === 'token') {
|
|
39
41
|
expect(tokenFamilies.has(field.tokenFamily), `${meta.name}.${field.label}`).toBe(true);
|
|
40
42
|
}
|
|
41
|
-
if (field.type === 'choice') {
|
|
43
|
+
if (field.type === 'choice' && field.default !== undefined) {
|
|
42
44
|
expect(field.options).toContain(field.default);
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
});
|
|
47
49
|
|
|
48
|
-
test('
|
|
49
|
-
expect(ZORA_THEME_RECIPE_META.Button?.fields.size).
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
expect(ZORA_THEME_RECIPE_META.Card?.fields.padding).toMatchObject({
|
|
55
|
-
type: 'token',
|
|
56
|
-
tokenFamily: 'spacing',
|
|
57
|
-
default: 'l',
|
|
58
|
-
});
|
|
50
|
+
test('keeps recipe defaults aligned with existing runtime behavior', () => {
|
|
51
|
+
expect(ZORA_THEME_RECIPE_META.Button?.fields.size?.default).toBe('l');
|
|
52
|
+
expect(ZORA_THEME_RECIPE_META.Card?.fields.padding?.default).toBeUndefined();
|
|
53
|
+
expect(ZORA_THEME_RECIPE_META.Text?.fields.weight?.default).toBeUndefined();
|
|
54
|
+
expect(ZORA_THEME_RECIPE_META.Heading?.fields.size?.default).toBeUndefined();
|
|
59
55
|
});
|
|
60
56
|
|
|
61
|
-
test('
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
test('does not persist theme-owned defaults into new instance blueprints', () => {
|
|
58
|
+
for (const [name, meta] of Object.entries(ZORA_COMPONENT_META)) {
|
|
59
|
+
const props = new Map(Object.entries(meta.props));
|
|
60
|
+
for (const propName of Object.keys(meta.blueprint?.defaultProps ?? {})) {
|
|
61
|
+
expect(
|
|
62
|
+
props.get(propName)?.authoring?.authority,
|
|
63
|
+
`${name}.${propName} blueprint must not pin a theme-owned value`,
|
|
64
|
+
).not.toBe('theme');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
test('is serializable for downstream authoring consumers', () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buttonThemeRecipeMeta } from '../components/button/themeRecipeMeta';
|
|
2
2
|
import { cardThemeRecipeMeta } from '../components/card/themeRecipeMeta';
|
|
3
|
+
import { headingThemeRecipeMeta } from '../components/heading/themeRecipeMeta';
|
|
3
4
|
import { textThemeRecipeMeta } from '../components/text/themeRecipeMeta';
|
|
4
5
|
import { panelThemeRecipeMeta } from '../patterns/panel/themeRecipeMeta';
|
|
5
6
|
import type { ZoraThemeRecipeMetaRegistry } from './themeRecipeTypes';
|
|
@@ -7,6 +8,7 @@ import type { ZoraThemeRecipeMetaRegistry } from './themeRecipeTypes';
|
|
|
7
8
|
export const ZORA_THEME_RECIPE_META: ZoraThemeRecipeMetaRegistry = {
|
|
8
9
|
Button: buttonThemeRecipeMeta,
|
|
9
10
|
Card: cardThemeRecipeMeta,
|
|
11
|
+
Heading: headingThemeRecipeMeta,
|
|
10
12
|
Text: textThemeRecipeMeta,
|
|
11
13
|
Panel: panelThemeRecipeMeta,
|
|
12
14
|
};
|
|
@@ -18,18 +18,18 @@ interface ZoraThemeRecipeFieldBase {
|
|
|
18
18
|
export interface ZoraThemeRecipeTokenFieldMeta extends ZoraThemeRecipeFieldBase {
|
|
19
19
|
readonly type: 'token';
|
|
20
20
|
readonly tokenFamily: ZoraThemeTokenFamily;
|
|
21
|
-
readonly default
|
|
21
|
+
readonly default?: string;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface ZoraThemeRecipeChoiceFieldMeta extends ZoraThemeRecipeFieldBase {
|
|
25
25
|
readonly type: 'choice';
|
|
26
26
|
readonly options: readonly string[];
|
|
27
|
-
readonly default
|
|
27
|
+
readonly default?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface ZoraThemeRecipeBooleanFieldMeta extends ZoraThemeRecipeFieldBase {
|
|
31
31
|
readonly type: 'boolean';
|
|
32
|
-
readonly default
|
|
32
|
+
readonly default?: boolean;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export type ZoraThemeRecipeFieldMeta =
|
package/src/metadata/types.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type ZoraComponentPropAuthoring =
|
|
|
30
30
|
| {
|
|
31
31
|
readonly authority: 'theme';
|
|
32
32
|
readonly scope: 'global' | 'component' | 'pattern';
|
|
33
|
+
readonly allowInstanceOverride?: boolean;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
export interface ZoraComponentPropArrayItemSchema {
|
|
@@ -49,22 +50,15 @@ export interface ZoraComponentPropSchema {
|
|
|
49
50
|
|
|
50
51
|
export interface ZoraComponentBlueprint {
|
|
51
52
|
label: string;
|
|
52
|
-
icon?: {
|
|
53
|
-
name: string;
|
|
54
|
-
provider?: string;
|
|
55
|
-
};
|
|
53
|
+
icon?: { name: string; provider?: string };
|
|
56
54
|
defaultProps?: Readonly<Record<string, ZoraComponentPropValue>>;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
export interface ZoraComponentI18nMeta {
|
|
60
|
-
fields: readonly {
|
|
61
|
-
keyProp: string;
|
|
62
|
-
defaultTextProp: string;
|
|
63
|
-
}[];
|
|
58
|
+
fields: readonly { keyProp: string; defaultTextProp: string }[];
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
export type ZoraComponentEventPayloadKind = ComponentEventDtoKind | (string & {});
|
|
67
|
-
|
|
68
62
|
export type ZoraComponentEventPayloadFieldType =
|
|
69
63
|
| 'boolean'
|
|
70
64
|
| 'number'
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { Card } from '../../components/card';
|
|
4
|
+
import { resolveCardThemeRecipe } from '../../components/card/resolveCardThemeRecipe';
|
|
5
|
+
import { useZoraThemeRecipe } from '../../theme/useZoraThemeRecipe';
|
|
4
6
|
import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
|
|
5
7
|
import type { PanelProps } from './types';
|
|
6
8
|
|
|
7
|
-
function PanelInner({
|
|
8
|
-
|
|
9
|
+
function PanelInner({
|
|
10
|
+
themeId: _themeId,
|
|
11
|
+
mode: _mode,
|
|
12
|
+
tone,
|
|
13
|
+
compact,
|
|
14
|
+
padding,
|
|
15
|
+
radius,
|
|
16
|
+
...props
|
|
17
|
+
}: PanelProps) {
|
|
18
|
+
const themeFields = useZoraThemeRecipe('Panel');
|
|
19
|
+
const recipe = resolveCardThemeRecipe({ tone, compact, padding, radius, themeFields });
|
|
20
|
+
return <Card {...props} {...recipe} />;
|
|
9
21
|
}
|
|
10
22
|
|
|
11
23
|
/***
|
|
12
24
|
* Semantic wrapper around `Card` for panel-style page sections.
|
|
13
|
-
*
|
|
14
|
-
|
|
15
25
|
*/
|
|
16
26
|
export const Panel = withZoraThemeScope(PanelInner);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { ZoraComponentMeta } from '../../metadata';
|
|
2
2
|
import { CONTAINER_ALLOWED_CHILDREN } from '../../metadata/allowedChildren';
|
|
3
3
|
|
|
4
|
+
const themeAuthoring = {
|
|
5
|
+
authority: 'theme',
|
|
6
|
+
scope: 'pattern',
|
|
7
|
+
allowInstanceOverride: true,
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
4
10
|
export const panelMeta = {
|
|
5
11
|
name: 'Panel',
|
|
6
12
|
category: 'pattern',
|
|
7
13
|
directManifestNode: true,
|
|
8
14
|
allowedChildren: [...CONTAINER_ALLOWED_CHILDREN],
|
|
9
|
-
blueprint: {
|
|
10
|
-
label: 'Panel',
|
|
11
|
-
defaultProps: {
|
|
12
|
-
title: 'Panel',
|
|
13
|
-
tone: 'default',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
15
|
+
blueprint: { label: 'Panel', defaultProps: { title: 'Panel' } },
|
|
16
16
|
props: {
|
|
17
17
|
title: {
|
|
18
18
|
type: 'string',
|
|
@@ -37,15 +37,15 @@ export const panelMeta = {
|
|
|
37
37
|
category: 'Style',
|
|
38
38
|
label: 'Tone',
|
|
39
39
|
enum: ['default', 'subtle', 'outline'],
|
|
40
|
-
|
|
41
|
-
authoring: { authority: 'theme', scope: 'pattern' },
|
|
40
|
+
authoring: themeAuthoring,
|
|
42
41
|
},
|
|
43
|
-
compact: {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
compact: { type: 'boolean', category: 'Layout', label: 'Compact', authoring: themeAuthoring },
|
|
43
|
+
padding: { type: 'spacing', category: 'Layout', label: 'Padding', authoring: themeAuthoring },
|
|
44
|
+
radius: {
|
|
45
|
+
type: 'radius',
|
|
46
|
+
category: 'Style',
|
|
47
|
+
label: 'Corner radius',
|
|
48
|
+
authoring: themeAuthoring,
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
} as const satisfies ZoraComponentMeta;
|
package/src/registry.test.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test';
|
|
|
3
3
|
import type { ZoraComponentRegistry } from './index';
|
|
4
4
|
|
|
5
5
|
const NON_RUNTIME_COMPONENT_EXPORTS = new Set(['SelectionProvider', 'ToastProvider']);
|
|
6
|
+
const RUNTIME_THEME_COMPONENT_EXPORTS = ['ThemeModeToggle'] as const;
|
|
6
7
|
const REGISTRY_SNAPSHOT_PREFIX = 'ZORA_REGISTRY_SNAPSHOT:';
|
|
7
8
|
|
|
8
9
|
let registryEntriesPromise: Promise<readonly string[]> | undefined;
|
|
@@ -48,7 +49,7 @@ async function listRuntimeRegistryEntries(): Promise<readonly string[]> {
|
|
|
48
49
|
|
|
49
50
|
async function listPublicConcreteComponentExports(): Promise<readonly string[]> {
|
|
50
51
|
const source = await Bun.file('src/index.ts').text();
|
|
51
|
-
|
|
52
|
+
const componentExports = Array.from(
|
|
52
53
|
source.matchAll(
|
|
53
54
|
/export\s+\{([^}]+)\}\s+from '\.\/(components\/[^']+|foundation|layout\/[^']+|patterns\/[^']+)';/g,
|
|
54
55
|
),
|
|
@@ -56,8 +57,9 @@ async function listPublicConcreteComponentExports(): Promise<readonly string[]>
|
|
|
56
57
|
.flatMap((match) => match[1].split(',').map((item) => item.trim()))
|
|
57
58
|
.map((item) => item.split(' as ')[0].trim())
|
|
58
59
|
.filter((name) => /^[A-Z][A-Za-z0-9]+$/.test(name))
|
|
59
|
-
.filter((name) => !NON_RUNTIME_COMPONENT_EXPORTS.has(name))
|
|
60
|
-
|
|
60
|
+
.filter((name) => !NON_RUNTIME_COMPONENT_EXPORTS.has(name));
|
|
61
|
+
|
|
62
|
+
return [...componentExports, ...RUNTIME_THEME_COMPONENT_EXPORTS].sort();
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
describe('ZORA_COMPONENT_REGISTRY', () => {
|
package/src/registry.ts
CHANGED
|
@@ -97,6 +97,7 @@ import { Timeline } from './patterns/timeline';
|
|
|
97
97
|
import { TreeItem, TreeView } from './patterns/tree-view';
|
|
98
98
|
import { ZoraDrawerContent } from './patterns/zora-drawer-content';
|
|
99
99
|
import { ZoraTabBar } from './patterns/zora-tab-bar';
|
|
100
|
+
import { ThemeModeToggle } from './theme/ThemeModeToggle';
|
|
100
101
|
|
|
101
102
|
export type ZoraComponentRegistry = Readonly<Record<string, React.ElementType>>;
|
|
102
103
|
|
|
@@ -177,6 +178,7 @@ const _ZORA_COMPONENT_REGISTRY = {
|
|
|
177
178
|
Toast,
|
|
178
179
|
Toolbar,
|
|
179
180
|
ToolbarAction,
|
|
181
|
+
ThemeModeToggle,
|
|
180
182
|
Box,
|
|
181
183
|
Center,
|
|
182
184
|
Container,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ZoraComponentMeta } from '../metadata';
|
|
2
|
+
|
|
3
|
+
export const themeModeToggleMeta = {
|
|
4
|
+
name: 'ThemeModeToggle',
|
|
5
|
+
category: 'component',
|
|
6
|
+
directManifestNode: true,
|
|
7
|
+
allowedChildren: [],
|
|
8
|
+
blueprint: {
|
|
9
|
+
label: 'Theme mode toggle',
|
|
10
|
+
defaultProps: {
|
|
11
|
+
size: 'm',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
props: {
|
|
15
|
+
disabled: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
category: 'Behavior',
|
|
18
|
+
label: 'Disabled',
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
type: 'enum',
|
|
23
|
+
category: 'Style',
|
|
24
|
+
label: 'Size',
|
|
25
|
+
enum: ['s', 'm', 'l'],
|
|
26
|
+
default: 'm',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
} as const satisfies ZoraComponentMeta;
|
|
@@ -3,7 +3,12 @@ import { resolveThemeModeToggleState } from './resolveThemeModeToggleState';
|
|
|
3
3
|
import type { ThemeModeToggleProps } from './ThemeModeToggleProps';
|
|
4
4
|
import { useZoraTheme } from './useZoraTheme';
|
|
5
5
|
|
|
6
|
-
export function ThemeModeToggle({
|
|
6
|
+
export function ThemeModeToggle({
|
|
7
|
+
disabled,
|
|
8
|
+
interactionPolicy,
|
|
9
|
+
size = 'm',
|
|
10
|
+
testID,
|
|
11
|
+
}: ThemeModeToggleProps) {
|
|
7
12
|
const { mode, setMode } = useZoraTheme();
|
|
8
13
|
const state = resolveThemeModeToggleState(mode);
|
|
9
14
|
|
|
@@ -12,6 +17,7 @@ export function ThemeModeToggle({ disabled, size = 'm', testID }: ThemeModeToggl
|
|
|
12
17
|
color="neutral"
|
|
13
18
|
disabled={disabled}
|
|
14
19
|
icon={{ name: state.iconName }}
|
|
20
|
+
interactionPolicy={interactionPolicy}
|
|
15
21
|
label={state.label}
|
|
16
22
|
onPress={() => setMode(state.nextMode)}
|
|
17
23
|
size={size}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { IconButtonProps } from '../components/icon-button';
|
|
2
2
|
|
|
3
|
-
export type ThemeModeToggleProps = Pick<
|
|
3
|
+
export type ThemeModeToggleProps = Pick<
|
|
4
|
+
IconButtonProps,
|
|
5
|
+
'disabled' | 'interactionPolicy' | 'size' | 'testID'
|
|
6
|
+
>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ThemeConfig } from '@ankhorage/contracts';
|
|
1
2
|
import { ThemeProvider } from '@ankhorage/surface';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
|
|
@@ -9,30 +10,26 @@ import { ZoraThemeRuntimeContext } from './ZoraThemeRuntimeContext';
|
|
|
9
10
|
export interface ZoraProviderProps {
|
|
10
11
|
children: React.ReactNode;
|
|
11
12
|
theme?: ZoraTheme;
|
|
13
|
+
themeConfig?: ThemeConfig;
|
|
12
14
|
initialMode?: ZoraThemeMode;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
* Installs the ZORA theme runtime and underlying Surface theme provider.
|
|
17
|
-
*
|
|
18
|
-
* Wrap an app with `ZoraProvider` once near the root so components, patterns,
|
|
19
|
-
* layouts, and theme hooks resolve the same design tokens and color mode.
|
|
20
|
-
*
|
|
21
|
-
* @example App provider
|
|
22
|
-
* ```tsx
|
|
23
|
-
* <ZoraProvider initialMode="light"><App /></ZoraProvider>
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
17
|
+
/** Installs the ZORA theme runtime and underlying Surface theme provider. */
|
|
26
18
|
export function ZoraProvider({
|
|
27
19
|
children,
|
|
28
20
|
theme = zoraDefaultTheme,
|
|
21
|
+
themeConfig,
|
|
29
22
|
initialMode = 'light',
|
|
30
23
|
}: ZoraProviderProps) {
|
|
31
|
-
const
|
|
24
|
+
const resolvedConfig = React.useMemo(
|
|
25
|
+
() => themeConfig ?? createZoraThemeConfig(theme),
|
|
26
|
+
[theme, themeConfig],
|
|
27
|
+
);
|
|
28
|
+
const runtimeValue = React.useMemo(() => ({ themeId: resolvedConfig.id }), [resolvedConfig.id]);
|
|
32
29
|
|
|
33
30
|
return (
|
|
34
31
|
<ZoraThemeRuntimeContext.Provider value={runtimeValue}>
|
|
35
|
-
<ThemeProvider initialConfig={
|
|
32
|
+
<ThemeProvider initialConfig={resolvedConfig} initialMode={initialMode}>
|
|
36
33
|
{children}
|
|
37
34
|
</ThemeProvider>
|
|
38
35
|
</ZoraThemeRuntimeContext.Provider>
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { createContext, useContext } from 'react';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { ZoraThemeId } from './types';
|
|
4
4
|
import { zoraDefaultTheme } from './zoraDefaultTheme';
|
|
5
5
|
|
|
6
6
|
interface ZoraThemeRuntime {
|
|
7
|
-
sourceTheme: ZoraTheme;
|
|
8
7
|
themeId: ZoraThemeId;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export const ZoraThemeRuntimeContext = createContext<ZoraThemeRuntime>({
|
|
12
|
-
sourceTheme: zoraDefaultTheme,
|
|
13
11
|
themeId: zoraDefaultTheme.id,
|
|
14
12
|
});
|
|
15
13
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createTheme, ThemeContext, useFontContext, useTheme } from '@ankhorage/surface';
|
|
2
2
|
import React, { useMemo } from 'react';
|
|
3
3
|
|
|
4
|
-
import { createZoraThemeConfig } from './createZoraThemeConfig';
|
|
5
4
|
import { resolveZoraScopedThemeId } from './resolveZoraScopedThemeId';
|
|
6
5
|
import type { ZoraThemeId, ZoraThemeMode } from './types';
|
|
7
6
|
import { useZoraThemeRuntime, ZoraThemeRuntimeContext } from './ZoraThemeRuntimeContext';
|
|
@@ -16,25 +15,16 @@ function ZoraThemeScopeInner({ children, themeId, mode }: ZoraThemeScopeProps) {
|
|
|
16
15
|
const parentSurface = useTheme();
|
|
17
16
|
const parentRuntime = useZoraThemeRuntime();
|
|
18
17
|
const { activeFontId } = useFontContext();
|
|
19
|
-
|
|
20
18
|
const scopedThemeId = resolveZoraScopedThemeId({
|
|
21
19
|
desiredThemeId: themeId,
|
|
22
20
|
inheritedThemeId: parentRuntime.themeId,
|
|
23
21
|
});
|
|
24
|
-
|
|
25
22
|
const scopedMode = mode ?? parentSurface.mode;
|
|
26
|
-
|
|
27
|
-
// Plan 2: there is no multi-theme registry yet. Keep the active theme seed inherited.
|
|
28
|
-
const surfaceConfig = useMemo(
|
|
29
|
-
() => createZoraThemeConfig(parentRuntime.sourceTheme),
|
|
30
|
-
[parentRuntime.sourceTheme],
|
|
31
|
-
);
|
|
32
|
-
|
|
23
|
+
const sourceConfig = parentSurface.theme.config;
|
|
33
24
|
const scopedTheme = useMemo(
|
|
34
|
-
() => createTheme(
|
|
35
|
-
[
|
|
25
|
+
() => createTheme(sourceConfig, scopedMode, activeFontId),
|
|
26
|
+
[sourceConfig, scopedMode, activeFontId],
|
|
36
27
|
);
|
|
37
|
-
|
|
38
28
|
const scopedSurfaceValue = useMemo(
|
|
39
29
|
() => ({
|
|
40
30
|
theme: scopedTheme,
|
|
@@ -45,14 +35,7 @@ function ZoraThemeScopeInner({ children, themeId, mode }: ZoraThemeScopeProps) {
|
|
|
45
35
|
}),
|
|
46
36
|
[parentSurface.setMode, parentSurface.setThemeConfig, scopedMode, scopedTheme],
|
|
47
37
|
);
|
|
48
|
-
|
|
49
|
-
const scopedRuntimeValue = useMemo(
|
|
50
|
-
() => ({
|
|
51
|
-
sourceTheme: parentRuntime.sourceTheme,
|
|
52
|
-
themeId: scopedThemeId,
|
|
53
|
-
}),
|
|
54
|
-
[parentRuntime.sourceTheme, scopedThemeId],
|
|
55
|
-
);
|
|
38
|
+
const scopedRuntimeValue = useMemo(() => ({ themeId: scopedThemeId }), [scopedThemeId]);
|
|
56
39
|
|
|
57
40
|
return (
|
|
58
41
|
<ZoraThemeRuntimeContext.Provider value={scopedRuntimeValue}>
|
|
@@ -62,10 +45,7 @@ function ZoraThemeScopeInner({ children, themeId, mode }: ZoraThemeScopeProps) {
|
|
|
62
45
|
}
|
|
63
46
|
|
|
64
47
|
export function ZoraThemeScope({ children, themeId, mode }: ZoraThemeScopeProps) {
|
|
65
|
-
if (mode === undefined && themeId === undefined)
|
|
66
|
-
return children;
|
|
67
|
-
}
|
|
68
|
-
|
|
48
|
+
if (mode === undefined && themeId === undefined) return children;
|
|
69
49
|
return (
|
|
70
50
|
<ZoraThemeScopeInner mode={mode} themeId={themeId}>
|
|
71
51
|
{children}
|