@ankhorage/zora 4.3.0 → 4.4.1
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/dist/components/card/meta.d.ts +1 -1
- package/dist/components/radio/RadioGroup.d.ts.map +1 -1
- package/dist/components/radio/RadioGroup.js +27 -7
- package/dist/components/radio/RadioGroup.js.map +1 -1
- package/dist/components/radio/meta.d.ts +203 -4
- package/dist/components/radio/meta.d.ts.map +1 -1
- package/dist/components/radio/meta.js +168 -4
- package/dist/components/radio/meta.js.map +1 -1
- package/dist/components/radio/resolveRadioGroupThemeRecipe.d.ts +20 -0
- package/dist/components/radio/resolveRadioGroupThemeRecipe.d.ts.map +1 -0
- package/dist/components/radio/resolveRadioGroupThemeRecipe.js +34 -0
- package/dist/components/radio/resolveRadioGroupThemeRecipe.js.map +1 -0
- package/dist/components/radio/themeRecipeMeta.d.ts +26 -0
- package/dist/components/radio/themeRecipeMeta.d.ts.map +1 -0
- package/dist/components/radio/themeRecipeMeta.js +27 -0
- package/dist/components/radio/themeRecipeMeta.js.map +1 -0
- package/dist/components/radio/types.d.ts +1 -0
- package/dist/components/radio/types.d.ts.map +1 -1
- package/dist/components/radio/types.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/bindableComponentMeta.d.ts +2 -2
- 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/patterns/message-bubble/meta.d.ts +1 -1
- package/dist/patterns/notice/meta.d.ts +1 -1
- package/dist/patterns/panel/meta.d.ts +1 -1
- package/dist/patterns/post-card/meta.d.ts +1 -1
- package/dist/patterns/reader/meta.d.ts +2 -2
- package/dist/patterns/scanner/meta.d.ts +2 -2
- package/package.json +1 -1
- package/src/components/radio/RadioGroup.test.ts +31 -0
- package/src/components/radio/RadioGroup.tsx +45 -8
- package/src/components/radio/meta.ts +170 -4
- package/src/components/radio/resolveRadioGroupThemeRecipe.ts +45 -0
- package/src/components/radio/themeRecipeMeta.ts +28 -0
- package/src/components/radio/types.ts +1 -0
- package/src/metadata/allowedChildren.ts +1 -0
- package/src/metadata/componentMeta.test.ts +21 -0
- package/src/metadata/radioGroupReachability.test.ts +20 -0
- package/src/metadata/themeRecipeMeta.test.ts +4 -0
- package/src/metadata/themeRecipeMeta.ts +2 -0
|
@@ -1,19 +1,185 @@
|
|
|
1
|
+
import { ZORA_COLORS } from '../../internal/colorModel';
|
|
1
2
|
import type { ZoraComponentMeta } from '../../metadata';
|
|
2
3
|
|
|
4
|
+
const themeAuthoring = {
|
|
5
|
+
authority: 'theme',
|
|
6
|
+
scope: 'component',
|
|
7
|
+
allowInstanceOverride: true,
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
3
10
|
export const radioMeta = {
|
|
4
11
|
name: 'Radio',
|
|
5
12
|
category: 'component',
|
|
6
13
|
directManifestNode: false,
|
|
7
14
|
allowedChildren: [],
|
|
8
|
-
note: '
|
|
15
|
+
note: 'Composition-level radio control; author repeated single-choice UI through RadioGroup.',
|
|
9
16
|
props: {},
|
|
10
17
|
} as const satisfies ZoraComponentMeta;
|
|
11
18
|
|
|
12
19
|
export const radioGroupMeta = {
|
|
13
20
|
name: 'RadioGroup',
|
|
14
21
|
category: 'component',
|
|
15
|
-
|
|
22
|
+
description:
|
|
23
|
+
'Single-choice group with inline or card presentation while preserving radio semantics.',
|
|
24
|
+
directManifestNode: true,
|
|
16
25
|
allowedChildren: [],
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
blueprint: {
|
|
27
|
+
label: 'Radio group',
|
|
28
|
+
defaultProps: {
|
|
29
|
+
value: 'option-b',
|
|
30
|
+
options: [
|
|
31
|
+
{ value: 'option-a', label: 'First option', description: 'Supporting detail' },
|
|
32
|
+
{ value: 'option-b', label: 'Second option', description: 'Supporting detail' },
|
|
33
|
+
{ value: 'option-c', label: 'Third option', description: 'Supporting detail' },
|
|
34
|
+
],
|
|
35
|
+
orientation: 'vertical',
|
|
36
|
+
presentation: 'inline',
|
|
37
|
+
invalid: false,
|
|
38
|
+
readOnly: false,
|
|
39
|
+
disabled: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
bindings: {
|
|
43
|
+
props: {
|
|
44
|
+
value: {
|
|
45
|
+
label: 'Value',
|
|
46
|
+
description: 'Currently selected option value.',
|
|
47
|
+
value: { type: 'string' },
|
|
48
|
+
acceptsFallback: true,
|
|
49
|
+
acceptsTransforms: true,
|
|
50
|
+
},
|
|
51
|
+
disabled: {
|
|
52
|
+
label: 'Disabled',
|
|
53
|
+
description: 'Whether the complete radio group is disabled.',
|
|
54
|
+
value: { type: 'boolean' },
|
|
55
|
+
acceptsFallback: true,
|
|
56
|
+
acceptsTransforms: true,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
events: {
|
|
60
|
+
valueChange: {
|
|
61
|
+
label: 'Value change',
|
|
62
|
+
description: 'Runs when the selected radio value changes.',
|
|
63
|
+
payload: {
|
|
64
|
+
eventType: 'radioGroup.valueChange',
|
|
65
|
+
fields: [{ path: 'value', type: 'string', label: 'Value' }],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
events: {
|
|
71
|
+
valueChange: {
|
|
72
|
+
label: 'Value change',
|
|
73
|
+
eventType: 'radioGroup.valueChange',
|
|
74
|
+
description: 'Emitted when the selected radio value changes.',
|
|
75
|
+
payloadFields: [{ path: 'value', type: 'string', label: 'Value' }],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
props: {
|
|
79
|
+
value: {
|
|
80
|
+
type: 'string',
|
|
81
|
+
category: 'State',
|
|
82
|
+
label: 'Value',
|
|
83
|
+
authoring: { authority: 'instance' },
|
|
84
|
+
},
|
|
85
|
+
options: {
|
|
86
|
+
type: 'array',
|
|
87
|
+
category: 'Content',
|
|
88
|
+
label: 'Options',
|
|
89
|
+
itemSchema: [
|
|
90
|
+
{
|
|
91
|
+
key: 'value',
|
|
92
|
+
schema: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
category: 'Content',
|
|
95
|
+
label: 'Value',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: 'label',
|
|
100
|
+
schema: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
category: 'Content',
|
|
103
|
+
label: 'Label',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: 'description',
|
|
108
|
+
schema: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
category: 'Content',
|
|
111
|
+
label: 'Description',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
key: 'disabled',
|
|
116
|
+
schema: {
|
|
117
|
+
type: 'boolean',
|
|
118
|
+
category: 'State',
|
|
119
|
+
label: 'Disabled',
|
|
120
|
+
default: false,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
authoring: { authority: 'instance' },
|
|
125
|
+
},
|
|
126
|
+
orientation: {
|
|
127
|
+
type: 'enum',
|
|
128
|
+
category: 'Layout',
|
|
129
|
+
label: 'Orientation',
|
|
130
|
+
enum: ['horizontal', 'vertical'],
|
|
131
|
+
default: 'vertical',
|
|
132
|
+
authoring: { authority: 'instance' },
|
|
133
|
+
},
|
|
134
|
+
gap: {
|
|
135
|
+
type: 'enum',
|
|
136
|
+
category: 'Layout',
|
|
137
|
+
label: 'Gap',
|
|
138
|
+
enum: ['xs', 's', 'm', 'l'],
|
|
139
|
+
authoring: themeAuthoring,
|
|
140
|
+
},
|
|
141
|
+
presentation: {
|
|
142
|
+
type: 'enum',
|
|
143
|
+
category: 'Style',
|
|
144
|
+
label: 'Presentation',
|
|
145
|
+
enum: ['inline', 'card'],
|
|
146
|
+
default: 'inline',
|
|
147
|
+
authoring: { authority: 'instance' },
|
|
148
|
+
},
|
|
149
|
+
color: {
|
|
150
|
+
type: 'enum',
|
|
151
|
+
category: 'Style',
|
|
152
|
+
label: 'Color',
|
|
153
|
+
enum: [...ZORA_COLORS],
|
|
154
|
+
authoring: themeAuthoring,
|
|
155
|
+
},
|
|
156
|
+
size: {
|
|
157
|
+
type: 'enum',
|
|
158
|
+
category: 'Style',
|
|
159
|
+
label: 'Size',
|
|
160
|
+
enum: ['s', 'm', 'l'],
|
|
161
|
+
authoring: themeAuthoring,
|
|
162
|
+
},
|
|
163
|
+
invalid: {
|
|
164
|
+
type: 'boolean',
|
|
165
|
+
category: 'State',
|
|
166
|
+
label: 'Invalid',
|
|
167
|
+
default: false,
|
|
168
|
+
authoring: { authority: 'instance' },
|
|
169
|
+
},
|
|
170
|
+
readOnly: {
|
|
171
|
+
type: 'boolean',
|
|
172
|
+
category: 'State',
|
|
173
|
+
label: 'Read only',
|
|
174
|
+
default: false,
|
|
175
|
+
authoring: { authority: 'instance' },
|
|
176
|
+
},
|
|
177
|
+
disabled: {
|
|
178
|
+
type: 'boolean',
|
|
179
|
+
category: 'State',
|
|
180
|
+
label: 'Disabled',
|
|
181
|
+
default: false,
|
|
182
|
+
authoring: { authority: 'instance' },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
19
185
|
} as const satisfies ZoraComponentMeta;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ThemeRecipeOverrideValue } from '@ankhorage/contracts';
|
|
2
|
+
|
|
3
|
+
import type { ZoraColor } from '../../internal/colorModel';
|
|
4
|
+
import type { RadioGroupProps } from './types';
|
|
5
|
+
|
|
6
|
+
type RadioGroupGap = NonNullable<RadioGroupProps<string>['gap']>;
|
|
7
|
+
type RadioGroupSize = NonNullable<RadioGroupProps<string>['size']>;
|
|
8
|
+
|
|
9
|
+
/***
|
|
10
|
+
* Resolves RadioGroup presentation props from instance overrides and the active theme recipe.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveRadioGroupThemeRecipe(input: {
|
|
13
|
+
readonly gap?: RadioGroupGap;
|
|
14
|
+
readonly color?: ZoraColor;
|
|
15
|
+
readonly size?: RadioGroupSize;
|
|
16
|
+
readonly themeFields: Readonly<Record<string, ThemeRecipeOverrideValue>>;
|
|
17
|
+
}) {
|
|
18
|
+
return {
|
|
19
|
+
gap: input.gap ?? readGap(input.themeFields.gap) ?? 's',
|
|
20
|
+
color: input.color ?? readColor(input.themeFields.color) ?? 'primary',
|
|
21
|
+
size: input.size ?? readSize(input.themeFields.size) ?? 'm',
|
|
22
|
+
} as const;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*** Reads a valid RadioGroup gap from a theme recipe value. */
|
|
26
|
+
function readGap(value: ThemeRecipeOverrideValue | undefined): RadioGroupGap | undefined {
|
|
27
|
+
if (value === 'xs' || value === 's' || value === 'm' || value === 'l') return value;
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*** Reads a valid ZORA color from a theme recipe value. */
|
|
32
|
+
function readColor(value: ThemeRecipeOverrideValue | undefined): ZoraColor | undefined {
|
|
33
|
+
if (value === 'primary' || value === 'secondary' || value === 'tertiary') return value;
|
|
34
|
+
if (value === 'quaternary' || value === 'neutral' || value === 'danger') return value;
|
|
35
|
+
if (value === 'success' || value === 'warning' || value === 'error' || value === 'info') {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*** Reads a valid RadioGroup control size from a theme recipe value. */
|
|
42
|
+
function readSize(value: ThemeRecipeOverrideValue | undefined): RadioGroupSize | undefined {
|
|
43
|
+
if (value === 's' || value === 'm' || value === 'l') return value;
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ZORA_COLORS } from '../../internal/colorModel';
|
|
2
|
+
import type { ZoraThemeRecipeMeta } from '../../metadata/themeRecipeTypes';
|
|
3
|
+
|
|
4
|
+
export const radioGroupThemeRecipeMeta = {
|
|
5
|
+
name: 'RadioGroup',
|
|
6
|
+
kind: 'component',
|
|
7
|
+
description: 'Maps shared selection-control presentation choices into RadioGroup defaults.',
|
|
8
|
+
fields: {
|
|
9
|
+
gap: {
|
|
10
|
+
type: 'choice',
|
|
11
|
+
label: 'Gap',
|
|
12
|
+
options: ['xs', 's', 'm', 'l'],
|
|
13
|
+
default: 's',
|
|
14
|
+
},
|
|
15
|
+
color: {
|
|
16
|
+
type: 'choice',
|
|
17
|
+
label: 'Color',
|
|
18
|
+
options: ZORA_COLORS,
|
|
19
|
+
default: 'primary',
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
type: 'choice',
|
|
23
|
+
label: 'Size',
|
|
24
|
+
options: ['s', 'm', 'l'],
|
|
25
|
+
default: 'm',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
} as const satisfies ZoraThemeRecipeMeta;
|
|
@@ -126,6 +126,7 @@ describe('ZORA_COMPONENT_META invariants', () => {
|
|
|
126
126
|
'EmptyState',
|
|
127
127
|
'Hero',
|
|
128
128
|
'Button',
|
|
129
|
+
'RadioGroup',
|
|
129
130
|
'ThemeModeToggle',
|
|
130
131
|
'Input',
|
|
131
132
|
'Textarea',
|
|
@@ -198,6 +199,26 @@ describe('ZORA_COMPONENT_META invariants', () => {
|
|
|
198
199
|
expect(ZORA_COMPONENT_META.Image.props.alt?.authoring).toEqual({ authority: 'instance' });
|
|
199
200
|
});
|
|
200
201
|
|
|
202
|
+
test('RadioGroup is a direct manifest leaf with card presentation and normalized value events', () => {
|
|
203
|
+
const radioGroup = ZORA_COMPONENT_META.RadioGroup;
|
|
204
|
+
|
|
205
|
+
expect(radioGroup.directManifestNode).toBe(true);
|
|
206
|
+
expect(radioGroup.allowedChildren).toEqual([]);
|
|
207
|
+
expect(radioGroup.props.presentation?.enum).toEqual(['inline', 'card']);
|
|
208
|
+
expect(radioGroup.props.options?.type).toBe('array');
|
|
209
|
+
expect(radioGroup.props.options?.itemSchema?.map((item) => item.key)).toEqual([
|
|
210
|
+
'value',
|
|
211
|
+
'label',
|
|
212
|
+
'description',
|
|
213
|
+
'disabled',
|
|
214
|
+
]);
|
|
215
|
+
expect(radioGroup.bindings?.props?.value?.value).toEqual({ type: 'string' });
|
|
216
|
+
expect(radioGroup.events?.valueChange?.eventType).toBe('radioGroup.valueChange');
|
|
217
|
+
expect(radioGroup.events?.valueChange?.payloadFields).toEqual([
|
|
218
|
+
{ path: 'value', type: 'string', label: 'Value' },
|
|
219
|
+
]);
|
|
220
|
+
});
|
|
221
|
+
|
|
201
222
|
test('ThemeModeToggle is a direct manifest leaf with serializable defaults', () => {
|
|
202
223
|
expect(ZORA_COMPONENT_META.ThemeModeToggle.directManifestNode).toBe(true);
|
|
203
224
|
expect(ZORA_COMPONENT_META.ThemeModeToggle.allowedChildren).toEqual([]);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { ZORA_COMPONENT_META } from './index';
|
|
4
|
+
|
|
5
|
+
const containers = [
|
|
6
|
+
['Screen', ZORA_COMPONENT_META.Screen],
|
|
7
|
+
['ScreenSection', ZORA_COMPONENT_META.ScreenSection],
|
|
8
|
+
['Box', ZORA_COMPONENT_META.Box],
|
|
9
|
+
['Stack', ZORA_COMPONENT_META.Stack],
|
|
10
|
+
['Card', ZORA_COMPONENT_META.Card],
|
|
11
|
+
['Panel', ZORA_COMPONENT_META.Panel],
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
test('keeps manifest RadioGroup reachable through canonical container composition', () => {
|
|
15
|
+
expect(ZORA_COMPONENT_META.RadioGroup.directManifestNode).toBe(true);
|
|
16
|
+
|
|
17
|
+
for (const [name, metadata] of containers) {
|
|
18
|
+
expect(metadata.allowedChildren, `${name} should allow RadioGroup`).toContain('RadioGroup');
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -11,6 +11,7 @@ describe('ZORA_THEME_RECIPE_META', () => {
|
|
|
11
11
|
'Button',
|
|
12
12
|
'Card',
|
|
13
13
|
'Heading',
|
|
14
|
+
'RadioGroup',
|
|
14
15
|
'Text',
|
|
15
16
|
'Panel',
|
|
16
17
|
]);
|
|
@@ -50,6 +51,9 @@ describe('ZORA_THEME_RECIPE_META', () => {
|
|
|
50
51
|
test('keeps recipe defaults aligned with existing runtime behavior', () => {
|
|
51
52
|
expect(ZORA_THEME_RECIPE_META.Button?.fields.size?.default).toBe('l');
|
|
52
53
|
expect(ZORA_THEME_RECIPE_META.Card?.fields.padding?.default).toBeUndefined();
|
|
54
|
+
expect(ZORA_THEME_RECIPE_META.RadioGroup?.fields.gap?.default).toBe('s');
|
|
55
|
+
expect(ZORA_THEME_RECIPE_META.RadioGroup?.fields.color?.default).toBe('primary');
|
|
56
|
+
expect(ZORA_THEME_RECIPE_META.RadioGroup?.fields.size?.default).toBe('m');
|
|
53
57
|
expect(ZORA_THEME_RECIPE_META.Text?.fields.weight?.default).toBeUndefined();
|
|
54
58
|
expect(ZORA_THEME_RECIPE_META.Heading?.fields.size?.default).toBeUndefined();
|
|
55
59
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buttonThemeRecipeMeta } from '../components/button/themeRecipeMeta';
|
|
2
2
|
import { cardThemeRecipeMeta } from '../components/card/themeRecipeMeta';
|
|
3
3
|
import { headingThemeRecipeMeta } from '../components/heading/themeRecipeMeta';
|
|
4
|
+
import { radioGroupThemeRecipeMeta } from '../components/radio/themeRecipeMeta';
|
|
4
5
|
import { textThemeRecipeMeta } from '../components/text/themeRecipeMeta';
|
|
5
6
|
import { panelThemeRecipeMeta } from '../patterns/panel/themeRecipeMeta';
|
|
6
7
|
import type { ZoraThemeRecipeMetaRegistry } from './themeRecipeTypes';
|
|
@@ -9,6 +10,7 @@ export const ZORA_THEME_RECIPE_META: ZoraThemeRecipeMetaRegistry = {
|
|
|
9
10
|
Button: buttonThemeRecipeMeta,
|
|
10
11
|
Card: cardThemeRecipeMeta,
|
|
11
12
|
Heading: headingThemeRecipeMeta,
|
|
13
|
+
RadioGroup: radioGroupThemeRecipeMeta,
|
|
12
14
|
Text: textThemeRecipeMeta,
|
|
13
15
|
Panel: panelThemeRecipeMeta,
|
|
14
16
|
};
|