@astryxdesign/cli 0.5.2-canary.c9c8564 → 0.5.2-canary.f83cae8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/assets/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +1 -1
  2. package/assets/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +2 -1
  3. package/assets/templates/blocks/components/ContextMenu/ContextMenuBottomSheet.doc.mjs +14 -0
  4. package/assets/templates/blocks/components/ContextMenu/ContextMenuBottomSheet.tsx +59 -0
  5. package/assets/templates/blocks/components/ContextMenu/ContextMenuShowcase.doc.mjs +1 -1
  6. package/assets/templates/blocks/components/ContextMenu/ContextMenuShowcase.tsx +2 -1
  7. package/assets/templates/blocks/components/DropdownMenu/DropdownMenuBottomSheet.doc.mjs +15 -0
  8. package/assets/templates/blocks/components/DropdownMenu/DropdownMenuBottomSheet.tsx +49 -0
  9. package/assets/templates/blocks/components/MoreMenu/MoreMenuBottomSheet.doc.mjs +14 -0
  10. package/assets/templates/blocks/components/MoreMenu/MoreMenuBottomSheet.tsx +63 -0
  11. package/assets/templates/blocks/components/MultiSelector/MultiSelectorBottomSheet.doc.mjs +15 -0
  12. package/assets/templates/blocks/components/MultiSelector/MultiSelectorBottomSheet.tsx +26 -0
  13. package/assets/templates/blocks/components/Popover/PopoverBottomSheetAlternative.doc.mjs +22 -0
  14. package/assets/templates/blocks/components/Popover/PopoverBottomSheetAlternative.tsx +71 -0
  15. package/assets/templates/blocks/components/Selector/SelectorBottomSheet.doc.mjs +15 -0
  16. package/assets/templates/blocks/components/Selector/SelectorBottomSheet.tsx +30 -0
  17. package/authoring/doctypes/base/type.ts +91 -1
  18. package/authoring/doctypes/component/component.doc.mjs +7 -1
  19. package/authoring/index.d.ts +7 -0
  20. package/foundation/discovery/__fixtures__/component-accessibility-overlay.doc.d.mts +10 -0
  21. package/foundation/discovery/__fixtures__/component-accessibility-overlay.doc.mjs +14 -0
  22. package/foundation/discovery/component-loader.mjs +12 -5
  23. package/foundation/discovery/componentDocOverlay.test.mjs +6 -0
  24. package/package.json +9 -9
@@ -7,7 +7,7 @@ export const doc = {
7
7
  name: 'ContextMenu — Basic',
8
8
  displayName: 'ContextMenu — Basic',
9
9
  description:
10
- 'A right-click area with action items and a divider separating a destructive action. Use to provide contextual actions for a specific element or region.',
10
+ 'An adaptive context area: long-press opens a BottomSheet on compact touch screens, while right-click opens a cursor-positioned menu elsewhere.',
11
11
  isReady: true,
12
12
  aspectRatio: 16 / 9,
13
13
  componentsUsed: ['ContextMenu'],
@@ -7,6 +7,7 @@ import {ContextMenu} from '@astryxdesign/core/ContextMenu';
7
7
  export default function ContextMenuBasic() {
8
8
  return (
9
9
  <ContextMenu
10
+ presentation="adaptive"
10
11
  items={[
11
12
  {label: 'Cut', onClick: () => {}},
12
13
  {label: 'Copy', onClick: () => {}},
@@ -25,7 +26,7 @@ export default function ContextMenuBasic() {
25
26
  color: '#6b7280',
26
27
  userSelect: 'none',
27
28
  }}>
28
- Right-click this area
29
+ Long-press or right-click this area
29
30
  </div>
30
31
  </ContextMenu>
31
32
  );
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'ContextMenu',
7
+ name: 'ContextMenu — Bottom Sheet',
8
+ displayName: 'ContextMenu — Bottom Sheet',
9
+ description:
10
+ 'A ContextMenu using the explicit BottomSheet presentation. Long-press the target on touch devices or right-click it with a pointer. Keep a visible menu trigger for important mobile actions.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['ContextMenu'],
14
+ };
@@ -0,0 +1,59 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {
6
+ DocumentDuplicateIcon,
7
+ PencilIcon,
8
+ ShareIcon,
9
+ TrashIcon,
10
+ } from '@heroicons/react/24/outline';
11
+ import {ContextMenu} from '@astryxdesign/core/ContextMenu';
12
+
13
+ export default function ContextMenuBottomSheet() {
14
+ return (
15
+ <ContextMenu
16
+ presentation="bottom-sheet"
17
+ label="Document actions"
18
+ items={[
19
+ {
20
+ label: 'Rename document',
21
+ description: 'Change the title shown to collaborators.',
22
+ icon: PencilIcon,
23
+ onClick: () => {},
24
+ },
25
+ {
26
+ label: 'Duplicate document',
27
+ description: 'Create a copy in the same workspace.',
28
+ icon: DocumentDuplicateIcon,
29
+ onClick: () => {},
30
+ },
31
+ {
32
+ label: 'Share document',
33
+ description: 'Invite people or copy a share link.',
34
+ icon: ShareIcon,
35
+ onClick: () => {},
36
+ },
37
+ {
38
+ label: 'Delete document',
39
+ description: 'Move this document to the trash.',
40
+ icon: TrashIcon,
41
+ variant: 'destructive',
42
+ onClick: () => {},
43
+ },
44
+ ]}>
45
+ <div
46
+ style={{
47
+ display: 'flex',
48
+ flexDirection: 'column',
49
+ gap: '8px',
50
+ padding: '32px',
51
+ border: '1px solid #d1d5db',
52
+ borderRadius: '12px',
53
+ }}>
54
+ <strong>Quarterly plan</strong>
55
+ <span>Long-press on touch or right-click for document actions.</span>
56
+ </div>
57
+ </ContextMenu>
58
+ );
59
+ }
@@ -6,7 +6,7 @@ export const doc = {
6
6
  name: 'ContextMenu',
7
7
  displayName: 'Context Menu',
8
8
  description:
9
- 'A right-click area that opens a context menu with action items.',
9
+ 'An adaptive context area that supports long-press on compact touch screens and right-click elsewhere.',
10
10
  isReady: true,
11
11
  isShowcase: true,
12
12
  aspectRatio: 1,
@@ -6,6 +6,7 @@ import {ContextMenu} from '@astryxdesign/core/ContextMenu';
6
6
  export default function ContextMenuShowcase() {
7
7
  return (
8
8
  <ContextMenu
9
+ presentation="adaptive"
9
10
  items={[
10
11
  {label: 'Cut', onClick: () => {}},
11
12
  {label: 'Copy', onClick: () => {}},
@@ -22,7 +23,7 @@ export default function ContextMenuShowcase() {
22
23
  color: '#6b7280',
23
24
  userSelect: 'none',
24
25
  }}>
25
- Right-click this area
26
+ Long-press or right-click this area
26
27
  </div>
27
28
  </ContextMenu>
28
29
  );
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'DropdownMenu',
7
+ alsoExampleFor: ['BottomSheet', 'useMediaQuery'],
8
+ name: 'DropdownMenu — Adaptive presentation',
9
+ displayName: 'DropdownMenu — Adaptive presentation',
10
+ description:
11
+ 'Chooses a bottom sheet for compact touch surfaces and an anchored popover otherwise. The media query is product policy, while DropdownMenu owns both presentations.',
12
+ isReady: true,
13
+ aspectRatio: 3 / 4,
14
+ componentsUsed: ['DropdownMenu', 'Stack', 'Text', 'useMediaQuery'],
15
+ };
@@ -0,0 +1,49 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {
7
+ ArchiveBoxIcon,
8
+ DocumentDuplicateIcon,
9
+ PencilIcon,
10
+ ShareIcon,
11
+ } from '@heroicons/react/24/outline';
12
+ import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
13
+ import {VStack} from '@astryxdesign/core/Stack';
14
+ import {Text} from '@astryxdesign/core/Text';
15
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
16
+
17
+ const COMPACT_TOUCH_QUERY =
18
+ '(max-width: 639px) and (pointer: coarse) and (hover: none)';
19
+
20
+ const ACTIONS = [
21
+ {label: 'Edit project', icon: PencilIcon},
22
+ {label: 'Duplicate project', icon: DocumentDuplicateIcon},
23
+ {label: 'Share project', icon: ShareIcon},
24
+ {label: 'Archive project', icon: ArchiveBoxIcon},
25
+ ] as const;
26
+
27
+ export default function DropdownMenuBottomSheet() {
28
+ const [lastAction, setLastAction] = useState<string | null>(null);
29
+ const isCompactTouchSurface = useMediaQuery(COMPACT_TOUCH_QUERY);
30
+
31
+ return (
32
+ <VStack gap={3}>
33
+ <DropdownMenu
34
+ button={{label: 'Project actions'}}
35
+ presentation={isCompactTouchSurface ? 'bottom-sheet' : 'popover'}
36
+ items={ACTIONS.map(({label, icon}) => ({
37
+ label,
38
+ icon,
39
+ onClick: () => setLastAction(label),
40
+ }))}
41
+ />
42
+ {lastAction && (
43
+ <Text type="supporting" color="secondary">
44
+ Last action: {lastAction}
45
+ </Text>
46
+ )}
47
+ </VStack>
48
+ );
49
+ }
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'MoreMenu',
7
+ name: 'MoreMenu — Bottom Sheet',
8
+ displayName: 'MoreMenu — Bottom Sheet',
9
+ description:
10
+ 'A visible overflow trigger that opens actions in a BottomSheet. Use this presentation for short action sets on compact touch surfaces.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['MoreMenu'],
14
+ };
@@ -0,0 +1,63 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {
6
+ DocumentDuplicateIcon,
7
+ PencilIcon,
8
+ ShareIcon,
9
+ TrashIcon,
10
+ } from '@heroicons/react/24/outline';
11
+ import {MoreMenu} from '@astryxdesign/core/MoreMenu';
12
+
13
+ export default function MoreMenuBottomSheet() {
14
+ return (
15
+ <div
16
+ style={{
17
+ display: 'flex',
18
+ alignItems: 'center',
19
+ justifyContent: 'space-between',
20
+ gap: '16px',
21
+ width: 'min(100%, 360px)',
22
+ padding: '16px',
23
+ border: '1px solid #d1d5db',
24
+ borderRadius: '12px',
25
+ }}>
26
+ <div style={{display: 'flex', flexDirection: 'column', gap: '4px'}}>
27
+ <strong>Quarterly plan</strong>
28
+ <span>Updated a few minutes ago</span>
29
+ </div>
30
+ <MoreMenu
31
+ presentation="bottom-sheet"
32
+ label="Project actions"
33
+ items={[
34
+ {
35
+ label: 'Rename project',
36
+ description: 'Update the project title.',
37
+ icon: PencilIcon,
38
+ onClick: () => {},
39
+ },
40
+ {
41
+ label: 'Duplicate project',
42
+ description: 'Create a copy in this workspace.',
43
+ icon: DocumentDuplicateIcon,
44
+ onClick: () => {},
45
+ },
46
+ {
47
+ label: 'Share project',
48
+ description: 'Invite people to collaborate.',
49
+ icon: ShareIcon,
50
+ onClick: () => {},
51
+ },
52
+ {
53
+ label: 'Delete project',
54
+ description: 'Move this project to the trash.',
55
+ icon: TrashIcon,
56
+ variant: 'destructive',
57
+ onClick: () => {},
58
+ },
59
+ ]}
60
+ />
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'MultiSelector',
7
+ alsoExampleFor: ['BottomSheet'],
8
+ name: 'MultiSelector — Bottom Sheet',
9
+ displayName: 'MultiSelector — Bottom Sheet',
10
+ description:
11
+ 'Keeps a multi-selection list open in a bottom sheet while choices are toggled.',
12
+ isReady: true,
13
+ aspectRatio: 3 / 4,
14
+ componentsUsed: ['MultiSelector'],
15
+ };
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {MultiSelector} from '@astryxdesign/core/MultiSelector';
7
+
8
+ const OPTIONS = ['Design', 'Engineering', 'Marketing', 'Operations'];
9
+
10
+ export default function MultiSelectorBottomSheet() {
11
+ const [value, setValue] = useState<string[]>([]);
12
+
13
+ return (
14
+ <div style={{width: 320, maxWidth: '100%'}}>
15
+ <MultiSelector
16
+ label="Teams"
17
+ options={OPTIONS}
18
+ value={value}
19
+ onChange={setValue}
20
+ placeholder="Choose teams"
21
+ hasSelectAll
22
+ presentation="bottom-sheet"
23
+ />
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,22 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'Popover',
7
+ name: 'Popover — Bottom Sheet Alternative',
8
+ displayName: 'Popover — Bottom Sheet Alternative',
9
+ description:
10
+ 'Opt-in BottomSheet alternative for compact touch surfaces where actions should use a modal presentation anchored to the bottom edge.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: [
14
+ 'BottomSheet',
15
+ 'Button',
16
+ 'Icon',
17
+ 'Section',
18
+ 'Layout',
19
+ 'Text',
20
+ 'List',
21
+ ],
22
+ };
@@ -0,0 +1,71 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import * as stylex from '@stylexjs/stylex';
7
+ import {CalendarIcon, TagIcon, UserIcon} from '@heroicons/react/24/outline';
8
+ import {BottomSheet} from '@astryxdesign/core/BottomSheet';
9
+ import {Button} from '@astryxdesign/core/Button';
10
+ import {Icon} from '@astryxdesign/core/Icon';
11
+ import {List, ListItem} from '@astryxdesign/core/List';
12
+ import {VStack} from '@astryxdesign/core/Layout';
13
+ import {Section} from '@astryxdesign/core/Section';
14
+ import {Heading, Text} from '@astryxdesign/core/Text';
15
+ import {spacingVars} from '@astryxdesign/core/theme/tokens.stylex';
16
+
17
+ const styles = stylex.create({
18
+ header: {
19
+ // Match the unchanged spacious ListItem inset so the title and description
20
+ // align with the action icons.
21
+ marginInlineStart: spacingVars['--spacing-3'],
22
+ },
23
+ });
24
+
25
+ const PROJECT_ACTIONS = [
26
+ [UserIcon, 'Assign owner', 'Route follow-up to a teammate.'],
27
+ [TagIcon, 'Add label', 'Group this item with related work.'],
28
+ [CalendarIcon, 'Set due date', 'Pick a reminder for review.'],
29
+ ] as const;
30
+
31
+ export default function PopoverBottomSheetAlternative() {
32
+ const [isOpen, setIsOpen] = useState(false);
33
+
34
+ return (
35
+ <>
36
+ <Button label="Open project actions" onClick={() => setIsOpen(true)}>
37
+ Open project actions
38
+ </Button>
39
+ <BottomSheet
40
+ isOpen={isOpen}
41
+ onOpenChange={setIsOpen}
42
+ label="Project actions"
43
+ height="hug">
44
+ <Section paddingBlock={4} paddingInline={1}>
45
+ <VStack gap={3}>
46
+ <VStack gap={1} xstyle={styles.header}>
47
+ <Heading level={3}>Project actions</Heading>
48
+ <Text type="supporting" color="secondary">
49
+ Use this modal touch surface when the task should move away from
50
+ its trigger and stay close to the bottom edge.
51
+ </Text>
52
+ </VStack>
53
+ <List density="spacious">
54
+ {PROJECT_ACTIONS.map(([icon, label, description]) => (
55
+ <ListItem
56
+ key={label}
57
+ label={label}
58
+ description={description}
59
+ startContent={
60
+ <Icon icon={icon} size="md" color="secondary" />
61
+ }
62
+ onClick={() => setIsOpen(false)}
63
+ />
64
+ ))}
65
+ </List>
66
+ </VStack>
67
+ </Section>
68
+ </BottomSheet>
69
+ </>
70
+ );
71
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'Selector',
7
+ alsoExampleFor: ['BottomSheet'],
8
+ name: 'Selector — Bottom Sheet',
9
+ displayName: 'Selector — Bottom Sheet',
10
+ description:
11
+ 'Presents a single-selection list in a bottom sheet for compact touch interfaces.',
12
+ isReady: true,
13
+ aspectRatio: 3 / 4,
14
+ componentsUsed: ['Selector'],
15
+ };
@@ -0,0 +1,30 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Selector} from '@astryxdesign/core/Selector';
7
+
8
+ const OPTIONS = [
9
+ {value: 'design', label: 'Design'},
10
+ {value: 'engineering', label: 'Engineering'},
11
+ {value: 'marketing', label: 'Marketing'},
12
+ {value: 'operations', label: 'Operations'},
13
+ ];
14
+
15
+ export default function SelectorBottomSheet() {
16
+ const [value, setValue] = useState<string | undefined>();
17
+
18
+ return (
19
+ <div style={{width: 320, maxWidth: '100%'}}>
20
+ <Selector
21
+ label="Team"
22
+ options={OPTIONS}
23
+ value={value}
24
+ onChange={setValue}
25
+ placeholder="Choose a team"
26
+ presentation="bottom-sheet"
27
+ />
28
+ </div>
29
+ );
30
+ }
@@ -64,8 +64,96 @@ export interface ComponentBestPractice {
64
64
  export interface ComponentAccessibilityRequirement {
65
65
  /** Short scannable label, e.g. `"Accessible name"` or `"Loading"`. */
66
66
  name: string;
67
- /** The accessibility contract consumers must preserve. */
67
+ /**
68
+ * The accessibility contract consumers must preserve. Write at about a
69
+ * grade-7 reading level with short sentences, common words, and active
70
+ * voice. For color contrast, put the ratio in `requirement`; name the exact
71
+ * foreground, background, state, and any overlay in `description`; explain
72
+ * exceptions in plain language; and include enough detail for a human or
73
+ * agent to reproduce the check.
74
+ */
68
75
  description: string;
76
+ /** Groups related requirements in the docsite Accessibility tab. */
77
+ category?: 'Color contrast' | 'Keyboard' | 'Semantics' | 'Content';
78
+ /** Relevant WCAG success criterion, e.g. `"1.4.3 Contrast (Minimum)"`. */
79
+ criterion?: string;
80
+ /** Short threshold or rule, e.g. `"4.5:1"`, `"3:1"`, or `"Exempt"`. */
81
+ requirement?: string;
82
+ /** Component states covered by this requirement. */
83
+ states?: string[];
84
+ }
85
+
86
+ export type ComponentAccessibilityThemeStatus = 'Pass' | 'Fail' | 'Not tested';
87
+
88
+ export type ComponentAccessibilityThemeApplicability =
89
+ 'Required' | 'Conditional' | 'Supplemental' | 'Decorative';
90
+
91
+ export interface ComponentAccessibilityThemeMeasurement {
92
+ /** Column heading, e.g. `"Rest"` or `"Spinner"`. */
93
+ label: string;
94
+ /** Display value, e.g. `"15.13:1"`. */
95
+ value: string;
96
+ /** Optional supporting detail shown below the value, such as a worst case. */
97
+ detail?: string;
98
+ /**
99
+ * Whether this measurement is required for every use, required only in some
100
+ * contexts, shown as a supplemental cue, or decorative. Each theme declares
101
+ * this intent, informed by the component contract. Never infer it from the
102
+ * measured ratio. Non-required measurements do not determine row status.
103
+ */
104
+ applicability?: ComponentAccessibilityThemeApplicability;
105
+ /** Rendered foreground and background colors used for this measurement. */
106
+ colorPair?: {
107
+ foreground: string;
108
+ background: string;
109
+ };
110
+ /** Optional per-variant results shown from a compact details trigger. */
111
+ breakdown?: Array<{
112
+ label: string;
113
+ value: string;
114
+ detail?: string;
115
+ colorPair: {
116
+ foreground: string;
117
+ background: string;
118
+ };
119
+ status?: 'Pass' | 'Fail';
120
+ }>;
121
+ /** Mark a failed measurement so the docsite can emphasize it. */
122
+ status?: 'Pass' | 'Fail';
123
+ }
124
+
125
+ export interface ComponentAccessibilityThemeResult {
126
+ /** Row heading, usually a component variant. */
127
+ name: string;
128
+ /** Measurements shown between the row heading and status. */
129
+ measurements: ComponentAccessibilityThemeMeasurement[];
130
+ /** Overall result for the row. */
131
+ status: ComponentAccessibilityThemeStatus;
132
+ }
133
+
134
+ export interface ComponentAccessibilityThemeMode {
135
+ /** Theme mode covered by these results. */
136
+ mode: 'Light' | 'Dark';
137
+ /** Detailed results for the component in this mode. */
138
+ results: ComponentAccessibilityThemeResult[];
139
+ }
140
+
141
+ export interface ComponentAccessibilityThemeTable {
142
+ /** Optional heading for one complete group of measurements. */
143
+ title?: string;
144
+ /** Explains the scope of this measurement group. */
145
+ description?: string;
146
+ /** Detailed results separated by theme mode. */
147
+ modes: ComponentAccessibilityThemeMode[];
148
+ }
149
+
150
+ export interface ComponentAccessibilityThemeCoverage {
151
+ /** Display name of the audited theme. */
152
+ theme: string;
153
+ /** Complete groups of measurements for this theme and component. */
154
+ tables: ComponentAccessibilityThemeTable[];
155
+ /** Theme visuals intentionally excluded from measurement, with a reason. */
156
+ notMeasured?: string[];
69
157
  }
70
158
 
71
159
  /**
@@ -462,6 +550,8 @@ export interface UsageDoc {
462
550
  /** Accessibility requirements specific to this component and its supported
463
551
  * content combinations. Generic audit procedure stays in the wiki rubric. */
464
552
  accessibility?: ComponentAccessibilityRequirement[];
553
+ /** Verified color-accessibility coverage for bundled themes. */
554
+ accessibilityThemeCoverage?: ComponentAccessibilityThemeCoverage[];
465
555
  /** Structural/visual anatomy of the component. Each entry describes one
466
556
  * element that makes up the component (icon slot, label, container, etc.).
467
557
  * Order entries in the visual reading order (leading → trailing, top → bottom). */
@@ -132,7 +132,13 @@ export const doc = {
132
132
  name: 'usage.accessibility',
133
133
  type: 'ComponentAccessibilityRequirement[]',
134
134
  description:
135
- 'Component-specific accessibility requirements ({name, description}) rendered as a dedicated Accessibility section. Keep audit procedures in the wiki rubric.',
135
+ 'Component-specific requirements rendered in the shared Accessibility tab. Write at about a grade-7 reading level with short sentences, common words, and active voice. For color contrast, put the ratio in `requirement`; name the exact foreground, background, state, and any overlay in `description`; explain exceptions in plain language; and give a human or agent enough detail to reproduce the check. Keep repository audit procedures in the wiki rubric.',
136
+ },
137
+ {
138
+ name: 'usage.accessibilityThemeCoverage',
139
+ type: 'ComponentAccessibilityThemeCoverage[]',
140
+ description:
141
+ 'Verified per-theme accessibility measurements rendered in the shared Accessibility tab. Record light and dark mode separately, include rendered color pairs, and mark failed measurements. Put visuals excluded from the audit in `notMeasured` with a short reason; do not add them as table measurements. Each theme declares `applicability` for measured values, informed by the component contract and never inferred from the ratio: `Conditional` is required only in some contexts, `Supplemental` adds another meaningful cue, and `Decorative` has no required meaning. These values do not change row status. Provide a complete breakdown when one cell summarizes multiple combinations, and protect derived values with an automated audit.',
136
142
  },
137
143
  {
138
144
  name: 'usage.anatomy',
@@ -68,6 +68,13 @@ export type {
68
68
  ComponentExampleDoc,
69
69
  ComponentAnatomyElement,
70
70
  ComponentAccessibilityRequirement,
71
+ ComponentAccessibilityThemeStatus,
72
+ ComponentAccessibilityThemeApplicability,
73
+ ComponentAccessibilityThemeMeasurement,
74
+ ComponentAccessibilityThemeResult,
75
+ ComponentAccessibilityThemeMode,
76
+ ComponentAccessibilityThemeTable,
77
+ ComponentAccessibilityThemeCoverage,
71
78
  ComponentBestPractice,
72
79
  ComponentSlotElement,
73
80
  ComponentPlaygroundConfig,
@@ -15,6 +15,16 @@ export namespace docs {
15
15
  name: string;
16
16
  description: string;
17
17
  }[];
18
+ let accessibilityThemeCoverage: {
19
+ theme: string;
20
+ tables: {
21
+ modes: {
22
+ mode: string;
23
+ results: never[];
24
+ }[];
25
+ }[];
26
+ notMeasured: string[];
27
+ }[];
18
28
  let anatomy: {
19
29
  name: string;
20
30
  required: boolean;
@@ -18,6 +18,20 @@ export const docs = {
18
18
  description: 'Provide an accessible name.',
19
19
  },
20
20
  ],
21
+ accessibilityThemeCoverage: [
22
+ {
23
+ theme: 'Fixture',
24
+ tables: [
25
+ {
26
+ modes: [
27
+ {mode: 'Light', results: []},
28
+ {mode: 'Dark', results: []},
29
+ ],
30
+ },
31
+ ],
32
+ notMeasured: ['Decorative track — Not part of the contrast audit.'],
33
+ },
34
+ ],
21
35
  anatomy: [
22
36
  {
23
37
  name: 'Base-only anatomy',
@@ -194,16 +194,23 @@ function overlayComponentDoc(docs, translation) {
194
194
  });
195
195
  };
196
196
 
197
- /** Preserve the new accessibility field without changing established translated output.
197
+ /** Preserve structured accessibility data without changing established translated output.
198
198
  * @param {any} baseUsage
199
199
  * @param {any} translatedUsage
200
200
  */
201
201
  const mergeUsage = (baseUsage, translatedUsage) => {
202
202
  if (!translatedUsage) return baseUsage;
203
- if (!baseUsage?.accessibility || translatedUsage.accessibility !== undefined) {
204
- return translatedUsage;
205
- }
206
- return {...translatedUsage, accessibility: baseUsage.accessibility};
203
+ return {
204
+ ...translatedUsage,
205
+ ...(translatedUsage.accessibility === undefined &&
206
+ baseUsage?.accessibility !== undefined
207
+ ? {accessibility: baseUsage.accessibility}
208
+ : null),
209
+ ...(translatedUsage.accessibilityThemeCoverage === undefined &&
210
+ baseUsage?.accessibilityThemeCoverage !== undefined
211
+ ? {accessibilityThemeCoverage: baseUsage.accessibilityThemeCoverage}
212
+ : null),
213
+ };
207
214
  };
208
215
 
209
216
  const merged = {...docs, ...translation, usage: mergeUsage(docs.usage, translation.usage)};
@@ -120,6 +120,12 @@ describe('the reported symptom', () => {
120
120
 
121
121
  expect(english.usage.accessibility.length).toBeGreaterThan(0);
122
122
  expect(dense.usage.accessibility).toEqual(english.usage.accessibility);
123
+ expect(dense.usage.accessibilityThemeCoverage).toEqual(
124
+ english.usage.accessibilityThemeCoverage,
125
+ );
126
+ expect(dense.usage.accessibilityThemeCoverage[0].notMeasured).toContain(
127
+ 'Decorative track — Not part of the contrast audit.',
128
+ );
123
129
  expect(dense.usage.bestPractices).toBeUndefined();
124
130
  expect(dense.usage.anatomy).toBeUndefined();
125
131
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.5.2-canary.c9c8564",
3
+ "version": "0.5.2-canary.f83cae8",
4
4
  "displayName": "CLI",
5
5
  "description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
6
6
  "author": "Meta Open Source",
@@ -87,10 +87,10 @@
87
87
  "zod": "^4.4.3"
88
88
  },
89
89
  "peerDependencies": {
90
- "@astryxdesign/charts": "0.5.2-canary.c9c8564",
91
- "@astryxdesign/core": "0.5.2-canary.c9c8564",
92
- "@astryxdesign/lab": "0.5.2-canary.c9c8564",
93
- "@astryxdesign/theme-neutral": "0.5.2-canary.c9c8564",
90
+ "@astryxdesign/charts": "0.5.2-canary.f83cae8",
91
+ "@astryxdesign/core": "0.5.2-canary.f83cae8",
92
+ "@astryxdesign/lab": "0.5.2-canary.f83cae8",
93
+ "@astryxdesign/theme-neutral": "0.5.2-canary.f83cae8",
94
94
  "gpt-tokenizer": "^3.4.0"
95
95
  },
96
96
  "peerDependenciesMeta": {
@@ -108,10 +108,10 @@
108
108
  }
109
109
  },
110
110
  "devDependencies": {
111
- "@astryxdesign/charts": "0.5.2-canary.c9c8564",
112
- "@astryxdesign/core": "0.5.2-canary.c9c8564",
113
- "@astryxdesign/lab": "0.5.2-canary.c9c8564",
114
- "@astryxdesign/theme-neutral": "0.5.2-canary.c9c8564",
111
+ "@astryxdesign/charts": "0.5.2-canary.f83cae8",
112
+ "@astryxdesign/core": "0.5.2-canary.f83cae8",
113
+ "@astryxdesign/lab": "0.5.2-canary.f83cae8",
114
+ "@astryxdesign/theme-neutral": "0.5.2-canary.f83cae8",
115
115
  "gpt-tokenizer": "^3.4.0"
116
116
  },
117
117
  "scripts": {