@astryxdesign/cli 0.4.3-canary.71781fc → 0.4.3-canary.773ff16

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 (25) hide show
  1. package/api/discover/_adapter.d.mts +5 -3
  2. package/api/discover/_adapter.mjs +6 -4
  3. package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.doc.mjs +21 -0
  4. package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.tsx +46 -0
  5. package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.doc.mjs +23 -0
  6. package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.tsx +100 -0
  7. package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.doc.mjs +22 -0
  8. package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.tsx +47 -0
  9. package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.doc.mjs +22 -0
  10. package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.tsx +51 -0
  11. package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.doc.mjs +26 -0
  12. package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.tsx +221 -0
  13. package/authoring/doctypes/base/type.ts +9 -0
  14. package/clients/cli/commands/discover.broken-integration.test.mjs +112 -0
  15. package/clients/cli/commands/discover.mjs +12 -0
  16. package/clients/cli/commands/search.mjs +9 -0
  17. package/foundation/integrations/integration-warnings.test.mjs +17 -0
  18. package/foundation/integrations/validate-contributions.mjs +7 -0
  19. package/package.json +9 -9
  20. package/assets/templates/pages/table-page-chart/page.tsx +0 -577
  21. package/assets/templates/pages/table-page-chart/template.doc.mjs +0 -13
  22. package/assets/templates/pages/table-page-heatmap-status/page.tsx +0 -467
  23. package/assets/templates/pages/table-page-heatmap-status/template.doc.mjs +0 -13
  24. package/assets/templates/pages/table-page-shoe-store-heatmap/page.tsx +0 -931
  25. package/assets/templates/pages/table-page-shoe-store-heatmap/template.doc.mjs +0 -13
@@ -6,9 +6,11 @@
6
6
  *
7
7
  * External packages come from configured integrations that declare a components
8
8
  * root; each becomes a scannable package keyed by its docsDir. `configured`
9
- * reports whether ANY integration declared a components root — it lets an empty
10
- * result distinguish "nothing configured" (`false`) from "configured but
11
- * nothing discovered" (`true`), which the list leaf surfaces as `meta`.
9
+ * reports whether the project configured ANY integration — including one whose
10
+ * manifest failed to load, which contributes nothing but is emphatically not
11
+ * "nothing configured". It lets an empty result distinguish "nothing
12
+ * configured" (`false`) from "configured but nothing discovered" (`true`),
13
+ * which the list leaf surfaces as `meta`.
12
14
  *
13
15
  * @returns {Promise<{packages: ScannedPackage[], configured: boolean}>}
14
16
  */
@@ -62,9 +62,11 @@ function validateDocs(docs) {
62
62
  *
63
63
  * External packages come from configured integrations that declare a components
64
64
  * root; each becomes a scannable package keyed by its docsDir. `configured`
65
- * reports whether ANY integration declared a components root — it lets an empty
66
- * result distinguish "nothing configured" (`false`) from "configured but
67
- * nothing discovered" (`true`), which the list leaf surfaces as `meta`.
65
+ * reports whether the project configured ANY integration — including one whose
66
+ * manifest failed to load, which contributes nothing but is emphatically not
67
+ * "nothing configured". It lets an empty result distinguish "nothing
68
+ * configured" (`false`) from "configured but nothing discovered" (`true`),
69
+ * which the list leaf surfaces as `meta`.
68
70
  *
69
71
  * @returns {Promise<{packages: ScannedPackage[], configured: boolean}>}
70
72
  */
@@ -84,7 +86,7 @@ export async function discoverPackages() {
84
86
  docsDir: integration.components,
85
87
  }));
86
88
  if (explicitPackages.length === 0) {
87
- return {packages: [], configured: false};
89
+ return {packages: [], configured: loadedIntegrations.length > 0};
88
90
  }
89
91
 
90
92
  const packages = scanAllPackages(
@@ -0,0 +1,21 @@
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: 'BottomSheet',
7
+ name: 'Bottom Sheet — Height variants',
8
+ displayName: 'Bottom Sheet — Height variants',
9
+ description:
10
+ 'Compares hug, capped, and tall starting heights for different amounts of content.',
11
+ isReady: true,
12
+ aspectRatio: 3 / 4,
13
+ componentsUsed: [
14
+ 'BottomSheet',
15
+ 'Button',
16
+ 'Divider',
17
+ 'Heading',
18
+ 'Stack',
19
+ 'Text',
20
+ ],
21
+ };
@@ -0,0 +1,46 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {
7
+ BottomSheet,
8
+ type BottomSheetHeight,
9
+ } from '@astryxdesign/core/BottomSheet';
10
+ import {Button} from '@astryxdesign/core/Button';
11
+ import {Divider} from '@astryxdesign/core/Divider';
12
+ import {Heading} from '@astryxdesign/core/Heading';
13
+ import {HStack, VStack} from '@astryxdesign/core/Stack';
14
+ import {Text} from '@astryxdesign/core/Text';
15
+
16
+ const descriptions: Record<BottomSheetHeight, string> = {
17
+ hug: 'Hug fits short, bounded content.',
18
+ capped: 'Capped starts at a comfortable mid-height for lists and filters.',
19
+ tall: 'Tall reserves most of the viewport for long or changing content.',
20
+ };
21
+
22
+ export default function BottomSheetHeights() {
23
+ const [height, setHeight] = useState<BottomSheetHeight | null>(null);
24
+
25
+ return (
26
+ <>
27
+ <HStack gap={2} wrap="wrap">
28
+ <Button label="Open hug" onClick={() => setHeight('hug')} />
29
+ <Button label="Open capped" onClick={() => setHeight('capped')} />
30
+ <Button label="Open tall" onClick={() => setHeight('tall')} />
31
+ </HStack>
32
+ <BottomSheet
33
+ isOpen={height != null}
34
+ onOpenChange={isOpen => !isOpen && setHeight(null)}
35
+ label={`${height ?? 'Hug'} height`}
36
+ height={height ?? 'hug'}>
37
+ <VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
38
+ <Heading level={3}>{height ?? 'Hug'} height</Heading>
39
+ <Divider />
40
+ <Text type="body">{descriptions[height ?? 'hug']}</Text>
41
+ <Button label="Close" onClick={() => setHeight(null)} />
42
+ </VStack>
43
+ </BottomSheet>
44
+ </>
45
+ );
46
+ }
@@ -0,0 +1,23 @@
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: 'BottomSheet',
7
+ name: 'Bottom Sheet — Mobile keyboard',
8
+ displayName: 'Bottom Sheet — Mobile keyboard',
9
+ description:
10
+ 'Uses a tall, scrollable form that keeps focused controls visible above the mobile keyboard.',
11
+ isReady: true,
12
+ aspectRatio: 3 / 4,
13
+ componentsUsed: [
14
+ 'BottomSheet',
15
+ 'Button',
16
+ 'Divider',
17
+ 'Heading',
18
+ 'Stack',
19
+ 'Text',
20
+ 'TextArea',
21
+ 'TextInput',
22
+ ],
23
+ };
@@ -0,0 +1,100 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {BottomSheet} from '@astryxdesign/core/BottomSheet';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+ import {Divider} from '@astryxdesign/core/Divider';
9
+ import {Heading} from '@astryxdesign/core/Heading';
10
+ import {VStack} from '@astryxdesign/core/Stack';
11
+ import {Text} from '@astryxdesign/core/Text';
12
+ import {TextArea} from '@astryxdesign/core/TextArea';
13
+ import {TextInput} from '@astryxdesign/core/TextInput';
14
+
15
+ interface ProfileFormValues {
16
+ name: string;
17
+ email: string;
18
+ company: string;
19
+ role: string;
20
+ bio: string;
21
+ notes: string;
22
+ }
23
+
24
+ const initialValues: ProfileFormValues = {
25
+ name: '',
26
+ email: '',
27
+ company: '',
28
+ role: '',
29
+ bio: '',
30
+ notes: '',
31
+ };
32
+
33
+ export default function BottomSheetMobileKeyboard() {
34
+ const [isOpen, setIsOpen] = useState(false);
35
+ const [values, setValues] = useState(initialValues);
36
+ const update =
37
+ (field: keyof ProfileFormValues) =>
38
+ (value: string): void =>
39
+ setValues(current => ({...current, [field]: value}));
40
+
41
+ return (
42
+ <>
43
+ <Button label="Edit profile" onClick={() => setIsOpen(true)} />
44
+ <BottomSheet
45
+ isOpen={isOpen}
46
+ onOpenChange={setIsOpen}
47
+ label="Edit profile"
48
+ height="tall">
49
+ <form
50
+ onSubmit={event => {
51
+ event.preventDefault();
52
+ setIsOpen(false);
53
+ }}>
54
+ <VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
55
+ <Heading level={3}>Edit profile</Heading>
56
+ <Divider />
57
+ <Text type="supporting" color="secondary">
58
+ Focus fields throughout the form to see them remain visible above
59
+ the mobile keyboard.
60
+ </Text>
61
+ <TextInput
62
+ label="Name"
63
+ value={values.name}
64
+ onChange={update('name')}
65
+ />
66
+ <TextInput
67
+ label="Email"
68
+ type="email"
69
+ value={values.email}
70
+ onChange={update('email')}
71
+ />
72
+ <TextInput
73
+ label="Company"
74
+ value={values.company}
75
+ onChange={update('company')}
76
+ />
77
+ <TextInput
78
+ label="Role"
79
+ value={values.role}
80
+ onChange={update('role')}
81
+ />
82
+ <TextArea
83
+ label="Bio"
84
+ rows={5}
85
+ value={values.bio}
86
+ onChange={update('bio')}
87
+ />
88
+ <TextArea
89
+ label="Notes"
90
+ rows={5}
91
+ value={values.notes}
92
+ onChange={update('notes')}
93
+ />
94
+ <Button label="Save profile" type="submit" />
95
+ </VStack>
96
+ </form>
97
+ </BottomSheet>
98
+ </>
99
+ );
100
+ }
@@ -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: 'BottomSheet',
7
+ name: 'Bottom Sheet — No scrim',
8
+ displayName: 'Bottom Sheet — No scrim',
9
+ description:
10
+ 'Keeps the page visible and interactive behind a non-modal bottom sheet.',
11
+ isReady: true,
12
+ aspectRatio: 3 / 4,
13
+ componentsUsed: [
14
+ 'BottomSheet',
15
+ 'Button',
16
+ 'Divider',
17
+ 'Heading',
18
+ 'Section',
19
+ 'Stack',
20
+ 'Text',
21
+ ],
22
+ };
@@ -0,0 +1,47 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {BottomSheet} from '@astryxdesign/core/BottomSheet';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+ import {Divider} from '@astryxdesign/core/Divider';
9
+ import {Heading} from '@astryxdesign/core/Heading';
10
+ import {Section} from '@astryxdesign/core/Section';
11
+ import {VStack} from '@astryxdesign/core/Stack';
12
+ import {Text} from '@astryxdesign/core/Text';
13
+
14
+ export default function BottomSheetNoScrim() {
15
+ const [isOpen, setIsOpen] = useState(false);
16
+ const [backgroundClicks, setBackgroundClicks] = useState(0);
17
+
18
+ return (
19
+ <Section padding={4}>
20
+ <VStack gap={3}>
21
+ <Heading level={3}>Nearby places</Heading>
22
+ <Text type="body">Background interactions: {backgroundClicks}</Text>
23
+ <Button
24
+ label="Interact with page"
25
+ variant="secondary"
26
+ onClick={() => setBackgroundClicks(count => count + 1)}
27
+ />
28
+ <Button label="Show place details" onClick={() => setIsOpen(true)} />
29
+ </VStack>
30
+ <BottomSheet
31
+ isOpen={isOpen}
32
+ onOpenChange={setIsOpen}
33
+ label="Place details"
34
+ height="hug"
35
+ hasScrim={false}>
36
+ <VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
37
+ <Heading level={3}>Central Park</Heading>
38
+ <Divider />
39
+ <Text type="body">
40
+ The page remains visible and interactive behind this sheet.
41
+ </Text>
42
+ <Button label="Close details" onClick={() => setIsOpen(false)} />
43
+ </VStack>
44
+ </BottomSheet>
45
+ </Section>
46
+ );
47
+ }
@@ -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: 'BottomSheet',
7
+ name: 'Bottom Sheet',
8
+ displayName: 'Bottom Sheet',
9
+ description: 'A mobile filter surface that rises from the bottom edge.',
10
+ isReady: true,
11
+ isShowcase: true,
12
+ aspectRatio: 3 / 4,
13
+ componentsUsed: [
14
+ 'BottomSheet',
15
+ 'Button',
16
+ 'CheckboxInput',
17
+ 'Divider',
18
+ 'Heading',
19
+ 'Section',
20
+ 'Stack',
21
+ ],
22
+ };
@@ -0,0 +1,51 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {BottomSheet} from '@astryxdesign/core/BottomSheet';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+ import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
9
+ import {Divider} from '@astryxdesign/core/Divider';
10
+ import {Heading} from '@astryxdesign/core/Heading';
11
+ import {Section} from '@astryxdesign/core/Section';
12
+ import {VStack} from '@astryxdesign/core/Stack';
13
+
14
+ export default function BottomSheetShowcase() {
15
+ const [isOpen, setIsOpen] = useState(false);
16
+ const [inStock, setInStock] = useState(false);
17
+ const [onSale, setOnSale] = useState(false);
18
+ const [freeShipping, setFreeShipping] = useState(false);
19
+
20
+ return (
21
+ <>
22
+ <Button label="Open sheet" onClick={() => setIsOpen(true)} />
23
+ <BottomSheet isOpen={isOpen} onOpenChange={setIsOpen} label="Filters">
24
+ <Section padding={4}>
25
+ <VStack gap={4}>
26
+ <Heading level={3}>Filters</Heading>
27
+ <Divider />
28
+ <VStack gap={2}>
29
+ <CheckboxInput
30
+ label="In stock"
31
+ value={inStock}
32
+ onChange={setInStock}
33
+ />
34
+ <CheckboxInput
35
+ label="On sale"
36
+ value={onSale}
37
+ onChange={setOnSale}
38
+ />
39
+ <CheckboxInput
40
+ label="Free shipping"
41
+ value={freeShipping}
42
+ onChange={setFreeShipping}
43
+ />
44
+ </VStack>
45
+ <Button label="Apply" onClick={() => setIsOpen(false)} />
46
+ </VStack>
47
+ </Section>
48
+ </BottomSheet>
49
+ </>
50
+ );
51
+ }
@@ -0,0 +1,26 @@
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: 'BottomSheetSwitcher',
7
+ name: 'Bottom Sheet Switcher',
8
+ displayName: 'Bottom Sheet Switcher',
9
+ description:
10
+ 'A three-step flow that transitions between content-hugging sheets of different heights inside one shared dialog.',
11
+ isReady: true,
12
+ isShowcase: true,
13
+ aspectRatio: 3 / 4,
14
+ componentsUsed: [
15
+ 'BottomSheet',
16
+ 'BottomSheetSwitcher',
17
+ 'Button',
18
+ 'CheckboxInput',
19
+ 'Divider',
20
+ 'Heading',
21
+ 'RadioList',
22
+ 'Section',
23
+ 'Stack',
24
+ 'Text',
25
+ ],
26
+ };
@@ -0,0 +1,221 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {BottomSheet, BottomSheetSwitcher} from '@astryxdesign/core/BottomSheet';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+ import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
9
+ import {Divider} from '@astryxdesign/core/Divider';
10
+ import {Heading} from '@astryxdesign/core/Heading';
11
+ import {Section} from '@astryxdesign/core/Section';
12
+ import {HStack, VStack} from '@astryxdesign/core/Stack';
13
+ import {Text} from '@astryxdesign/core/Text';
14
+ import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
15
+
16
+ type NotificationSheetHeight = 'hug' | 'capped';
17
+
18
+ interface NotificationOverviewSheetProps {
19
+ height: NotificationSheetHeight;
20
+ onCancel: () => void;
21
+ onContinue: () => void;
22
+ }
23
+
24
+ function NotificationOverviewSheet({
25
+ height,
26
+ onCancel,
27
+ onContinue,
28
+ }: NotificationOverviewSheetProps) {
29
+ return (
30
+ <BottomSheet
31
+ sheetId="overview"
32
+ label="Set up notifications"
33
+ height={height}>
34
+ <Section padding={4}>
35
+ <VStack gap={4}>
36
+ <VStack gap={1}>
37
+ <Heading level={3}>Set up notifications</Heading>
38
+ <Text type="supporting" color="secondary">
39
+ Step 1 of 3
40
+ </Text>
41
+ </VStack>
42
+ <Divider />
43
+ <Text type="supporting" color="secondary">
44
+ Stay informed about activity that matters without checking back
45
+ throughout the day.
46
+ </Text>
47
+ <VStack gap={3}>
48
+ <VStack gap={1}>
49
+ <Text type="label">Important activity</Text>
50
+ <Text type="supporting" color="secondary">
51
+ Know when someone mentions you or needs your attention.
52
+ </Text>
53
+ </VStack>
54
+ <VStack gap={1}>
55
+ <Text type="label">Timely reminders</Text>
56
+ <Text type="supporting" color="secondary">
57
+ Get a reminder before work reaches its due date.
58
+ </Text>
59
+ </VStack>
60
+ <VStack gap={1}>
61
+ <Text type="label">Useful summaries</Text>
62
+ <Text type="supporting" color="secondary">
63
+ Catch up on anything you may have missed.
64
+ </Text>
65
+ </VStack>
66
+ </VStack>
67
+ <HStack gap={2} hAlign="end">
68
+ <Button label="Cancel" variant="secondary" onClick={onCancel} />
69
+ <Button label="Continue" onClick={onContinue} />
70
+ </HStack>
71
+ </VStack>
72
+ </Section>
73
+ </BottomSheet>
74
+ );
75
+ }
76
+
77
+ interface NotificationFrequencySheetProps {
78
+ height: NotificationSheetHeight;
79
+ onBack: () => void;
80
+ onContinue: () => void;
81
+ }
82
+
83
+ function NotificationFrequencySheet({
84
+ height,
85
+ onBack,
86
+ onContinue,
87
+ }: NotificationFrequencySheetProps) {
88
+ const [frequency, setFrequency] = useState('daily');
89
+
90
+ return (
91
+ <BottomSheet
92
+ sheetId="frequency"
93
+ label="Notification frequency"
94
+ height={height}>
95
+ <Section padding={4}>
96
+ <VStack gap={4}>
97
+ <VStack gap={1}>
98
+ <Heading level={3}>How often?</Heading>
99
+ <Text type="supporting" color="secondary">
100
+ Step 2 of 3
101
+ </Text>
102
+ </VStack>
103
+ <Divider />
104
+ <RadioList
105
+ label="Notification frequency"
106
+ isLabelHidden
107
+ value={frequency}
108
+ onChange={setFrequency}>
109
+ <RadioListItem label="Immediately" value="immediately" />
110
+ <RadioListItem label="Daily" value="daily" />
111
+ <RadioListItem label="Weekly" value="weekly" />
112
+ </RadioList>
113
+ <HStack gap={2} hAlign="end">
114
+ <Button label="Back" variant="secondary" onClick={onBack} />
115
+ <Button label="Continue" onClick={onContinue} />
116
+ </HStack>
117
+ </VStack>
118
+ </Section>
119
+ </BottomSheet>
120
+ );
121
+ }
122
+
123
+ interface NotificationChannelsSheetProps {
124
+ height: NotificationSheetHeight;
125
+ onBack: () => void;
126
+ onFinish: () => void;
127
+ }
128
+
129
+ function NotificationChannelsSheet({
130
+ height,
131
+ onBack,
132
+ onFinish,
133
+ }: NotificationChannelsSheetProps) {
134
+ const [email, setEmail] = useState(true);
135
+ const [pushNotifications, setPushNotifications] = useState(true);
136
+ const [textMessages, setTextMessages] = useState(false);
137
+
138
+ return (
139
+ <BottomSheet
140
+ sheetId="channels"
141
+ label="Notification channels"
142
+ height={height}>
143
+ <Section padding={4}>
144
+ <VStack gap={4}>
145
+ <VStack gap={1}>
146
+ <Heading level={3}>Where should we notify you?</Heading>
147
+ <Text type="supporting" color="secondary">
148
+ Step 3 of 3
149
+ </Text>
150
+ </VStack>
151
+ <Divider />
152
+ <Text type="supporting" color="secondary">
153
+ Choose any combination. You can change these preferences later.
154
+ </Text>
155
+ <VStack gap={2}>
156
+ <CheckboxInput label="Email" value={email} onChange={setEmail} />
157
+ <CheckboxInput
158
+ label="Push notifications"
159
+ value={pushNotifications}
160
+ onChange={setPushNotifications}
161
+ />
162
+ <CheckboxInput
163
+ label="Text messages"
164
+ value={textMessages}
165
+ onChange={setTextMessages}
166
+ />
167
+ </VStack>
168
+ <HStack gap={2} hAlign="end">
169
+ <Button label="Back" variant="secondary" onClick={onBack} />
170
+ <Button label="Finish" onClick={onFinish} />
171
+ </HStack>
172
+ </VStack>
173
+ </Section>
174
+ </BottomSheet>
175
+ );
176
+ }
177
+
178
+ interface MultiStepSwitcherExampleProps {
179
+ height: NotificationSheetHeight;
180
+ hasScrim?: boolean;
181
+ }
182
+
183
+ function MultiStepSwitcherExample({
184
+ height,
185
+ hasScrim = true,
186
+ }: MultiStepSwitcherExampleProps) {
187
+ const [activeSheet, setActiveSheet] = useState<string | null>(null);
188
+
189
+ return (
190
+ <>
191
+ <Button
192
+ label="Set up notifications"
193
+ onClick={() => setActiveSheet('overview')}
194
+ />
195
+ <BottomSheetSwitcher
196
+ activeSheet={activeSheet}
197
+ onActiveSheetChange={setActiveSheet}
198
+ hasScrim={hasScrim}>
199
+ <NotificationOverviewSheet
200
+ height={height}
201
+ onCancel={() => setActiveSheet(null)}
202
+ onContinue={() => setActiveSheet('frequency')}
203
+ />
204
+ <NotificationFrequencySheet
205
+ height={height}
206
+ onBack={() => setActiveSheet('overview')}
207
+ onContinue={() => setActiveSheet('channels')}
208
+ />
209
+ <NotificationChannelsSheet
210
+ height={height}
211
+ onBack={() => setActiveSheet('frequency')}
212
+ onFinish={() => setActiveSheet(null)}
213
+ />
214
+ </BottomSheetSwitcher>
215
+ </>
216
+ );
217
+ }
218
+
219
+ export default function BottomSheetSwitcherShowcase() {
220
+ return <MultiStepSwitcherExample height="hug" />;
221
+ }
@@ -117,6 +117,15 @@ export interface ComponentPlaygroundConfig {
117
117
  * the component is visible on load and knobs stay usable, whereas a real
118
118
  * top-layer modal makes the rest of the page inert (#3657). */
119
119
  overlay?: boolean;
120
+ /** Override the controlled prop and open value used by an overlay preview.
121
+ * Use with `overlay: true` when the component does not use `isOpen: true`
122
+ * to represent its open state. */
123
+ overlayControl?: {
124
+ /** Controlled prop that determines whether the overlay is open. */
125
+ stateProp: string;
126
+ /** Value assigned to stateProp when the preview's open button is used. */
127
+ openValue: unknown;
128
+ };
120
129
  /** The component reads AppShell mobile context and renders nothing
121
130
  * without it (e.g. `MobileNavToggle` returns null unless the context
122
131
  * reports an enabled mobile viewport — the default value outside