@astryxdesign/core 0.6.0 → 0.6.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 +41 -3
- package/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
- package/dist/Center/Center.d.ts +23 -16
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +7 -5
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +12 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +12 -2
- package/dist/Field/Field.d.ts.map +1 -1
- package/dist/Field/Field.js +1 -0
- package/dist/Field/InputClearButton.d.ts +2 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +16 -4
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +12 -1
- package/dist/HoverCard/useHoverCard.js +2 -2
- package/dist/Indicator/CheckboxIndicator.js +2 -2
- package/dist/Indicator/RadioIndicator.js +2 -2
- package/dist/Layer/layerStack.d.ts +10 -0
- package/dist/Layer/layerStack.d.ts.map +1 -1
- package/dist/Layer/layerStack.js +21 -9
- package/dist/Layer/useLayerDismissal.d.ts +2 -3
- package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
- package/dist/Layer/useLayerDismissal.js +2 -3
- package/dist/NavIcon/NavIcon.js +2 -2
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +12 -2
- package/dist/Popover/usePopover.d.ts +3 -2
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +4 -2
- package/dist/ProgressBar/ProgressBar.js +2 -2
- package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
- package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
- package/dist/ScrollableArea/ScrollableArea.js +144 -0
- package/dist/ScrollableArea/index.d.ts +11 -0
- package/dist/ScrollableArea/index.d.ts.map +1 -0
- package/dist/ScrollableArea/index.js +11 -0
- package/dist/StatusDot/StatusDot.js +2 -2
- package/dist/TextArea/TextArea.js +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +19 -4
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +12 -2
- package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +56 -20
- package/dist/astryx.css +15 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scrollGeometry.d.ts +24 -0
- package/dist/hooks/scrollGeometry.d.ts.map +1 -0
- package/dist/hooks/scrollGeometry.js +86 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
- package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
- package/dist/hooks/scrollOwnerRegistry.js +24 -0
- package/dist/hooks/useFocusTrap.d.ts +8 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +22 -11
- package/dist/hooks/useScrollableArea.d.ts +51 -0
- package/dist/hooks/useScrollableArea.d.ts.map +1 -0
- package/dist/hooks/useScrollableArea.js +287 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +2 -6
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +1 -1
- package/dist/theme/derivedVarRegistry.js +1 -1
- package/dist/theme/localTokens.d.ts +8 -11
- package/dist/theme/localTokens.d.ts.map +1 -1
- package/dist/theme/localTokens.js +17 -71
- package/dist/theme/themeAdaptations.d.ts.map +1 -1
- package/dist/theme/themeAdaptations.js +4 -4
- package/dist/utils/themeProps.d.ts +10 -10
- package/dist/utils/themeProps.d.ts.map +1 -1
- package/dist/utils/themeProps.js +27 -10
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +7 -2
- package/src/AppShell/AppShell.test.tsx +36 -0
- package/src/AppShell/AppShell.tsx +4 -1
- package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
- package/src/Banner/Banner.test.tsx +3 -1
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
- package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
- package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
- package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
- package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
- package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
- package/src/Center/Center.doc.mjs +32 -28
- package/src/Center/Center.spec.md +225 -0
- package/src/Center/Center.test.tsx +42 -4
- package/src/Center/Center.tsx +24 -17
- package/src/Chat/ChatSystemMessage.test.tsx +2 -9
- package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/DateInput/DateInput.test.tsx +4 -4
- package/src/DateInput/DateInput.tsx +15 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
- package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
- package/src/DateTimeInput/DateTimeInput.tsx +18 -7
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
- package/src/Field/Field.test.tsx +42 -0
- package/src/Field/Field.tsx +6 -0
- package/src/Field/InputClearButton.test.tsx +35 -1
- package/src/Field/InputClearButton.tsx +7 -3
- package/src/Field/PanelSearchInput.tsx +21 -8
- package/src/FieldStatus/FieldStatus.spec.md +27 -17
- package/src/FieldStatus/FieldStatus.test.tsx +7 -5
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
- package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
- package/src/FileInput/FileInput.tsx +10 -1
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
- package/src/HoverCard/HoverCard.doc.mjs +4 -4
- package/src/HoverCard/useHoverCard.tsx +2 -2
- package/src/Indicator/CheckboxIndicator.tsx +2 -2
- package/src/Indicator/Indicator.doc.mjs +2 -2
- package/src/Indicator/Indicator.test.tsx +1 -1
- package/src/Indicator/RadioIndicator.tsx +2 -2
- package/src/Layer/layerStack.ts +20 -9
- package/src/Layer/useLayerDismissal.ts +2 -3
- package/src/MultiSelector/MultiSelector.test.tsx +4 -4
- package/src/NavIcon/NavIcon.doc.mjs +4 -4
- package/src/NavIcon/NavIcon.tsx +2 -2
- package/src/NumberInput/NumberInput.tsx +18 -7
- package/src/Popover/Popover.doc.mjs +10 -10
- package/src/Popover/Popover.spec.md +55 -65
- package/src/Popover/Popover.test.tsx +29 -0
- package/src/Popover/usePopover.doc.mjs +4 -4
- package/src/Popover/usePopover.tsx +7 -4
- package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
- package/src/ProgressBar/ProgressBar.test.tsx +1 -31
- package/src/ProgressBar/ProgressBar.tsx +2 -2
- package/src/RadioList/RadioList.test.tsx +5 -144
- package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
- package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
- package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
- package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
- package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
- package/src/ScrollableArea/ScrollableArea.tsx +259 -0
- package/src/ScrollableArea/index.ts +26 -0
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
- package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
- package/src/Selector/Selector.test.tsx +4 -4
- package/src/Spinner/Spinner.test.tsx +0 -18
- package/src/StatusDot/StatusDot.doc.mjs +4 -4
- package/src/StatusDot/StatusDot.tsx +2 -2
- package/src/TabList/TabList.test.tsx +5 -9
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
- package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
- package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
- package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
- package/src/Table/Table.doc.mjs +2 -2
- package/src/TextArea/TextArea.doc.mjs +4 -4
- package/src/TextArea/TextArea.tsx +2 -2
- package/src/TextInput/TextInput.doc.mjs +2 -1
- package/src/TextInput/TextInput.test.tsx +94 -0
- package/src/TextInput/TextInput.tsx +22 -6
- package/src/TimeInput/TimeInput.tsx +18 -7
- package/src/Toast/ToastViewport.test.tsx +1 -39
- package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
- package/src/Typeahead/BaseTypeahead.spec.md +269 -0
- package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
- package/src/Typeahead/BaseTypeahead.tsx +99 -30
- package/src/hooks/index.ts +13 -0
- package/src/hooks/scrollGeometry.ts +155 -0
- package/src/hooks/scrollOwnerRegistry.ts +47 -0
- package/src/hooks/useFocusTrap.ts +22 -11
- package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
- package/src/hooks/useScrollableArea.doc.mjs +108 -0
- package/src/hooks/useScrollableArea.test.tsx +437 -0
- package/src/hooks/useScrollableArea.ts +469 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +65 -105
- package/src/theme/defineTheme.ts +3 -9
- package/src/theme/derivedVarRegistry.ts +1 -1
- package/src/theme/localTokens.ts +25 -96
- package/src/theme/publicThemeHelperContract.test.ts +2 -2
- package/src/theme/themeAdaptations.test.ts +16 -42
- package/src/theme/themeAdaptations.ts +6 -9
- package/src/utils/themeProps.test.ts +29 -10
- package/src/utils/themeProps.ts +36 -17
|
@@ -3,26 +3,19 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file localTokens.ts
|
|
5
5
|
* @input Root and adaptation theme-local token declarations and references
|
|
6
|
-
* @output Validated
|
|
7
|
-
* @position Theme-local
|
|
6
|
+
* @output Validated exact-name ownership metadata plus normalized conditional writes
|
|
7
|
+
* @position Theme-local ownership boundary; final adaptation-cycle validation is
|
|
8
8
|
* performed after the reachable ordered cascade is assembled.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const CSS_VAR_PATTERN = /var\(\s*(--[^,\s)]+)/gi;
|
|
15
|
-
|
|
16
|
-
/** Whether a custom-property name belongs to Astryx's reserved local namespace. */
|
|
17
|
-
export function isReservedThemeLocalTokenName(name) {
|
|
18
|
-
return name.startsWith(LOCAL_TOKEN_PREFIX);
|
|
19
|
-
}
|
|
11
|
+
const CSS_NAME_CHARACTER = String.raw`(?:[-_a-zA-Z0-9]|\P{ASCII}|\\(?:[0-9a-fA-F]{1,6}[ \t\r\n\f]?|[^\r\n\f]))`;
|
|
12
|
+
const CSS_CUSTOM_PROPERTY_NAME_PATTERN = new RegExp(`^--${CSS_NAME_CHARACTER}*$`, 'u');
|
|
13
|
+
const CSS_VAR_PATTERN = new RegExp(`var\\(\\s*(--${CSS_NAME_CHARACTER}*)(?=\\s*(?:,|\\)))`, 'giu');
|
|
20
14
|
function hasOwn(object, key) {
|
|
21
15
|
return Object.prototype.hasOwnProperty.call(object, key);
|
|
22
16
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
return name.startsWith(prefix) && LOCAL_TOKEN_SUFFIX_PATTERN.test(name.slice(prefix.length));
|
|
17
|
+
function isValidCSSCustomPropertyName(name) {
|
|
18
|
+
return !name.includes('\0') && CSS_CUSTOM_PROPERTY_NAME_PATTERN.test(name);
|
|
26
19
|
}
|
|
27
20
|
function resolveTokenValue(value, path) {
|
|
28
21
|
if (typeof value === 'string') {
|
|
@@ -53,15 +46,6 @@ function collectCustomPropertyReferences(value, refs) {
|
|
|
53
46
|
}
|
|
54
47
|
}
|
|
55
48
|
}
|
|
56
|
-
function collectLocalReferences(value, refs) {
|
|
57
|
-
const customProperties = new Set();
|
|
58
|
-
collectCustomPropertyReferences(value, customProperties);
|
|
59
|
-
for (const name of customProperties) {
|
|
60
|
-
if (isReservedThemeLocalTokenName(name)) {
|
|
61
|
-
refs.add(name);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
49
|
export function assertNoTokenCycles(tokenValues, context, relevantNames) {
|
|
66
50
|
const dependencies = new Map();
|
|
67
51
|
for (const [name, value] of Object.entries(tokenValues)) {
|
|
@@ -140,18 +124,6 @@ export function assertNoTokenCycles(tokenValues, context, relevantNames) {
|
|
|
140
124
|
}
|
|
141
125
|
}
|
|
142
126
|
}
|
|
143
|
-
function assertDeclaredReferences(localTokens, components, onDark, onLight) {
|
|
144
|
-
const refs = new Set();
|
|
145
|
-
collectLocalReferences(localTokens, refs);
|
|
146
|
-
collectLocalReferences(components, refs);
|
|
147
|
-
collectLocalReferences(onDark?.components, refs);
|
|
148
|
-
collectLocalReferences(onLight?.components, refs);
|
|
149
|
-
for (const reference of refs) {
|
|
150
|
-
if (!hasOwn(localTokens, reference)) {
|
|
151
|
-
throw new Error(`Theme-local token reference "${reference}" has no declaration in the enrolled theme lineage.`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
127
|
function assertInheritedContract(themeName, base) {
|
|
156
128
|
const lineage = base.__localTokenLineage;
|
|
157
129
|
if (!Array.isArray(lineage) || lineage.length === 0 || lineage[lineage.length - 1] !== base.name || lineage.some(name => typeof name !== 'string' || name.length === 0)) {
|
|
@@ -162,7 +134,7 @@ function assertInheritedContract(themeName, base) {
|
|
|
162
134
|
}
|
|
163
135
|
for (const [name, value] of Object.entries(base.localTokens)) {
|
|
164
136
|
const owner = base.__localTokenOwners[name];
|
|
165
|
-
if (typeof value !== 'string' || !owner || !lineage.includes(owner) || !
|
|
137
|
+
if (typeof value !== 'string' || !owner || !lineage.includes(owner) || !isValidCSSCustomPropertyName(name)) {
|
|
166
138
|
throw new Error(`defineTheme("${themeName}"): inherited local token "${name}" does not match its exact lineage metadata.`);
|
|
167
139
|
}
|
|
168
140
|
}
|
|
@@ -177,17 +149,14 @@ function assertInheritedContract(themeName, base) {
|
|
|
177
149
|
* Resolve and validate the opt-in theme-local token contract.
|
|
178
150
|
*
|
|
179
151
|
* Themes that omit `localTokens` and do not extend an enrolled base bypass this
|
|
180
|
-
* function's
|
|
152
|
+
* function's exact owner and lineage checks so legacy token behavior stays intact.
|
|
181
153
|
*/
|
|
182
|
-
export function resolveLocalTokenContract(input, base, tokens,
|
|
154
|
+
export function resolveLocalTokenContract(input, base, tokens, portableTokens) {
|
|
183
155
|
const directlyEnrolled = hasOwn(input, 'localTokens');
|
|
184
156
|
const inherited = base?.__localTokenLineage !== undefined;
|
|
185
157
|
if (!directlyEnrolled && !inherited) {
|
|
186
158
|
return undefined;
|
|
187
159
|
}
|
|
188
|
-
if (directlyEnrolled && !THEME_NAME_PATTERN.test(input.name)) {
|
|
189
|
-
throw new Error(`defineTheme("${input.name}"): themes using localTokens require a stable lower-kebab name.`);
|
|
190
|
-
}
|
|
191
160
|
if (inherited && base) {
|
|
192
161
|
assertInheritedContract(input.name, base);
|
|
193
162
|
}
|
|
@@ -203,11 +172,10 @@ export function resolveLocalTokenContract(input, base, tokens, components, onDar
|
|
|
203
172
|
}
|
|
204
173
|
for (const [name, value] of Object.entries(declarations ?? {})) {
|
|
205
174
|
const inheritedOwner = owners[name];
|
|
175
|
+
if (!isValidCSSCustomPropertyName(name)) {
|
|
176
|
+
throw new Error(`defineTheme("${input.name}"): local token "${name}" must be a valid CSS custom-property name.`);
|
|
177
|
+
}
|
|
206
178
|
if (!inheritedOwner) {
|
|
207
|
-
const expectedPrefix = `${LOCAL_TOKEN_PREFIX}${input.name}-`;
|
|
208
|
-
if (!isExactLocalTokenName(name, input.name)) {
|
|
209
|
-
throw new Error(`defineTheme("${input.name}"): local token "${name}" must use the exact namespace "${expectedPrefix}" followed by a lowercase kebab-case purpose.`);
|
|
210
|
-
}
|
|
211
179
|
owners[name] = input.name;
|
|
212
180
|
}
|
|
213
181
|
localTokens[name] = resolveTokenValue(value, `defineTheme("${input.name}").localTokens["${name}"]`);
|
|
@@ -216,11 +184,10 @@ export function resolveLocalTokenContract(input, base, tokens, components, onDar
|
|
|
216
184
|
if (!owners[name]) {
|
|
217
185
|
throw new Error(`defineTheme("${input.name}"): inherited local token "${name}" has no owner metadata.`);
|
|
218
186
|
}
|
|
219
|
-
if (hasOwn(tokens, name)) {
|
|
187
|
+
if (hasOwn(tokens, name) || hasOwn(portableTokens, name)) {
|
|
220
188
|
throw new Error(`defineTheme("${input.name}"): token "${name}" cannot be declared in both tokens and localTokens.`);
|
|
221
189
|
}
|
|
222
190
|
}
|
|
223
|
-
assertDeclaredReferences(localTokens, components, onDark, onLight);
|
|
224
191
|
assertNoTokenCycles(localTokens, `defineTheme("${input.name}").localTokens`);
|
|
225
192
|
return {
|
|
226
193
|
localTokens,
|
|
@@ -233,20 +200,12 @@ export function resolveLocalTokenContract(input, base, tokens, components, onDar
|
|
|
233
200
|
* Resolve theme-local values written by one adaptation rule.
|
|
234
201
|
*
|
|
235
202
|
* Adaptations may replace names already enrolled by the root theme lineage, but
|
|
236
|
-
* they never enroll names of their own.
|
|
237
|
-
*
|
|
203
|
+
* they never enroll names of their own. Cycle validation waits until all
|
|
204
|
+
* co-matching writes have cascaded in order.
|
|
238
205
|
*/
|
|
239
|
-
export function resolveAdaptationLocalTokens(themeName, ruleIndex, declarations, rootLocalTokens
|
|
206
|
+
export function resolveAdaptationLocalTokens(themeName, ruleIndex, declarations, rootLocalTokens) {
|
|
240
207
|
const path = `defineTheme("${themeName}").adaptations.rules[${ruleIndex}].value.localTokens`;
|
|
241
208
|
if (declarations === undefined) {
|
|
242
|
-
const refs = new Set();
|
|
243
|
-
collectLocalReferences(tokens, refs);
|
|
244
|
-
collectLocalReferences(components, refs);
|
|
245
|
-
for (const reference of refs) {
|
|
246
|
-
if (!rootLocalTokens || !hasOwn(rootLocalTokens, reference)) {
|
|
247
|
-
throw new Error(`${path}: theme-local token reference "${reference}" has no declaration in the enrolled root theme lineage.`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
209
|
return undefined;
|
|
251
210
|
}
|
|
252
211
|
if (declarations === null || typeof declarations !== 'object' || Array.isArray(declarations)) {
|
|
@@ -259,18 +218,5 @@ export function resolveAdaptationLocalTokens(themeName, ruleIndex, declarations,
|
|
|
259
218
|
}
|
|
260
219
|
resolved[name] = resolveTokenValue(value, `${path}["${name}"]`);
|
|
261
220
|
}
|
|
262
|
-
const effective = {
|
|
263
|
-
...rootLocalTokens,
|
|
264
|
-
...resolved
|
|
265
|
-
};
|
|
266
|
-
const refs = new Set();
|
|
267
|
-
collectLocalReferences(resolved, refs);
|
|
268
|
-
collectLocalReferences(tokens, refs);
|
|
269
|
-
collectLocalReferences(components, refs);
|
|
270
|
-
for (const reference of refs) {
|
|
271
|
-
if (!hasOwn(effective, reference)) {
|
|
272
|
-
throw new Error(`${path}: theme-local token reference "${reference}" has no declaration in the enrolled root theme lineage.`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
221
|
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
276
222
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themeAdaptations.d.ts","sourceRoot":"","sources":["../../src/theme/themeAdaptations.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAE,SAAS,EAAE,UAAU,EAAC,MAAM,eAAe,CAAC;AAC5E,OAAO,KAAK,EAAC,gBAAgB,EAAiB,MAAM,SAAS,CAAC;AAC9D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"themeAdaptations.d.ts","sourceRoot":"","sources":["../../src/theme/themeAdaptations.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { resolveThemeValues } from "./resolveThemeValues.js";
|
|
25
|
-
import { assertNoTokenCycles,
|
|
25
|
+
import { assertNoTokenCycles, resolveAdaptationLocalTokens } from "./localTokens.js";
|
|
26
26
|
|
|
27
27
|
// =============================================================================
|
|
28
28
|
// Public authoring vocabulary
|
|
@@ -593,11 +593,11 @@ export function resolveThemeAdaptationRules(themeName, adaptations, axes, rootTo
|
|
|
593
593
|
assertConcreteLeaf(resolved.components, `defineTheme("${themeName}").adaptations.rules[${index}].value.components`);
|
|
594
594
|
}
|
|
595
595
|
for (const name of Object.keys(rule.value.tokens ?? {})) {
|
|
596
|
-
if (
|
|
597
|
-
throw new Error(`defineTheme("${themeName}").adaptations.rules[${index}].value.tokens["${name}"]
|
|
596
|
+
if (rootLocalTokens && Object.prototype.hasOwnProperty.call(rootLocalTokens, name)) {
|
|
597
|
+
throw new Error(`defineTheme("${themeName}").adaptations.rules[${index}].value.tokens["${name}"] matches an enrolled theme-local declaration; write it through value.localTokens instead.`);
|
|
598
598
|
}
|
|
599
599
|
}
|
|
600
|
-
const localTokens = resolveAdaptationLocalTokens(themeName, index, rule.value.localTokens, rootLocalTokens
|
|
600
|
+
const localTokens = resolveAdaptationLocalTokens(themeName, index, rule.value.localTokens, rootLocalTokens);
|
|
601
601
|
return {
|
|
602
602
|
when: rule.when,
|
|
603
603
|
query: mediaQueryForCondition(themeName, index, rule.when, adaptations.widthBreakpoints),
|
|
@@ -17,12 +17,13 @@ export declare function themeDataAttributes(props?: ClassProps): ThemeDataAttrib
|
|
|
17
17
|
* Build the props object components should spread onto the same element that
|
|
18
18
|
* receives the stable Astryx class name.
|
|
19
19
|
*
|
|
20
|
-
* This emits one stable
|
|
21
|
-
* visual props and runtime states.
|
|
20
|
+
* This emits one stable Astryx target, deprecated bare compatibility classes,
|
|
21
|
+
* and canonical data-attribute reflection for visual props and runtime states.
|
|
22
|
+
* For example:
|
|
22
23
|
*
|
|
23
24
|
* ```ts
|
|
24
25
|
* themeProps('button', { variant: 'primary', size: 'sm' })
|
|
25
|
-
* // → { className: 'astryx-button', data-variant: 'primary', data-size: 'sm' }
|
|
26
|
+
* // → { className: 'astryx-button primary sm', data-variant: 'primary', data-size: 'sm' }
|
|
26
27
|
* ```
|
|
27
28
|
*/
|
|
28
29
|
/**
|
|
@@ -30,17 +31,16 @@ export declare function themeDataAttributes(props?: ClassProps): ThemeDataAttrib
|
|
|
30
31
|
*/
|
|
31
32
|
export type ThemePropsOptions = {
|
|
32
33
|
/**
|
|
33
|
-
* Stable
|
|
34
|
-
*
|
|
34
|
+
* Stable target names to emit alongside the canonical target for backwards
|
|
35
|
+
* compatibility.
|
|
35
36
|
*
|
|
36
37
|
* A theme target is public API: renaming one silently breaks every theme
|
|
37
|
-
* that styles it.
|
|
38
|
-
*
|
|
39
|
-
* class on the element until the old name is dropped in a major.
|
|
38
|
+
* that styles it. Keep aliases emitted unless a separate compatibility
|
|
39
|
+
* decision explicitly retires them.
|
|
40
40
|
*
|
|
41
41
|
* Pass plain string literals — the theming guards scan for them statically.
|
|
42
|
-
* Document
|
|
43
|
-
* `theming.targets` so
|
|
42
|
+
* Document each old name with `deprecatedFor` in the component's
|
|
43
|
+
* `theming.targets` so discovery and diagnostics name the replacement.
|
|
44
44
|
*/
|
|
45
45
|
legacyNames?: ReadonlyArray<string>;
|
|
46
46
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themeProps.d.ts","sourceRoot":"","sources":["../../src/utils/themeProps.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;AAC5D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACpD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,GAAG,mBAAmB,CAAC;AAEnE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,MAAM,EAAE,CAErE;
|
|
1
|
+
{"version":3,"file":"themeProps.d.ts","sourceRoot":"","sources":["../../src/utils/themeProps.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;AAC5D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACpD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAC/E,MAAM,MAAM,UAAU,GAAG;IAAC,SAAS,EAAE,MAAM,CAAA;CAAC,GAAG,mBAAmB,CAAC;AAEnE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,MAAM,EAAE,CAErE;AA0CD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,mBAAmB,CAa3E;AAED;;;;;;;;;;;;GAYG;AACH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACrC,CAAC;AAEF,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,UAAU,CAQZ"}
|
package/dist/utils/themeProps.js
CHANGED
|
@@ -4,14 +4,20 @@ import { stableClassName } from "../naming.js";
|
|
|
4
4
|
export function themeDataAttributeName(prop) {
|
|
5
5
|
return `data-${prop.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()}`;
|
|
6
6
|
}
|
|
7
|
+
function legacyClassTokenForPropValue(prop, value) {
|
|
8
|
+
// CSS classes cannot start with a digit, so preserve the released prop prefix.
|
|
9
|
+
return /^\d/.test(value) ? `${prop}-${value}` : value;
|
|
10
|
+
}
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* Build the stable astryx-* class name string for a component.
|
|
10
14
|
*
|
|
11
15
|
* Every component renders one stable target class (`astryx-button`,
|
|
12
|
-
* `astryx-card`, etc.). Visual props and runtime states
|
|
13
|
-
*
|
|
14
|
-
*
|
|
16
|
+
* `astryx-card`, etc.). Visual props and runtime states use explicit data
|
|
17
|
+
* attributes (`data-variant`, `data-size`, `data-selected`, etc.) as their
|
|
18
|
+
* canonical selector surface. Released bare value/state classes remain on the
|
|
19
|
+
* same element as deprecated compatibility aliases until their 0.7.0 removal
|
|
20
|
+
* window.
|
|
15
21
|
*
|
|
16
22
|
* The `astryx-` prefix comes from the centralized naming module
|
|
17
23
|
* (`packages/core/src/naming.ts`) so the namespace lives in one place.
|
|
@@ -20,10 +26,20 @@ export function themeDataAttributeName(prop) {
|
|
|
20
26
|
* <!-- SYNC: packages/core/src/utils/parseStyleKey.ts -->
|
|
21
27
|
*
|
|
22
28
|
* @param component - Component name in lowercase (e.g. 'button', 'card')
|
|
23
|
-
* @
|
|
29
|
+
* @param props - Visual prop values whose released bare classes remain aliases
|
|
30
|
+
* @returns Stable target and deprecated compatibility classes
|
|
24
31
|
*/
|
|
25
|
-
function buildClassName(component) {
|
|
26
|
-
|
|
32
|
+
function buildClassName(component, props) {
|
|
33
|
+
const classes = [stableClassName(component)];
|
|
34
|
+
if (props) {
|
|
35
|
+
for (const [prop, value] of Object.entries(props)) {
|
|
36
|
+
if (value == null) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
classes.push(legacyClassTokenForPropValue(prop, String(value)));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return classes.join(' ');
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
/**
|
|
@@ -50,12 +66,13 @@ export function themeDataAttributes(props) {
|
|
|
50
66
|
* Build the props object components should spread onto the same element that
|
|
51
67
|
* receives the stable Astryx class name.
|
|
52
68
|
*
|
|
53
|
-
* This emits one stable
|
|
54
|
-
* visual props and runtime states.
|
|
69
|
+
* This emits one stable Astryx target, deprecated bare compatibility classes,
|
|
70
|
+
* and canonical data-attribute reflection for visual props and runtime states.
|
|
71
|
+
* For example:
|
|
55
72
|
*
|
|
56
73
|
* ```ts
|
|
57
74
|
* themeProps('button', { variant: 'primary', size: 'sm' })
|
|
58
|
-
* // → { className: 'astryx-button', data-variant: 'primary', data-size: 'sm' }
|
|
75
|
+
* // → { className: 'astryx-button primary sm', data-variant: 'primary', data-size: 'sm' }
|
|
59
76
|
* ```
|
|
60
77
|
*/
|
|
61
78
|
/**
|
|
@@ -63,7 +80,7 @@ export function themeDataAttributes(props) {
|
|
|
63
80
|
*/
|
|
64
81
|
|
|
65
82
|
export function themeProps(component, props, options) {
|
|
66
|
-
const className = buildClassName(component);
|
|
83
|
+
const className = buildClassName(component, props);
|
|
67
84
|
const legacy = options?.legacyNames?.map(name => stableClassName(name)) ?? [];
|
|
68
85
|
return {
|
|
69
86
|
className: legacy.length > 0 ? [className, ...legacy].join(' ') : className,
|
package/locales/en.json
CHANGED
|
@@ -347,6 +347,22 @@
|
|
|
347
347
|
"defaultMessage": "Slide {current, number} of {total, number}",
|
|
348
348
|
"description": "Screen-reader accessible name for one slide in a Carousel, giving its position. `current` is the 1-based slide number; `total` is the slide count."
|
|
349
349
|
},
|
|
350
|
+
"@astryx.chart.label": {
|
|
351
|
+
"defaultMessage": "Chart",
|
|
352
|
+
"description": "Generic screen-reader name for a Chart with no primary series and no consumer-supplied title. Noun (a data visualization), not a verb."
|
|
353
|
+
},
|
|
354
|
+
"@astryx.chart.labelWithSeries": {
|
|
355
|
+
"defaultMessage": "Chart of {series} by {xKey}",
|
|
356
|
+
"description": "Generated screen-reader name for a Chart without a consumer-supplied title. `{series}` is a locale-formatted list of series labels; `{xKey}` is the consumer's horizontal data-field name."
|
|
357
|
+
},
|
|
358
|
+
"@astryx.chart.dataTableCaption": {
|
|
359
|
+
"defaultMessage": "{label} data",
|
|
360
|
+
"description": "Screen-reader-only caption for the small-data table that mirrors a Chart. `{label}` is the Chart's resolved accessible name."
|
|
361
|
+
},
|
|
362
|
+
"@astryx.chartLegend.label": {
|
|
363
|
+
"defaultMessage": "Chart legend",
|
|
364
|
+
"description": "Screen-reader-only accessible name for the list of series labels and swatches rendered by ChartLegend."
|
|
365
|
+
},
|
|
350
366
|
"@astryx.chat.status.sending": {
|
|
351
367
|
"defaultMessage": "Sending",
|
|
352
368
|
"description": "Chat send-status caption under an outgoing message while it is being transmitted. Part of the set sending → sent → delivered → read (or failed) — keep tense/aspect consistent."
|
package/locales/pseudo.json
CHANGED
|
@@ -260,6 +260,18 @@
|
|
|
260
260
|
"@astryx.carousel.slideLabel": {
|
|
261
261
|
"defaultMessage": "⟦Šłíðé {current, number} óƒ {total, number}⟧"
|
|
262
262
|
},
|
|
263
|
+
"@astryx.chart.label": {
|
|
264
|
+
"defaultMessage": "⟦Çĥàřţ⟧"
|
|
265
|
+
},
|
|
266
|
+
"@astryx.chart.labelWithSeries": {
|
|
267
|
+
"defaultMessage": "⟦Çĥàřţ óƒ {series} ƀý {xKey}⟧"
|
|
268
|
+
},
|
|
269
|
+
"@astryx.chart.dataTableCaption": {
|
|
270
|
+
"defaultMessage": "⟦{label} ðàţà⟧"
|
|
271
|
+
},
|
|
272
|
+
"@astryx.chartLegend.label": {
|
|
273
|
+
"defaultMessage": "⟦Çĥàřţ łéĝéñð⟧"
|
|
274
|
+
},
|
|
263
275
|
"@astryx.chat.status.sending": {
|
|
264
276
|
"defaultMessage": "⟦Šéñðíñĝ⟧"
|
|
265
277
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -434,6 +434,11 @@
|
|
|
434
434
|
"types": "./dist/Resizable/index.d.ts",
|
|
435
435
|
"default": "./dist/Resizable/index.js"
|
|
436
436
|
},
|
|
437
|
+
"./ScrollableArea": {
|
|
438
|
+
"source": "./src/ScrollableArea/index.ts",
|
|
439
|
+
"types": "./dist/ScrollableArea/index.d.ts",
|
|
440
|
+
"default": "./dist/ScrollableArea/index.js"
|
|
441
|
+
},
|
|
437
442
|
"./Section": {
|
|
438
443
|
"source": "./src/Section/index.ts",
|
|
439
444
|
"types": "./dist/Section/index.d.ts",
|
|
@@ -676,7 +681,7 @@
|
|
|
676
681
|
"@testing-library/jest-dom": "^6.6.0",
|
|
677
682
|
"@testing-library/react": "^16.3.2",
|
|
678
683
|
"@astryxdesign/a11y-spec": "0.0.0",
|
|
679
|
-
"@astryxdesign/cli": "0.6.
|
|
684
|
+
"@astryxdesign/cli": "0.6.1"
|
|
680
685
|
},
|
|
681
686
|
"dependencies": {
|
|
682
687
|
"intl-messageformat": "^11.2.9"
|
|
@@ -26,6 +26,7 @@ import {InternationalizationProvider} from '../i18n';
|
|
|
26
26
|
import {MobileNav} from '../MobileNav';
|
|
27
27
|
import {SideNav, SideNavItem, SideNavSection} from '../SideNav';
|
|
28
28
|
import {TopNav, TopNavHeading, TopNavItem} from '../TopNav';
|
|
29
|
+
import {TextInput} from '../TextInput';
|
|
29
30
|
import {useAppShellMobile} from './AppShellMobileContext';
|
|
30
31
|
import {Theme, defineTheme} from '../theme';
|
|
31
32
|
|
|
@@ -706,6 +707,41 @@ describe('AppShell', () => {
|
|
|
706
707
|
// Sticky navigation in auto mode
|
|
707
708
|
// ===========================================================================
|
|
708
709
|
|
|
710
|
+
// A Field input wrapper carries a local z-index (it paints above the
|
|
711
|
+
// attached status message box via a negative-margin overlap), so without a
|
|
712
|
+
// component-owned isolation boundary it competes with page-level stacking —
|
|
713
|
+
// a field scrolled underneath the sticky header then paints over it (#5689).
|
|
714
|
+
// The fix is local containment: the Field root isolates the wrapper's local
|
|
715
|
+
// layer, so the header needs no escalated z-index to stay above it. This
|
|
716
|
+
// asserts that ownership boundary — the field's painted surface is an
|
|
717
|
+
// isolated stacking context — not a comparison between two page-level
|
|
718
|
+
// z-index values.
|
|
719
|
+
it('contains Field input stacking locally so the sticky header needs no escalated z-index in auto mode', () => {
|
|
720
|
+
render(
|
|
721
|
+
<AppShell height="auto" topNav={<div>Nav</div>}>
|
|
722
|
+
<TextInput
|
|
723
|
+
label="Name"
|
|
724
|
+
statusVariant="detached"
|
|
725
|
+
value=""
|
|
726
|
+
onChange={() => {}}
|
|
727
|
+
/>
|
|
728
|
+
</AppShell>,
|
|
729
|
+
);
|
|
730
|
+
const header = screen.getByRole('banner');
|
|
731
|
+
const inputWrapper = screen.getByRole('textbox').parentElement!;
|
|
732
|
+
// The detached variant renders the input wrapper outside Field's isolated
|
|
733
|
+
// attached-status wrapper, so the Field root must own the boundary.
|
|
734
|
+
const fieldRoot = inputWrapper.parentElement!;
|
|
735
|
+
expect(inputWrapper).toBeTruthy();
|
|
736
|
+
expect(getComputedStyle(header).position).toBe('sticky');
|
|
737
|
+
// The header stays at its normal local stacking level — no escalation.
|
|
738
|
+
expect(Number(getComputedStyle(header).zIndex)).toBe(1);
|
|
739
|
+
expect(getComputedStyle(inputWrapper).position).toBe('relative');
|
|
740
|
+
// Field's local layers cannot escape into page-level stacking: the root
|
|
741
|
+
// establishes an isolation boundary around the input wrapper's z-index.
|
|
742
|
+
expect(getComputedStyle(fieldRoot).isolation).toBe('isolate');
|
|
743
|
+
});
|
|
744
|
+
|
|
709
745
|
it('wraps header in sticky container in auto mode', () => {
|
|
710
746
|
render(
|
|
711
747
|
<AppShell
|
|
@@ -395,7 +395,10 @@ const styles = stylex.create({
|
|
|
395
395
|
height: spacingVars['--spacing-12'],
|
|
396
396
|
paddingInline: spacingVars['--spacing-2'],
|
|
397
397
|
},
|
|
398
|
-
// Sticky header for auto height mode
|
|
398
|
+
// Sticky header for auto height mode. The header stays at its local
|
|
399
|
+
// stacking level — Field-owned input surfaces contain their own local
|
|
400
|
+
// z-index layers (AST-027), so no escalated page-level value is needed
|
|
401
|
+
// for the header to paint above scrolled content.
|
|
399
402
|
headerSticky: {
|
|
400
403
|
position: 'sticky',
|
|
401
404
|
top: 0,
|
|
@@ -11,7 +11,7 @@ export const docs = {
|
|
|
11
11
|
description:
|
|
12
12
|
'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
|
|
13
13
|
bestPractices: [
|
|
14
|
-
{guidance: true, description: 'Express the ratio as a fraction
|
|
14
|
+
{guidance: true, description: 'Express the ratio as a fraction for readability — `ratio={16 / 9}` rather than `ratio={1.78}`. It is a number, so the string form `ratio="16/9"` is a type error.'},
|
|
15
15
|
{guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
|
|
16
16
|
{guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
|
|
17
17
|
{guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
|
|
@@ -79,7 +79,7 @@ export const docsZh = {
|
|
|
79
79
|
description:
|
|
80
80
|
'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
|
|
81
81
|
bestPractices: [
|
|
82
|
-
{guidance: true, description: 'Express the ratio as a fraction
|
|
82
|
+
{guidance: true, description: 'Express the ratio as a fraction for readability — `ratio={16 / 9}` rather than `ratio={1.78}`. It is a number, so the string form `ratio="16/9"` is a type error.'},
|
|
83
83
|
{guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
|
|
84
84
|
{guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
|
|
85
85
|
{guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
|
|
@@ -130,7 +130,7 @@ export const docsDense = {
|
|
|
130
130
|
description:
|
|
131
131
|
'Maintains a fixed width-to-height ratio for its children as its container resizes. Use it for media containers like videos, images, thumbnails, or any content that needs consistent proportions. It takes its width from the container and derives its height from the ratio, so it needs an ancestor with a definite width.',
|
|
132
132
|
bestPractices: [
|
|
133
|
-
{guidance: true, description: 'Express the ratio as a fraction
|
|
133
|
+
{guidance: true, description: 'Express the ratio as a fraction for readability — `ratio={16 / 9}` rather than `ratio={1.78}`. It is a number, so the string form `ratio="16/9"` is a type error.'},
|
|
134
134
|
{guidance: true, description: 'Use for media that needs consistent proportions across screen sizes.'},
|
|
135
135
|
{guidance: true, description: 'Use `fit="cover"` for images and video so the component sizes the child; the child should not repeat `width`/`height`/`objectFit` styles.'},
|
|
136
136
|
{guidance: true, description: 'Pass one child. With `fit` set, every direct child is stretched to fill the box, so put an overlay or caption inside a single wrapper child rather than passing it as a second child.'},
|
|
@@ -531,7 +531,9 @@ describe('Banner', () => {
|
|
|
531
531
|
);
|
|
532
532
|
const frame = container.firstElementChild;
|
|
533
533
|
expect(frame).toHaveClass('astryx-banner-frame');
|
|
534
|
-
|
|
534
|
+
// The data attributes are canonical; the released bare values remain
|
|
535
|
+
// same-element compatibility classes through the 0.7.0 window.
|
|
536
|
+
expect(frame).toHaveClass(containerType, elevation);
|
|
535
537
|
expect(frame).toHaveAttribute('data-container', containerType);
|
|
536
538
|
expect(frame).toHaveAttribute('data-elevation', elevation);
|
|
537
539
|
expect(frame?.firstElementChild).toHaveClass('astryx-banner');
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
/** @type {import('@astryxdesign/cli/authoring').
|
|
2
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
|
|
3
|
+
|
|
4
|
+
const anatomy = [
|
|
5
|
+
{
|
|
6
|
+
name: 'Shared dialog',
|
|
7
|
+
required: true,
|
|
8
|
+
description:
|
|
9
|
+
'One native dialog that owns modality, focus, dismissal, and lifecycle for the complete flow.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Sheet panels',
|
|
13
|
+
required: true,
|
|
14
|
+
description:
|
|
15
|
+
'Direct BottomSheet children; exactly one is interactive while a previous panel may remain visible and inert during a handoff.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Scrim',
|
|
19
|
+
required: false,
|
|
20
|
+
description:
|
|
21
|
+
'Native dialog backdrop shown by the default scrim-backed modal presentation.',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
3
24
|
|
|
25
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
26
|
export const docs = {
|
|
5
27
|
name: 'BottomSheetSwitcher',
|
|
6
28
|
displayName: 'Bottom Sheet Switcher',
|
|
@@ -122,6 +144,17 @@ export const docs = {
|
|
|
122
144
|
description:
|
|
123
145
|
'Coordinates multiple BottomSheets as a mutually exclusive flow. One activeSheet ID selects the only interactive sheet; during a handoff, the new sheet enters above the inert previous sheet. If the new sheet is shorter, the previous sheet simultaneously moves down until their top edges align, then fades after both transforms complete. The switcher owns one shared native <dialog>: modal flows call showModal() once for one top-layer boundary and one ::backdrop across the whole flow, while no-scrim flows use a non-modal show() shell. Its ref and shared DOM props target that dialog.',
|
|
124
146
|
props: [
|
|
147
|
+
{
|
|
148
|
+
name: 'ref',
|
|
149
|
+
type: 'Ref<HTMLDialogElement>',
|
|
150
|
+
description: 'Ref forwarded to the one shared native dialog.',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'onCancel',
|
|
154
|
+
type: '(event: SyntheticEvent<HTMLDialogElement>) => void',
|
|
155
|
+
description:
|
|
156
|
+
'Called before the switcher handles a native dialog cancel request. Calling preventDefault() keeps the controlled flow open.',
|
|
157
|
+
},
|
|
125
158
|
{
|
|
126
159
|
name: 'activeSheet',
|
|
127
160
|
type: 'string | null',
|
|
@@ -151,6 +184,7 @@ export const docs = {
|
|
|
151
184
|
},
|
|
152
185
|
],
|
|
153
186
|
usage: {
|
|
187
|
+
anatomy,
|
|
154
188
|
description:
|
|
155
189
|
"Coordinates a multi-step bottom-sheet flow in one shared dialog; set activeSheet to a nested BottomSheet's sheetId to open or switch steps, and to null to close.",
|
|
156
190
|
bestPractices: [
|
|
@@ -159,11 +193,21 @@ export const docs = {
|
|
|
159
193
|
description:
|
|
160
194
|
'Use when each step depends on the previous one and only one step needs attention at a time.',
|
|
161
195
|
},
|
|
196
|
+
{
|
|
197
|
+
guidance: true,
|
|
198
|
+
description:
|
|
199
|
+
'Give every child a unique sheetId and non-empty label, choose its purpose to match dismissal requirements, and follow the WAI-ARIA Dialog (Modal) pattern for scrim-backed flows: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/.',
|
|
200
|
+
},
|
|
162
201
|
{
|
|
163
202
|
guidance: false,
|
|
164
203
|
description:
|
|
165
204
|
"Don't split information across sheets when people need to compare it; use a full-page layout that keeps the relevant content visible together instead.",
|
|
166
205
|
},
|
|
206
|
+
{
|
|
207
|
+
guidance: false,
|
|
208
|
+
description:
|
|
209
|
+
"Don't use the switcher when multiple panels must stay interactive or visible together; activeSheet intentionally selects one interactive step.",
|
|
210
|
+
},
|
|
167
211
|
],
|
|
168
212
|
},
|
|
169
213
|
examples: [
|
|
@@ -201,6 +245,7 @@ export const docsDense = {
|
|
|
201
245
|
description:
|
|
202
246
|
'controller with one shared native dialog for mutually exclusive multi-step BottomSheets',
|
|
203
247
|
usage: {
|
|
248
|
+
anatomy,
|
|
204
249
|
description:
|
|
205
250
|
"Coordinates a multi-step bottom-sheet flow in one shared dialog; set activeSheet to a nested BottomSheet's sheetId to open or switch steps, and to null to close.",
|
|
206
251
|
bestPractices: [
|
|
@@ -209,11 +254,21 @@ export const docsDense = {
|
|
|
209
254
|
description:
|
|
210
255
|
'Use when each step depends on the previous one and only one step needs attention at a time.',
|
|
211
256
|
},
|
|
257
|
+
{
|
|
258
|
+
guidance: true,
|
|
259
|
+
description:
|
|
260
|
+
'Give every child a unique sheetId and non-empty label, choose its purpose to match dismissal requirements, and follow the WAI-ARIA Dialog (Modal) pattern for scrim-backed flows: https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/.',
|
|
261
|
+
},
|
|
212
262
|
{
|
|
213
263
|
guidance: false,
|
|
214
264
|
description:
|
|
215
265
|
"Don't split information across sheets when people need to compare it; use a full-page layout that keeps the relevant content visible together instead.",
|
|
216
266
|
},
|
|
267
|
+
{
|
|
268
|
+
guidance: false,
|
|
269
|
+
description:
|
|
270
|
+
"Don't use the switcher when multiple panels must stay interactive or visible together; activeSheet intentionally selects one interactive step.",
|
|
271
|
+
},
|
|
217
272
|
],
|
|
218
273
|
},
|
|
219
274
|
};
|