@astryxdesign/cli 0.4.3-canary.fd13115 → 0.4.4-canary.0f4d45f

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 (37) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -0
  3. package/api/discover/_adapter.d.mts +5 -3
  4. package/api/discover/_adapter.mjs +6 -4
  5. package/api/theme/theme.type.d.mts +16 -0
  6. package/api/theme/theme.type.mjs +11 -0
  7. package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.doc.mjs +21 -0
  8. package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.tsx +46 -0
  9. package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.doc.mjs +23 -0
  10. package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.tsx +100 -0
  11. package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.doc.mjs +22 -0
  12. package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.tsx +47 -0
  13. package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.doc.mjs +22 -0
  14. package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.tsx +51 -0
  15. package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.doc.mjs +26 -0
  16. package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.tsx +221 -0
  17. package/authoring/doctypes/base/type.ts +9 -0
  18. package/clients/cli/commands/build-theme.mjs +154 -65
  19. package/clients/cli/commands/build-theme.multi.test.mjs +148 -0
  20. package/clients/cli/commands/build-theme.watch.test.mjs +67 -0
  21. package/clients/cli/commands/discover.broken-integration.test.mjs +112 -0
  22. package/clients/cli/commands/discover.mjs +12 -0
  23. package/clients/cli/commands/search.mjs +9 -0
  24. package/clients/cli/commands/theme-build.doc.mjs +11 -5
  25. package/clients/cli/lib/json-shim.test.mjs +1 -1
  26. package/clients/cli/lib/manifest.mjs +1 -1
  27. package/clients/cli/lib/manifest.test.mjs +4 -2
  28. package/foundation/integrations/integration-warnings.test.mjs +17 -0
  29. package/foundation/integrations/validate-contributions.mjs +7 -0
  30. package/foundation/response/response-types.doc.mjs +5 -0
  31. package/package.json +12 -9
  32. package/assets/templates/pages/table-page-chart/page.tsx +0 -577
  33. package/assets/templates/pages/table-page-chart/template.doc.mjs +0 -13
  34. package/assets/templates/pages/table-page-heatmap-status/page.tsx +0 -467
  35. package/assets/templates/pages/table-page-heatmap-status/template.doc.mjs +0 -13
  36. package/assets/templates/pages/table-page-shoe-store-heatmap/page.tsx +0 -931
  37. package/assets/templates/pages/table-page-shoe-store-heatmap/template.doc.mjs +0 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @xds/cli
2
2
 
3
+ # 0.4.4
4
+
5
+ #### New Components
6
+
7
+ - Promote `BottomSheet` and `BottomSheetSwitcher` from the canary-only Lab package to Core. The stable package now includes their existing native-dialog, drag-detent, transition, and mobile-keyboard behavior, plus Core documentation and examples (#5080).
8
+
9
+ #### New Features
10
+
11
+ - `astryx template --cdn` writes a working no-build-step CDN starter page (#5068).
12
+ A CDN starter is a template, so it joins the template family beside `--skeleton` rather than claiming a top-level command. It is a flag and not the positional `astryx template cdn` because the positional resolves against everything `discoverAll()` finds, where a `cdn` id would shadow a discovered template. `cdn.template.html` loads Astryx from jsDelivr and esm.sh with no bundler, no install and no build step, with every CDN URL pinned to the Astryx version you have installed — an unpinned CDN URL resolves to whatever is latest and is cached hard, so a page written today breaks tomorrow without being edited. An existing file is never clobbered; `--overwrite` replaces it, and `--json` returns the receipt.
13
+
14
+ The annotations are the things that are load-bearing and silent when missing: `?external=react,react-dom` (without it esm.sh bundles a second React and every hook throws `Cannot read properties of null (reading 'useState')`), `react/jsx-runtime` in the import map (the published bundle imports it; omitting it fails the page with `Failed to resolve module specifier`), and a `font-family` on `body` (nothing in the stylesheets sets a document font, so `Button` — which is `font: inherit` — otherwise renders its label in the browser's default serif).
15
+
16
+ Three more lessons came out of building a real app on it. The page now `<link>`s the theme's webfont from Google Fonts, because the theme _names_ Figtree and never loads it, so every viewer silently got the fallback stack (#5015 again). It imports the theme OBJECT and wraps in `<Theme theme={neutralTheme} mode="system">`, so light and dark follow the OS — the `data-astryx-theme` attribute alone scopes the stylesheet but cannot switch modes. And `#root:empty` carries a "Loading…" state, because ESM-from-CDN has real latency and a blank page reads as broken. Markup is `htm`, with a comment saying it is optional and `createElement` is the dependency-free alternative.
17
+
18
+ A recipe that is only read is a recipe that is only assumed to work, so CI renders it: `.github/scripts/cdn-template-smoke-test.mjs` scaffolds the page with the real CLI and opens it in headless Chromium, failing on any console error, page error or failed request, and on a page that loads without rendering.
19
+ - `astryx theme build` takes any number of theme files — `astryx theme build themes/*.ts` compiles them all in one process, so an app with several themes no longer hand-rolls a loop that re-enters the CLI once per theme. Outputs are byte-identical to the serial invocations; the run stops at the first failure and names the theme that failed. The CLI's Node floor (>=22.13) is now declared in `engines`, so a package manager can enforce it at install instead of the build failing later (#5121).
20
+ - `defineTheme`: `color.accent` accepts a `[light, dark]` tuple (#2279)
21
+ `ColorScaleConfig.accent` now takes either a single hex or a `[light, dark]` tuple, matching `TokenValue`. With a tuple, `expandColorScale` derives the light half of every generated `light-dark()` pair from the light seed's palettes and the dark half from the dark seed's, so each scheme gets a consistent derived palette (muted, on-accent, neutrals) instead of the `tokens['--color-accent']` workaround that skips scale generation. Single-string configs are unchanged, token for token. Also documents the precedence between `color` and `tokens` for accent-derived values: `tokens` entries win token by token, the `var(--color-accent)` reference tokens follow a `--color-accent` override at runtime, and the baked `--color-on-accent` stays derived from the `color.accent` seed.
22
+
23
+ #### Fixes
24
+
25
+ - Bottom Sheet showcase block: the filter checkboxes are interactive again (#5157).
26
+ `CheckboxInput` is fully controlled — `value` is required and the input only moves when the owner updates it. The showcase passed a literal `value={false}` with no `onChange`, so the three filters ("In stock", "On sale", "Free shipping") rendered but could never be toggled: on the docs site the first thing a reader tries in a Bottom Sheet does nothing, and anyone copying the block inherits three dead controls. Each filter now has its own `useState` and `onChange`, matching the checkbox wiring already used in the Bottom Sheet Switcher showcase.
27
+ - An integration whose manifest fails to load is no longer silent. A manifest that throws on import — the common case being one still calling a `create*` authoring factory, removed in 0.3.0 — contributes nothing, and the CLI treated that as if the package had never been configured: `astryx discover` answered `No integrations configured.` while `astryx.config.mjs` plainly configured one, and no command said a word. The only way to find out was to already suspect it and run `validate-integration` by name. Meta's internal `@nest/xds-meta` sat invisible to CLI discovery for a week that way, and the app team's conclusion was that the components did not exist (#5119).
28
+ The load error now counts as an integration issue, so the existing one-line stderr nudge fires on `component`, `template` and `upgrade`, and `discover` — the command whose whole job is listing integrations — nudges too, as does `search`. `discover` also stops reporting `configured: false` for a project that configured an integration that failed to load; the empty state now distinguishes "you configured nothing" from "what you configured contributed nothing", which is the distinction `meta.configured` was introduced to carry.
29
+
30
+ Nothing becomes fatal: the warning is best-effort, stderr-only, suppressed under `--json`, and never changes an exit code. Broken contributions are still skipped exactly as before.
31
+
32
+ #### Contributors
33
+
34
+ Thanks to everyone who contributed to this release:
35
+
36
+ - @cixzhang
37
+ - @imdreamrunner
38
+ - @jiunshinn
39
+
40
+ ---
41
+
3
42
  # 0.4.3
4
43
 
5
44
  #### Fixes
@@ -8,6 +47,7 @@
8
47
  The `theme.build` receipt now separates the two: `warnings` are defects the author should fix, `notices` are advisories about a correct theme. The font advisory moves to `notices` and to stdout with the rest of the build's progress; stderr stays for defects. The template guard is back to `warnings` being empty, and no longer needs to know which fonts the template names.
9
48
 
10
49
  Programmatic callers reading `data.warnings` for font advisories should read `data.notices`; the message text is unchanged.
50
+
11
51
  - `extends` now reaches the CSS. A theme that extended another built a stylesheet holding only the declarations it stated itself: the base's tokens, component overrides and surface rules were all absent, and because each theme is `@scope`d to its own `data-astryx-theme` value, loading the base's stylesheet alongside could not fill the gap either. Every consumer of an inheritance chain silently got stock geometry, elevation and type with a new palette painted over it (#5067). Nothing warned; the loss only showed up by diffing two generated stylesheets token by token.
12
52
  The cause was `theme build` shadowing its own inputs. It writes `<name>.js` next to `<name>.ts`, and the loader resolved a plain `./<name>` specifier to that generated artifact before the source — so the second build of a family read the artifact, which carries no `components` and exports `<name>Theme` rather than whatever the source exports. A named import that missed became `extends: undefined`, and `defineTheme` treated an absent base as no base at all. The loader now resolves source extensions first, which is also the resolution the author's TypeScript sees, so the CSS a build emits matches the theme that type-checked.
13
53
 
package/README.md CHANGED
@@ -415,6 +415,7 @@ Every response has a `type` discriminant. The full set is below (generated from
415
415
  | `hook.detail.params` | Just one hook's parameters table (HookParamDoc[]). |
416
416
  | `theme.build` | A theme build receipt: name, token- and component-override counts, output size, the written outputs {css, js, dts, and variantsDts when applicable}, and any validation warnings. |
417
417
  | `theme.build.check` | The --check receipt: theme name, an upToDate flag, the stale outputs (each {path, reason: missing \| outdated}), and the full list of checked paths. Writes nothing. |
418
+ | `theme.build.batch` | Several themes built in one invocation: `count` plus one {file, receipt} per theme in argument order, where receipt is that theme's theme.build (or theme.build.check) envelope, or null when it produced no CSS. |
418
419
  | `theme.list` | Every bundled theme as a ThemeListEntry[]: each with slug, displayName, description, and a maintained flag. |
419
420
  | `theme.add` | A scaffold receipt: resolved slug, displayName, maintained flag, outputDir (relative to cwd), the theme entry file, its exportName, and the files written. |
420
421
  | `theme.template` | A write receipt for the annotated theme template: the path (relative to cwd), whether it was written, and the reason it was not — `exists` when a file was already there, which is a success. |
@@ -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 integrationincluding 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 integrationincluding 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(
@@ -41,6 +41,22 @@ export type ThemeBuildCheckResponse = {
41
41
  checked: string[];
42
42
  };
43
43
  };
44
+ /**
45
+ * xds --json theme build <a> <b> … — several themes in one invocation. Each
46
+ * result carries the file as it was passed and the receipt a single-file build
47
+ * would have returned (null when that theme produced no CSS). One file still
48
+ * returns the bare theme.build / theme.build.check envelope.
49
+ */
50
+ export type ThemeBuildBatchResponse = {
51
+ type: "theme.build.batch";
52
+ data: {
53
+ count: number;
54
+ results: Array<{
55
+ file: string;
56
+ receipt: ThemeBuildResponse | ThemeBuildCheckResponse | null;
57
+ }>;
58
+ };
59
+ };
44
60
  /**
45
61
  * A single theme entry as surfaced by `theme list`.
46
62
  */
@@ -10,6 +10,7 @@
10
10
  * ------------------------------------------------------------------
11
11
  * xds --json theme build <file> -> theme.build
12
12
  * xds --json theme build <file> --check -> theme.build.check
13
+ * xds --json theme build <a> <b> … -> theme.build.batch
13
14
  * xds --json theme list -> theme.list
14
15
  * xds --json theme add <slug> -> theme.add
15
16
  * xds --json theme template -> theme.template
@@ -35,6 +36,16 @@
35
36
  * @property {{name: string, upToDate: boolean, stale: Array<{path: string, reason: 'missing' | 'outdated'}>, checked: string[]}} data
36
37
  */
37
38
 
39
+ /**
40
+ * xds --json theme build <a> <b> … — several themes in one invocation. Each
41
+ * result carries the file as it was passed and the receipt a single-file build
42
+ * would have returned (null when that theme produced no CSS). One file still
43
+ * returns the bare theme.build / theme.build.check envelope.
44
+ * @typedef {object} ThemeBuildBatchResponse
45
+ * @property {'theme.build.batch'} type
46
+ * @property {{count: number, results: Array<{file: string, receipt: ThemeBuildResponse | ThemeBuildCheckResponse | null}>}} data
47
+ */
48
+
38
49
  /**
39
50
  * A single theme entry as surfaced by `theme list`.
40
51
  * @typedef {object} ThemeListEntry
@@ -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
+ };