@ankhorage/zora 4.2.2 → 4.4.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 +24 -0
- package/README.md +2 -1
- package/dist/components/button/resolveButtonThemeRecipe.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/corePlugin.d.ts +4 -0
- package/dist/corePlugin.d.ts.map +1 -0
- package/dist/corePlugin.js +8 -0
- package/dist/corePlugin.js.map +1 -0
- package/dist/corePluginMetadata.d.ts +3 -0
- package/dist/corePluginMetadata.d.ts.map +1 -0
- package/dist/corePluginMetadata.js +31 -0
- package/dist/corePluginMetadata.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/metadata/index.d.ts +3 -0
- package/dist/metadata/index.d.ts.map +1 -1
- package/dist/metadata/index.js +2 -0
- package/dist/metadata/index.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/types.d.ts +2 -1
- package/dist/metadata/types.d.ts.map +1 -1
- package/dist/metadata/types.js.map +1 -1
- package/dist/pluginComposition.d.ts +45 -0
- package/dist/pluginComposition.d.ts.map +1 -0
- package/dist/pluginComposition.js +153 -0
- package/dist/pluginComposition.js.map +1 -0
- package/package.json +6 -6
- 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/corePlugin.ts +9 -0
- package/src/corePluginMetadata.ts +44 -0
- package/src/index.ts +15 -0
- package/src/metadata/componentMeta.test.ts +21 -0
- package/src/metadata/index.ts +7 -0
- package/src/metadata/themeRecipeMeta.test.ts +4 -0
- package/src/metadata/themeRecipeMeta.ts +2 -0
- package/src/metadata/types.ts +2 -0
- package/src/pluginComposition.test.ts +141 -0
- package/src/pluginComposition.ts +278 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/*** Report one deterministic plugin composition contract violation. */
|
|
2
|
+
export class ZoraPluginCompositionError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
packageName;
|
|
5
|
+
componentName;
|
|
6
|
+
constructor(args) {
|
|
7
|
+
super(args.message);
|
|
8
|
+
this.name = 'ZoraPluginCompositionError';
|
|
9
|
+
this.code = args.code;
|
|
10
|
+
this.packageName = args.packageName;
|
|
11
|
+
this.componentName = args.componentName;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/*** Compose a selected ZORA metadata set for authoring tools that must not load React Native runtime modules. */
|
|
15
|
+
export function composeZoraPluginMetadata(plugins) {
|
|
16
|
+
const descriptors = sortPlugins(plugins);
|
|
17
|
+
const packages = new Set();
|
|
18
|
+
const componentOwners = new Map();
|
|
19
|
+
const componentMeta = new Map();
|
|
20
|
+
const extensionHosts = new Set();
|
|
21
|
+
const interactionPolicySupportedComponents = new Set();
|
|
22
|
+
for (const descriptor of descriptors) {
|
|
23
|
+
validateUniquePackage(descriptor, packages);
|
|
24
|
+
validateMetadata(descriptor);
|
|
25
|
+
for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {
|
|
26
|
+
claimComponent(componentName, descriptor.packageName, componentOwners);
|
|
27
|
+
componentMeta.set(componentName, meta);
|
|
28
|
+
}
|
|
29
|
+
for (const componentName of descriptor.extensionHosts ?? [])
|
|
30
|
+
extensionHosts.add(componentName);
|
|
31
|
+
for (const componentName of descriptor.interactionPolicySupportedComponents ?? []) {
|
|
32
|
+
interactionPolicySupportedComponents.add(componentName);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const descriptor of descriptors) {
|
|
36
|
+
for (const placement of descriptor.placements ?? []) {
|
|
37
|
+
applyPlacement({ descriptor, placement, componentMeta, extensionHosts });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
componentMeta: Object.fromEntries(componentMeta),
|
|
42
|
+
bindableComponentMeta: Object.fromEntries(componentMeta),
|
|
43
|
+
packageManifests: descriptors.map(({ packageName, displayName, componentMeta }) => ({
|
|
44
|
+
packageName,
|
|
45
|
+
...(displayName === undefined ? {} : { displayName }),
|
|
46
|
+
components: componentMeta,
|
|
47
|
+
})),
|
|
48
|
+
interactionPolicySupportedComponents: Object.fromEntries([...interactionPolicySupportedComponents]
|
|
49
|
+
.sort()
|
|
50
|
+
.map((componentName) => [componentName, true])),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/*** Compose a selected ZORA core/plugin descriptor set into one validated authoring/runtime catalog. */
|
|
54
|
+
export function composeZoraPlugins(plugins) {
|
|
55
|
+
const descriptors = sortPlugins(plugins);
|
|
56
|
+
const componentRegistry = new Map();
|
|
57
|
+
for (const descriptor of descriptors) {
|
|
58
|
+
validateRuntime(descriptor);
|
|
59
|
+
for (const [componentName, component] of sortedEntries(descriptor.componentRegistry)) {
|
|
60
|
+
componentRegistry.set(componentName, component);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const metadataCatalog = composeZoraPluginMetadata(descriptors);
|
|
64
|
+
return { ...metadataCatalog, componentRegistry: Object.fromEntries(componentRegistry) };
|
|
65
|
+
}
|
|
66
|
+
function validateUniquePackage(descriptor, packages) {
|
|
67
|
+
if (packages.has(descriptor.packageName)) {
|
|
68
|
+
throw compositionError('duplicate-package', descriptor.packageName, `Duplicate ZORA plugin package '${descriptor.packageName}'.`);
|
|
69
|
+
}
|
|
70
|
+
packages.add(descriptor.packageName);
|
|
71
|
+
}
|
|
72
|
+
function validateMetadata(descriptor) {
|
|
73
|
+
for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {
|
|
74
|
+
if (meta.name !== componentName) {
|
|
75
|
+
throw compositionError('invalid-component-meta', descriptor.packageName, `ZORA plugin '${descriptor.packageName}' metadata key '${componentName}' does not match meta.name '${meta.name}'.`, componentName);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function validateRuntime(descriptor) {
|
|
80
|
+
const componentMetaNames = new Set(Object.keys(descriptor.componentMeta));
|
|
81
|
+
const runtimeComponentNames = new Set(Object.keys(descriptor.componentRegistry));
|
|
82
|
+
for (const componentName of sortedKeys(descriptor.componentRegistry)) {
|
|
83
|
+
if (!componentMetaNames.has(componentName)) {
|
|
84
|
+
throw compositionError('missing-component-meta', descriptor.packageName, `ZORA plugin '${descriptor.packageName}' registers runtime component '${componentName}' without component metadata.`, componentName);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {
|
|
88
|
+
if (meta.directManifestNode && !runtimeComponentNames.has(componentName)) {
|
|
89
|
+
throw compositionError('missing-runtime-component', descriptor.packageName, `ZORA plugin '${descriptor.packageName}' exposes direct manifest node '${componentName}' without a runtime component.`, componentName);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const componentName of descriptor.interactionPolicySupportedComponents ?? []) {
|
|
93
|
+
if (!runtimeComponentNames.has(componentName)) {
|
|
94
|
+
throw compositionError('invalid-interaction-policy-component', descriptor.packageName, `ZORA plugin '${descriptor.packageName}' declares interaction-policy support for unregistered component '${componentName}'.`, componentName);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function claimComponent(componentName, packageName, owners) {
|
|
99
|
+
const existingOwner = owners.get(componentName);
|
|
100
|
+
if (existingOwner) {
|
|
101
|
+
throw compositionError('duplicate-component', packageName, `ZORA component '${componentName}' is owned by both '${existingOwner}' and '${packageName}'.`, componentName);
|
|
102
|
+
}
|
|
103
|
+
owners.set(componentName, packageName);
|
|
104
|
+
}
|
|
105
|
+
function applyPlacement(args) {
|
|
106
|
+
const { descriptor, placement, componentMeta, extensionHosts } = args;
|
|
107
|
+
const childMeta = descriptor.componentMeta[placement.child];
|
|
108
|
+
if (!childMeta?.directManifestNode) {
|
|
109
|
+
throw compositionError('invalid-placement-child', descriptor.packageName, `ZORA plugin '${descriptor.packageName}' placement child '${placement.child}' is not an owned direct manifest node.`, placement.child);
|
|
110
|
+
}
|
|
111
|
+
for (const parentName of [...placement.parents].sort()) {
|
|
112
|
+
const parentMeta = componentMeta.get(parentName);
|
|
113
|
+
if (!parentMeta || !extensionHosts.has(parentName)) {
|
|
114
|
+
throw compositionError('invalid-placement-parent', descriptor.packageName, `ZORA plugin '${descriptor.packageName}' placement parent '${parentName}' is not a declared extension host.`, placement.child);
|
|
115
|
+
}
|
|
116
|
+
componentMeta.set(parentName, addAllowedChild(parentMeta, placement.child));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function addAllowedChild(meta, child) {
|
|
120
|
+
const allowedChildren = appendUnique(meta.allowedChildren, child);
|
|
121
|
+
const childrenSlot = meta.slots?.children;
|
|
122
|
+
return {
|
|
123
|
+
...meta,
|
|
124
|
+
allowedChildren,
|
|
125
|
+
...(childrenSlot
|
|
126
|
+
? {
|
|
127
|
+
slots: {
|
|
128
|
+
...meta.slots,
|
|
129
|
+
children: {
|
|
130
|
+
...childrenSlot,
|
|
131
|
+
allowedChildren: appendUnique(childrenSlot.allowedChildren ?? [], child),
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
: {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function appendUnique(values, value) {
|
|
139
|
+
return values.includes(value) ? values : [...values, value];
|
|
140
|
+
}
|
|
141
|
+
function sortedKeys(value) {
|
|
142
|
+
return Object.keys(value).sort();
|
|
143
|
+
}
|
|
144
|
+
function sortedEntries(value) {
|
|
145
|
+
return Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
146
|
+
}
|
|
147
|
+
function sortPlugins(plugins) {
|
|
148
|
+
return [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
149
|
+
}
|
|
150
|
+
function compositionError(code, packageName, message, componentName) {
|
|
151
|
+
return new ZoraPluginCompositionError({ code, packageName, message, componentName });
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=pluginComposition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pluginComposition.js","sourceRoot":"","sources":["../src/pluginComposition.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UiComponentMetaRegistry, UiComponentPackageManifest } from '@ankhorage/contracts';\n\nimport type { ZoraComponentMeta, ZoraComponentMetaRegistry } from './metadata/types';\nimport type { ZoraComponentRegistry } from './registry';\n\nexport type ZoraPluginCompositionErrorCode =\n | 'duplicate-component'\n | 'duplicate-package'\n | 'invalid-component-meta'\n | 'invalid-interaction-policy-component'\n | 'invalid-placement-child'\n | 'invalid-placement-parent'\n | 'missing-component-meta'\n | 'missing-runtime-component';\n\nexport interface ZoraPluginPlacement {\n readonly child: string;\n readonly parents: readonly string[];\n}\n\nexport interface ZoraPluginMetadata {\n readonly packageName: string;\n readonly displayName?: string;\n readonly componentMeta: ZoraComponentMetaRegistry;\n readonly extensionHosts?: readonly string[];\n readonly placements?: readonly ZoraPluginPlacement[];\n readonly interactionPolicySupportedComponents?: readonly string[];\n}\n\nexport interface ZoraPluginDescriptor extends ZoraPluginMetadata {\n readonly componentRegistry: ZoraComponentRegistry;\n}\n\nexport interface ComposedZoraPluginMetadataCatalog {\n readonly componentMeta: ZoraComponentMetaRegistry;\n readonly bindableComponentMeta: UiComponentMetaRegistry;\n readonly packageManifests: readonly UiComponentPackageManifest[];\n readonly interactionPolicySupportedComponents: Readonly<Record<string, true>>;\n}\n\nexport interface ComposedZoraPluginCatalog extends ComposedZoraPluginMetadataCatalog {\n readonly componentRegistry: ZoraComponentRegistry;\n}\n\n/*** Report one deterministic plugin composition contract violation. */\nexport class ZoraPluginCompositionError extends Error {\n readonly code: ZoraPluginCompositionErrorCode;\n readonly packageName: string;\n readonly componentName?: string;\n\n constructor(args: {\n readonly code: ZoraPluginCompositionErrorCode;\n readonly packageName: string;\n readonly message: string;\n readonly componentName?: string;\n }) {\n super(args.message);\n this.name = 'ZoraPluginCompositionError';\n this.code = args.code;\n this.packageName = args.packageName;\n this.componentName = args.componentName;\n }\n}\n\n/*** Compose a selected ZORA metadata set for authoring tools that must not load React Native runtime modules. */\nexport function composeZoraPluginMetadata(\n plugins: readonly ZoraPluginMetadata[],\n): ComposedZoraPluginMetadataCatalog {\n const descriptors = sortPlugins(plugins);\n const packages = new Set<string>();\n const componentOwners = new Map<string, string>();\n const componentMeta = new Map<string, ZoraComponentMeta>();\n const extensionHosts = new Set<string>();\n const interactionPolicySupportedComponents = new Set<string>();\n\n for (const descriptor of descriptors) {\n validateUniquePackage(descriptor, packages);\n validateMetadata(descriptor);\n for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {\n claimComponent(componentName, descriptor.packageName, componentOwners);\n componentMeta.set(componentName, meta);\n }\n for (const componentName of descriptor.extensionHosts ?? []) extensionHosts.add(componentName);\n for (const componentName of descriptor.interactionPolicySupportedComponents ?? []) {\n interactionPolicySupportedComponents.add(componentName);\n }\n }\n\n for (const descriptor of descriptors) {\n for (const placement of descriptor.placements ?? []) {\n applyPlacement({ descriptor, placement, componentMeta, extensionHosts });\n }\n }\n\n return {\n componentMeta: Object.fromEntries(componentMeta),\n bindableComponentMeta: Object.fromEntries(componentMeta),\n packageManifests: descriptors.map(({ packageName, displayName, componentMeta }) => ({\n packageName,\n ...(displayName === undefined ? {} : { displayName }),\n components: componentMeta,\n })),\n interactionPolicySupportedComponents: Object.fromEntries(\n [...interactionPolicySupportedComponents]\n .sort()\n .map((componentName) => [componentName, true]),\n ),\n };\n}\n\n/*** Compose a selected ZORA core/plugin descriptor set into one validated authoring/runtime catalog. */\nexport function composeZoraPlugins(\n plugins: readonly ZoraPluginDescriptor[],\n): ComposedZoraPluginCatalog {\n const descriptors = sortPlugins(plugins);\n const componentRegistry = new Map<string, ZoraComponentRegistry[string]>();\n\n for (const descriptor of descriptors) {\n validateRuntime(descriptor);\n for (const [componentName, component] of sortedEntries(descriptor.componentRegistry)) {\n componentRegistry.set(componentName, component);\n }\n }\n const metadataCatalog = composeZoraPluginMetadata(descriptors);\n\n return { ...metadataCatalog, componentRegistry: Object.fromEntries(componentRegistry) };\n}\n\nfunction validateUniquePackage(descriptor: ZoraPluginMetadata, packages: Set<string>): void {\n if (packages.has(descriptor.packageName)) {\n throw compositionError(\n 'duplicate-package',\n descriptor.packageName,\n `Duplicate ZORA plugin package '${descriptor.packageName}'.`,\n );\n }\n packages.add(descriptor.packageName);\n}\n\nfunction validateMetadata(descriptor: ZoraPluginMetadata): void {\n for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {\n if (meta.name !== componentName) {\n throw compositionError(\n 'invalid-component-meta',\n descriptor.packageName,\n `ZORA plugin '${descriptor.packageName}' metadata key '${componentName}' does not match meta.name '${meta.name}'.`,\n componentName,\n );\n }\n }\n}\n\nfunction validateRuntime(descriptor: ZoraPluginDescriptor): void {\n const componentMetaNames = new Set(Object.keys(descriptor.componentMeta));\n const runtimeComponentNames = new Set(Object.keys(descriptor.componentRegistry));\n for (const componentName of sortedKeys(descriptor.componentRegistry)) {\n if (!componentMetaNames.has(componentName)) {\n throw compositionError(\n 'missing-component-meta',\n descriptor.packageName,\n `ZORA plugin '${descriptor.packageName}' registers runtime component '${componentName}' without component metadata.`,\n componentName,\n );\n }\n }\n for (const [componentName, meta] of sortedEntries(descriptor.componentMeta)) {\n if (meta.directManifestNode && !runtimeComponentNames.has(componentName)) {\n throw compositionError(\n 'missing-runtime-component',\n descriptor.packageName,\n `ZORA plugin '${descriptor.packageName}' exposes direct manifest node '${componentName}' without a runtime component.`,\n componentName,\n );\n }\n }\n for (const componentName of descriptor.interactionPolicySupportedComponents ?? []) {\n if (!runtimeComponentNames.has(componentName)) {\n throw compositionError(\n 'invalid-interaction-policy-component',\n descriptor.packageName,\n `ZORA plugin '${descriptor.packageName}' declares interaction-policy support for unregistered component '${componentName}'.`,\n componentName,\n );\n }\n }\n}\n\nfunction claimComponent(\n componentName: string,\n packageName: string,\n owners: Map<string, string>,\n): void {\n const existingOwner = owners.get(componentName);\n if (existingOwner) {\n throw compositionError(\n 'duplicate-component',\n packageName,\n `ZORA component '${componentName}' is owned by both '${existingOwner}' and '${packageName}'.`,\n componentName,\n );\n }\n owners.set(componentName, packageName);\n}\n\nfunction applyPlacement(args: {\n descriptor: ZoraPluginMetadata;\n placement: ZoraPluginPlacement;\n componentMeta: Map<string, ZoraComponentMeta>;\n extensionHosts: ReadonlySet<string>;\n}): void {\n const { descriptor, placement, componentMeta, extensionHosts } = args;\n const childMeta = descriptor.componentMeta[placement.child];\n if (!childMeta?.directManifestNode) {\n throw compositionError(\n 'invalid-placement-child',\n descriptor.packageName,\n `ZORA plugin '${descriptor.packageName}' placement child '${placement.child}' is not an owned direct manifest node.`,\n placement.child,\n );\n }\n for (const parentName of [...placement.parents].sort()) {\n const parentMeta = componentMeta.get(parentName);\n if (!parentMeta || !extensionHosts.has(parentName)) {\n throw compositionError(\n 'invalid-placement-parent',\n descriptor.packageName,\n `ZORA plugin '${descriptor.packageName}' placement parent '${parentName}' is not a declared extension host.`,\n placement.child,\n );\n }\n componentMeta.set(parentName, addAllowedChild(parentMeta, placement.child));\n }\n}\n\nfunction addAllowedChild(meta: ZoraComponentMeta, child: string): ZoraComponentMeta {\n const allowedChildren = appendUnique(meta.allowedChildren, child);\n const childrenSlot = meta.slots?.children;\n return {\n ...meta,\n allowedChildren,\n ...(childrenSlot\n ? {\n slots: {\n ...meta.slots,\n children: {\n ...childrenSlot,\n allowedChildren: appendUnique(childrenSlot.allowedChildren ?? [], child),\n },\n },\n }\n : {}),\n };\n}\n\nfunction appendUnique(values: readonly string[], value: string): readonly string[] {\n return values.includes(value) ? values : [...values, value];\n}\n\nfunction sortedKeys(value: Readonly<Record<string, unknown>>): string[] {\n return Object.keys(value).sort();\n}\n\nfunction sortedEntries<T>(value: Readonly<Record<string, T>>): [string, T][] {\n return Object.entries(value).sort(([left], [right]) => left.localeCompare(right));\n}\n\nfunction sortPlugins<T extends ZoraPluginMetadata>(plugins: readonly T[]): T[] {\n return [...plugins].sort((left, right) => left.packageName.localeCompare(right.packageName));\n}\n\nfunction compositionError(\n code: ZoraPluginCompositionErrorCode,\n packageName: string,\n message: string,\n componentName?: string,\n): ZoraPluginCompositionError {\n return new ZoraPluginCompositionError({ code, packageName, message, componentName });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/zora",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.4.0",
|
|
5
5
|
"description": "Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/zora#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@ankhorage/color-theory": "^0.3.0",
|
|
41
|
-
"@ankhorage/contracts": "^
|
|
42
|
-
"@ankhorage/surface": "^3.4.
|
|
41
|
+
"@ankhorage/contracts": "^12.0.1",
|
|
42
|
+
"@ankhorage/surface": "^3.4.4"
|
|
43
43
|
},
|
|
44
44
|
"files": [
|
|
45
45
|
"dist",
|
|
@@ -95,14 +95,14 @@
|
|
|
95
95
|
"version-packages": "ankhorage-changeset version"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
-
"@ankhorage/devtools": "^1.
|
|
98
|
+
"@ankhorage/devtools": "^1.13.0",
|
|
99
99
|
"@ankhorage/paradox": "^0.1.24",
|
|
100
100
|
"@react-native-picker/picker": "2.11.4",
|
|
101
101
|
"@react-native-vector-icons/fontawesome": "^13.1.3",
|
|
102
102
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
103
103
|
"@react-native-vector-icons/fontawesome6": "^13.1.3",
|
|
104
104
|
"@react-native-vector-icons/ionicons": "^13.1.3",
|
|
105
|
-
"@types/bun": "^1.4.
|
|
105
|
+
"@types/bun": "^1.4.1",
|
|
106
106
|
"@types/node": "^24.13.3",
|
|
107
107
|
"@types/react": "^19.2.18",
|
|
108
108
|
"@types/react-dom": "^19.2.4",
|
|
@@ -116,5 +116,5 @@
|
|
|
116
116
|
"react-native-web": "~0.21.0",
|
|
117
117
|
"typescript": "~6.0.3"
|
|
118
118
|
},
|
|
119
|
-
"packageManager": "bun@1.4.
|
|
119
|
+
"packageManager": "bun@1.4.2"
|
|
120
120
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { resolveRadioGroupThemeRecipe } from './resolveRadioGroupThemeRecipe';
|
|
4
|
+
|
|
5
|
+
test('resolves RadioGroup theme defaults while preserving instance overrides', () => {
|
|
6
|
+
expect(resolveRadioGroupThemeRecipe({ themeFields: {} })).toEqual({
|
|
7
|
+
gap: 's',
|
|
8
|
+
color: 'primary',
|
|
9
|
+
size: 'm',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(
|
|
13
|
+
resolveRadioGroupThemeRecipe({
|
|
14
|
+
gap: 'l',
|
|
15
|
+
color: 'danger',
|
|
16
|
+
size: 's',
|
|
17
|
+
themeFields: { gap: 'xs', color: 'secondary', size: 'l' },
|
|
18
|
+
}),
|
|
19
|
+
).toEqual({ gap: 'l', color: 'danger', size: 's' });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('card presentation stays on the RadioGroup and Surface Radio semantic path', async () => {
|
|
23
|
+
const source = await Bun.file('src/components/radio/RadioGroup.tsx').text();
|
|
24
|
+
|
|
25
|
+
expect(source).toContain('accessibilityRole="radiogroup"');
|
|
26
|
+
expect(source).toContain('checked={value === option.value}');
|
|
27
|
+
expect(source).toContain('<Radio');
|
|
28
|
+
expect(source).toContain("const isCard = presentation === 'card';");
|
|
29
|
+
expect(source).not.toContain('<Button');
|
|
30
|
+
expect(source).not.toContain('SelectableItem');
|
|
31
|
+
});
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { Radio } from '@ankhorage/surface';
|
|
1
|
+
import { Radio, useTheme } from '@ankhorage/surface';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { View } from 'react-native';
|
|
4
4
|
|
|
5
5
|
import { Stack } from '../../foundation';
|
|
6
|
+
import { useZoraThemeRecipe } from '../../theme/useZoraThemeRecipe';
|
|
6
7
|
import { withZoraThemeScope } from '../../theme/withZoraThemeScope';
|
|
7
8
|
import { Text } from '../text';
|
|
9
|
+
import { resolveRadioGroupThemeRecipe } from './resolveRadioGroupThemeRecipe';
|
|
8
10
|
import type { RadioGroupOption, RadioGroupProps } from './types';
|
|
9
11
|
|
|
12
|
+
/***
|
|
13
|
+
* Resolves the themed radio-group presentation within the active ZORA scope.
|
|
14
|
+
*/
|
|
10
15
|
function RadioGroupInner<TValue extends string>({
|
|
11
16
|
themeId: _themeId,
|
|
12
17
|
mode: _mode,
|
|
@@ -14,9 +19,10 @@ function RadioGroupInner<TValue extends string>({
|
|
|
14
19
|
onValueChange,
|
|
15
20
|
options,
|
|
16
21
|
orientation = 'vertical',
|
|
17
|
-
gap
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
gap,
|
|
23
|
+
presentation = 'inline',
|
|
24
|
+
color,
|
|
25
|
+
size,
|
|
20
26
|
invalid = false,
|
|
21
27
|
readOnly = false,
|
|
22
28
|
disabled = false,
|
|
@@ -24,6 +30,8 @@ function RadioGroupInner<TValue extends string>({
|
|
|
24
30
|
interactionPolicy,
|
|
25
31
|
}: RadioGroupProps<TValue>) {
|
|
26
32
|
const isHorizontal = orientation === 'horizontal';
|
|
33
|
+
const themeFields = useZoraThemeRecipe('RadioGroup');
|
|
34
|
+
const recipe = resolveRadioGroupThemeRecipe({ gap, color, size, themeFields });
|
|
27
35
|
|
|
28
36
|
return (
|
|
29
37
|
<View
|
|
@@ -36,7 +44,7 @@ function RadioGroupInner<TValue extends string>({
|
|
|
36
44
|
>
|
|
37
45
|
<Stack
|
|
38
46
|
direction={isHorizontal ? 'row' : 'column'}
|
|
39
|
-
gap={gap}
|
|
47
|
+
gap={recipe.gap}
|
|
40
48
|
wrap={isHorizontal ? 'wrap' : 'nowrap'}
|
|
41
49
|
>
|
|
42
50
|
{options.map((option) => (
|
|
@@ -47,8 +55,10 @@ function RadioGroupInner<TValue extends string>({
|
|
|
47
55
|
disabled={disabled || option.disabled === true}
|
|
48
56
|
invalid={invalid}
|
|
49
57
|
readOnly={readOnly}
|
|
50
|
-
size={size}
|
|
51
|
-
color={color}
|
|
58
|
+
size={recipe.size}
|
|
59
|
+
color={recipe.color}
|
|
60
|
+
orientation={orientation}
|
|
61
|
+
presentation={presentation}
|
|
52
62
|
onSelect={onValueChange}
|
|
53
63
|
interactionPolicy={interactionPolicy}
|
|
54
64
|
/>
|
|
@@ -63,6 +73,9 @@ function RadioGroupInner<TValue extends string>({
|
|
|
63
73
|
*/
|
|
64
74
|
export const RadioGroup = withZoraThemeScope(RadioGroupInner);
|
|
65
75
|
|
|
76
|
+
/***
|
|
77
|
+
* Renders one option through the single Surface Radio interaction boundary.
|
|
78
|
+
*/
|
|
66
79
|
function RadioGroupItem<TValue extends string>({
|
|
67
80
|
option,
|
|
68
81
|
checked,
|
|
@@ -71,6 +84,8 @@ function RadioGroupItem<TValue extends string>({
|
|
|
71
84
|
readOnly,
|
|
72
85
|
size,
|
|
73
86
|
color,
|
|
87
|
+
orientation,
|
|
88
|
+
presentation,
|
|
74
89
|
onSelect,
|
|
75
90
|
interactionPolicy,
|
|
76
91
|
}: {
|
|
@@ -81,10 +96,14 @@ function RadioGroupItem<TValue extends string>({
|
|
|
81
96
|
readOnly: boolean;
|
|
82
97
|
size: NonNullable<RadioGroupProps<TValue>['size']>;
|
|
83
98
|
color: NonNullable<RadioGroupProps<TValue>['color']>;
|
|
99
|
+
orientation: NonNullable<RadioGroupProps<TValue>['orientation']>;
|
|
100
|
+
presentation: NonNullable<RadioGroupProps<TValue>['presentation']>;
|
|
84
101
|
onSelect: (value: TValue) => void;
|
|
85
102
|
interactionPolicy: RadioGroupProps<TValue>['interactionPolicy'];
|
|
86
103
|
}) {
|
|
104
|
+
const { theme } = useTheme();
|
|
87
105
|
const passive = interactionPolicy === 'passive';
|
|
106
|
+
const isCard = presentation === 'card';
|
|
88
107
|
|
|
89
108
|
return (
|
|
90
109
|
<Radio
|
|
@@ -96,13 +115,31 @@ function RadioGroupItem<TValue extends string>({
|
|
|
96
115
|
size={size}
|
|
97
116
|
color={color}
|
|
98
117
|
testID={option.testID}
|
|
118
|
+
bg={
|
|
119
|
+
isCard
|
|
120
|
+
? checked
|
|
121
|
+
? theme.semantics.selection.background
|
|
122
|
+
: theme.semantics.surface.default
|
|
123
|
+
: undefined
|
|
124
|
+
}
|
|
125
|
+
borderColor={
|
|
126
|
+
isCard
|
|
127
|
+
? checked
|
|
128
|
+
? theme.semantics.selection.border
|
|
129
|
+
: theme.semantics.border.default
|
|
130
|
+
: undefined
|
|
131
|
+
}
|
|
132
|
+
borderWidth={isCard ? 1 : undefined}
|
|
133
|
+
p={isCard ? 'm' : undefined}
|
|
134
|
+
radius={isCard ? 'l' : undefined}
|
|
135
|
+
width={isCard && orientation === 'vertical' ? '100%' : undefined}
|
|
99
136
|
onCheckedChange={(nextChecked) => {
|
|
100
137
|
if (passive) return;
|
|
101
138
|
if (nextChecked) onSelect(option.value);
|
|
102
139
|
}}
|
|
103
140
|
>
|
|
104
141
|
<Stack gap="xs">
|
|
105
|
-
<Text>{option.label}</Text>
|
|
142
|
+
<Text weight={isCard ? 'semiBold' : undefined}>{option.label}</Text>
|
|
106
143
|
{option.description ? (
|
|
107
144
|
<Text emphasis="muted" variant="caption">
|
|
108
145
|
{option.description}
|
|
@@ -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;
|