@astryxdesign/cli 0.4.6 → 0.4.7-canary.20bae6c

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 (65) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +53 -51
  3. package/api/docs/docs.doc.mjs +2 -2
  4. package/api/index.d.mts +1 -1
  5. package/api/index.mjs +1 -1
  6. package/api/template/template.doc.mjs +2 -2
  7. package/api/theme/build/build.mjs +8 -38
  8. package/api/theme/targets/targets.d.mts +18 -0
  9. package/api/theme/targets/targets.mjs +87 -0
  10. package/api/theme/targets/targets.test.mjs +65 -0
  11. package/api/theme/theme.d.mts +1 -0
  12. package/api/theme/theme.mjs +3 -1
  13. package/api/theme/theme.type.d.mts +23 -0
  14. package/api/theme/theme.type.mjs +21 -1
  15. package/api/theme/themeTargets.doc.d.mts +11 -0
  16. package/api/theme/themeTargets.doc.mjs +58 -0
  17. package/api/theme/themeTemplate.doc.mjs +3 -3
  18. package/assets/docs/README.md +50 -0
  19. package/assets/docs/cli-integrations.doc.mjs +4 -4
  20. package/assets/docs/theme.doc.dense.mjs +1 -1
  21. package/assets/docs/theme.doc.mjs +1 -1
  22. package/assets/docs/typography.doc.mjs +2 -2
  23. package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.doc.mjs +25 -0
  24. package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.tsx +167 -0
  25. package/assets/templates/blocks/components/Dialog/DialogScrollingContent.tsx +1 -1
  26. package/assets/templates/blocks/components/Step/StepContent.doc.mjs +14 -0
  27. package/assets/templates/blocks/components/Step/StepContent.tsx +32 -0
  28. package/assets/templates/blocks/components/Step/StepIndicator.doc.mjs +14 -0
  29. package/assets/templates/blocks/components/Step/StepIndicator.tsx +60 -0
  30. package/assets/templates/blocks/components/Step/StepShowcase.doc.mjs +15 -0
  31. package/assets/templates/blocks/components/Step/StepShowcase.tsx +26 -0
  32. package/assets/templates/blocks/components/Step/StepStates.doc.mjs +14 -0
  33. package/assets/templates/blocks/components/Step/StepStates.tsx +46 -0
  34. package/assets/templates/blocks/components/Stepper/StepperCustomContent.doc.mjs +22 -0
  35. package/assets/templates/blocks/components/Stepper/StepperCustomContent.tsx +126 -0
  36. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +1 -1
  37. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +17 -5
  38. package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.doc.mjs +14 -0
  39. package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.tsx +25 -0
  40. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +2 -2
  41. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +1 -1
  42. package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +1 -1
  43. package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +6 -7
  44. package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +1 -1
  45. package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
  46. package/assets/templates/themes/neutral/neutralTheme.ts +13 -8
  47. package/clients/cli/commands/build-theme.mjs +85 -0
  48. package/clients/cli/commands/dialog-adaptive-template.test.mjs +24 -0
  49. package/clients/cli/commands/theme-targets.behavior.test.mjs +64 -0
  50. package/clients/cli/commands/theme-targets.doc.mjs +38 -0
  51. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  52. package/clients/cli/commands/theme.doc.mjs +4 -2
  53. package/clients/cli/index.mjs +1 -0
  54. package/clients/cli/lib/manifest.mjs +2 -0
  55. package/foundation/discovery/theming-targets.d.mts +48 -0
  56. package/foundation/discovery/theming-targets.mjs +135 -0
  57. package/foundation/discovery/theming-targets.test.mjs +127 -0
  58. package/foundation/response/response-types.doc.mjs +7 -2
  59. package/package.json +9 -9
  60. package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +0 -14
  61. package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +0 -24
  62. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +0 -14
  63. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +0 -92
  64. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +0 -14
  65. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +0 -40
@@ -15,9 +15,9 @@ export const doc = {
15
15
  displayName: 'themeTemplate()',
16
16
  summary: 'Write the annotated theme template into a project.',
17
17
  description:
18
- 'Writes theme.template.ts: the annotated reference for the whole theme surface every ' +
18
+ 'Writes theme.template.ts: the annotated reference for the whole theme surface, covering every ' +
19
19
  'defineTheme field, the token families, the component override syntax, and how a theme is ' +
20
- 'consumed with the CLI command that prints the authoritative reference for each section. ' +
20
+ 'consumed, with the CLI command that prints the authoritative reference for each section. ' +
21
21
  'Read it, copy what you need into your own theme file, delete it. Where `theme add` starts ' +
22
22
  'you from a theme we ship, this starts you from a blank one. Refuses to overwrite without ' +
23
23
  '`overwrite`, so it is safe to re-run.',
@@ -48,7 +48,7 @@ export const doc = {
48
48
  {
49
49
  type: 'theme.template',
50
50
  description:
51
- 'Receipt: 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, not a failure.',
51
+ 'Receipt: 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, not a failure.',
52
52
  },
53
53
  ],
54
54
  throws: [{code: 'ERR_PATH_TRAVERSAL', when: 'the target path escapes cwd'}],
@@ -0,0 +1,50 @@
1
+ # /packages/cli/assets/docs
2
+
3
+ Reference topics for people **building with** Astryx. Not docs about building Astryx itself.
4
+
5
+ One `{topic}.doc.mjs` per topic, plus optional `{topic}.doc.dense.mjs` / `{topic}.doc.zh.mjs` prose overlays. `foundation/discovery/docs-discovery.mjs` picks up any `{topic}.doc.mjs` here with no registration; `api/docs/_adapter.mjs` merges the overlays.
6
+
7
+ What you add reaches `astryx docs <topic>`, `astryx search`, the `--json` API, the agent-docs block and the doc site — and ships on npm.
8
+
9
+ ## Who you are writing for
10
+
11
+ Someone building a product with Astryx. Their questions:
12
+
13
+ - what a component is for, and when to reach for something else
14
+ - the props, their defaults, and what each does to what they see
15
+ - how to compose it, and the pattern to copy
16
+ - what it costs — bundle size, the a11y obligations they inherit
17
+ - how to theme it, and which targets are stable
18
+
19
+ **The test for anything you add: does a caller act on it?** They are not reviewing a PR, promoting a component out of lab, or attaching evidence to a checklist.
20
+
21
+ ## Tells that you are writing for us instead
22
+
23
+ - second person aimed at the wrong reader — "reviewers should…", "before promoting a component…", "attach evidence for…"
24
+ - **rubric, readiness, gate, audit, checklist, sign-off, promotion, evidence** as things the reader must produce
25
+ - a table of things to verify rather than things to use
26
+ - anything about lab → core, which is our lifecycle, not theirs
27
+ - Storybook, Playwright, CI or the Simulator named as tools the reader runs
28
+
29
+ One subtlety: a statement about the **system's behavior** is caller-facing even when it sounds like process. "A component's theme targets are stable once published" tells a caller what they can rely on; "reviewers must check that theme targets are stable" is ours. Same fact, different reader — **rewrite it rather than move it**.
30
+
31
+ ## Where the rest goes
32
+
33
+ The material is usually good; the finding is placement, not quality. It goes in the [wiki](https://github.com/facebook/astryx/wiki) — **as a section on the page that already covers it, not a new page.** The wiki is at nearly 60 pages, several of them overlapping, because every stray section got its own.
34
+
35
+ | what you wrote | where it goes |
36
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | how a component is graded — checks, scoring | [Component-Audit-Rubric](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric) |
38
+ | lab → core promotion, what a component must satisfy | [Component-Lifecycle](https://github.com/facebook/astryx/wiki/Component-Lifecycle) |
39
+ | how to build a new component | [Component-Authoring-Guide](https://github.com/facebook/astryx/wiki/Component-Authoring-Guide), [Creating-New-Components](https://github.com/facebook/astryx/wiki/Creating-New-Components) |
40
+ | what a component must be hardened against | [Component-Hardening-Protocol](https://github.com/facebook/astryx/wiki/Component-Hardening-Protocol), [Hardening-Audit-Guide](https://github.com/facebook/astryx/wiki/Hardening-Audit-Guide) |
41
+ | a11y requirements as checks we run | [Accessibility-Checklist](https://github.com/facebook/astryx/wiki/Accessibility-Checklist) |
42
+ | how the system is put together | [System-Architecture](https://github.com/facebook/astryx/wiki/System-Architecture), [Theming-Infrastructure](https://github.com/facebook/astryx/wiki/Theming-Infrastructure) |
43
+ | API naming and shape decisions | [API-Conventions](https://github.com/facebook/astryx/wiki/API-Conventions), [API-Arbitration](https://github.com/facebook/astryx/wiki/API-Arbitration) |
44
+ | contributor workflow, PR process | [Contributing](https://github.com/facebook/astryx/wiki/Contributing), [Contributing-with-AI-Assistants](https://github.com/facebook/astryx/wiki/Contributing-with-AI-Assistants) |
45
+ | release mechanics | [Release-Process](https://github.com/facebook/astryx/wiki/Release-Process) |
46
+ | what a nightly agent role does | the Night-Watch pages, from [Night-Watch-Overview](https://github.com/facebook/astryx/wiki/Night-Watch-Overview) |
47
+
48
+ **Fits no row?** It is still not caller-facing. Default it to [Contributing](https://github.com/facebook/astryx/wiki/Contributing), or `CONTRIBUTING.md` when it is a step someone follows with the repo cloned. Never default it back to this directory.
49
+
50
+ Worked example: a responsive-and-interaction readiness rubric is grading criteria → **Component-Audit-Rubric**, or **Component-Lifecycle** if it is a promotion gate.
@@ -115,7 +115,7 @@ export const docs = {
115
115
  content: [
116
116
  {
117
117
  type: 'prose',
118
- text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves `astryx docs` lists it, `astryx docs <topic>` prints it, `astryx search` indexes it, and `astryx init` names it in the agent block. A topic is a plain object stamped `type: 'generic'`, the same shape core's own topics use.",
118
+ text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves: `astryx docs` lists it, `astryx docs <topic>` prints it, `astryx search` indexes it, and `astryx init` names it in the agent block. A topic is a plain object stamped `type: 'generic'`, the same shape core's own topics use.",
119
119
  },
120
120
  {
121
121
  type: 'code',
@@ -124,7 +124,7 @@ export const docs = {
124
124
  },
125
125
  {
126
126
  type: 'prose',
127
- text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x core's, or another integration's so a package whose consumers install it differently can serve its own Getting Started instead of the built-in one. Give the replacement a different `name` and the old name keeps resolving to it, so a link or an agent that learned the old topic still lands in the right place.",
127
+ text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x (core's, or another integration's) so a package whose consumers install it differently can serve its own Getting Started instead of the built-in one. Give the replacement a different `name` and the old name keeps resolving to it, so a link or an agent that learned the old topic still lands in the right place.",
128
128
  },
129
129
  {
130
130
  type: 'code',
@@ -133,14 +133,14 @@ export const docs = {
133
133
  },
134
134
  {
135
135
  type: 'prose',
136
- text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork a fork of someone else's guide stops receiving their fixes the day you write it.",
136
+ text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork: a fork of someone else's guide stops receiving their fixes the day you write it.",
137
137
  },
138
138
  {
139
139
  type: 'list',
140
140
  style: 'unordered',
141
141
  items: [
142
142
  'A topic name is a CLI argument and a docsite path, so it may hold only letters, digits, `_` and `-`.',
143
- "A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override the CLI will not guess which one you meant.",
143
+ "A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override; the CLI will not guess which one you meant.",
144
144
  '`replaces` and `extends` are exclusive: a topic either takes another\'s place or merges onto it.',
145
145
  'Two integrations replacing one topic is a warning, and the one configured later in `astryx.config` wins.',
146
146
  ],
@@ -10,7 +10,7 @@ export const docsDense = {
10
10
  { section: 'Theme Props', title: 'Props', content: [null] },
11
11
  { section: 'Creating a Custom Theme', title: 'Custom Theme', content: [{ type: 'prose', text: '`theme list` + `theme add <slug>` to start from a shipped theme, or defineTheme from scratch. only override tokens that differ.' }, null, { type: 'prose', text: '`astryx theme template` writes theme.template.ts: every defineTheme field + token families + override syntax, annotated, with the CLI command that prints each reference.' }] },
12
12
  { section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent via HCT; accent = hex or [light, dark] tuple (per-scheme palettes). tokens overrides win token-by-token; --color-on-accent stays baked from color.accent, so prefer a tuple accent over overriding --color-accent.' }, null, null] },
13
- { section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-focus-offset etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `astryx component <Name>` for details.' }, null, null, null, null] },
13
+ { section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-focus-offset etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `astryx theme targets [Name]` to enumerate every themeable key (--json for lint), `astryx component <Name>` for one component.' }, null, null, null, null] },
14
14
  { section: 'Custom Variants', title: 'Custom Variants', content: [{ type: 'prose', text: 'any unknown prop:value in components becomes a new variant. astryx theme build generates TS augmentations. works on any extensible prop axis (variant, status, etc).' }, null, null, null, null] },
15
15
  { section: 'Building Themes for Production', title: 'Build for Production', content: [{ type: 'prose', text: 'astryx theme build compiles defineTheme to static CSS. outputs .css + .js (__built:true) + .d.ts.' }, null, null, null, null] },
16
16
  { section: 'Runtime vs Built Themes', title: 'Runtime vs Built', content: [{ type: 'prose', text: 'runtime: useInsertionEffect injects styles client-side. built: static CSS on first paint. USE /built + theme.css FOR SSR.' }, null, null, null] },
@@ -300,7 +300,7 @@ const brandTheme = defineTheme({
300
300
  },
301
301
  {
302
302
  type: 'prose',
303
- text: 'Run `astryx component <Name>` to see a component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
303
+ text: 'Run `astryx theme targets` for every themeable key in the system (`astryx theme targets <Name>` to scope it, `--json` to lint a theme against it), and `astryx component <Name>` for one component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
304
304
  },
305
305
  {
306
306
  type: 'list',
@@ -51,7 +51,7 @@ export const docs = {
51
51
  content: [
52
52
  {
53
53
  type: 'prose',
54
- text: 'Astryx never loads font files. defineTheme and the built CSS only set font-family naming a webfont (Fraunces, JetBrains Mono, ) makes every browser look for it, and quietly fall back when the app has not loaded it. `astryx theme build` warns when a theme names families that are neither CSS generics nor common system fonts and prints the snippet to add; loading the font is always the app\'s job.',
54
+ text: 'Astryx never loads font files. defineTheme and the built CSS only set font-family: naming a webfont (Fraunces, JetBrains Mono, and so on) makes every browser look for it, and quietly fall back when the app has not loaded it. `astryx theme build` warns when a theme names families that are neither CSS generics nor common system fonts and prints the snippet to add; loading the font is always the app\'s job.',
55
55
  },
56
56
  {
57
57
  type: 'code',
@@ -77,7 +77,7 @@ export const docs = {
77
77
  },
78
78
  {
79
79
  type: 'prose',
80
- text: "Always pair a webfont with a real fallback stack metric-similar system fonts plus a generic so text stays readable before the font loads and wherever it never does: defineTheme({typography: {heading: {family: 'Fraunces', fallbacks: 'Georgia, serif'}}}).",
80
+ text: "Always pair a webfont with a real fallback stack (metric-similar system fonts plus a generic) so text stays readable before the font loads and wherever it never does: defineTheme({typography: {heading: {family: 'Fraunces', fallbacks: 'Georgia, serif'}}}).",
81
81
  },
82
82
  ],
83
83
  },
@@ -0,0 +1,25 @@
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: 'Dialog',
7
+ alsoExampleFor: ['BottomSheet', 'useMediaQuery'],
8
+ name: 'Dialog — Adaptive presentation',
9
+ displayName: 'Dialog — Adaptive presentation',
10
+ description:
11
+ 'Opt-in recipe for an AdaptiveDialog wrapper: Dialog remains the default everywhere, while touchPresentation="bottom-sheet" switches only at lg and below when pointer is coarse and hover is unavailable. Includes a deterministic presentation override for tests/unusual environments and notes that BottomSheet purpose controls swipe and scrim dismissal. Usage examples: touchPresentation="dialog" keeps Dialog, "fullscreen" chooses fullscreen Dialog, and "bottom-sheet" chooses BottomSheet only for the touch-oriented range. Keep presentation as the deterministic override. Do not use this by default for AlertDialog or destructive confirmations.',
12
+ isReady: true,
13
+ aspectRatio: 3 / 4,
14
+ componentsUsed: [
15
+ 'Dialog',
16
+ 'DialogHeader',
17
+ 'BottomSheet',
18
+ 'Layout',
19
+ 'Button',
20
+ 'Text',
21
+ 'TextInput',
22
+ 'TextArea',
23
+ 'useMediaQuery',
24
+ ],
25
+ };
@@ -0,0 +1,167 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState, type ReactNode} from 'react';
6
+ import {BottomSheet} from '@astryxdesign/core/BottomSheet';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+ import {
9
+ Dialog,
10
+ DialogHeader,
11
+ type DialogPurpose,
12
+ } from '@astryxdesign/core/Dialog';
13
+ import {Heading} from '@astryxdesign/core/Heading';
14
+ import {
15
+ HStack,
16
+ Layout,
17
+ LayoutContent,
18
+ LayoutFooter,
19
+ VStack,
20
+ } from '@astryxdesign/core/Layout';
21
+ import {Text} from '@astryxdesign/core/Text';
22
+ import {TextArea} from '@astryxdesign/core/TextArea';
23
+ import {TextInput} from '@astryxdesign/core/TextInput';
24
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
25
+
26
+ const TOUCH_ORIENTED_LG_QUERY =
27
+ '(max-width: 1024px) and (pointer: coarse) and (hover: none)';
28
+
29
+ type AdaptivePresentation = 'dialog' | 'fullscreen' | 'bottom-sheet';
30
+
31
+ type AdaptiveDialogProps = {
32
+ isOpen: boolean;
33
+ onOpenChange: (isOpen: boolean) => void;
34
+ title: string;
35
+ children: ReactNode;
36
+ footer?: ReactNode;
37
+ purpose?: DialogPurpose;
38
+ width?: number | string;
39
+ maxHeight?: number | string;
40
+ touchPresentation?: AdaptivePresentation;
41
+ presentation?: AdaptivePresentation;
42
+ bottomSheetHeight?: 'hug' | 'capped' | 'tall' | number | string;
43
+ };
44
+
45
+ function AdaptiveDialog({
46
+ isOpen,
47
+ onOpenChange,
48
+ title,
49
+ children,
50
+ footer,
51
+ purpose = 'info',
52
+ width = 480,
53
+ maxHeight = '75dvh',
54
+ touchPresentation = 'dialog',
55
+ presentation,
56
+ bottomSheetHeight = 'capped',
57
+ }: AdaptiveDialogProps) {
58
+ const isTouchOrientedLargeOrBelow = useMediaQuery(TOUCH_ORIENTED_LG_QUERY);
59
+ const resolvedPresentation =
60
+ presentation ??
61
+ (isTouchOrientedLargeOrBelow ? touchPresentation : 'dialog');
62
+
63
+ if (resolvedPresentation === 'bottom-sheet') {
64
+ return (
65
+ <BottomSheet
66
+ isOpen={isOpen}
67
+ onOpenChange={onOpenChange}
68
+ label={title}
69
+ purpose={purpose}
70
+ height={bottomSheetHeight}>
71
+ <VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
72
+ <Heading level={3}>{title}</Heading>
73
+ {children}
74
+ {footer}
75
+ </VStack>
76
+ </BottomSheet>
77
+ );
78
+ }
79
+
80
+ const dialogContent = (
81
+ <Layout
82
+ header={<DialogHeader title={title} onOpenChange={onOpenChange} />}
83
+ content={<LayoutContent>{children}</LayoutContent>}
84
+ footer={footer ? <LayoutFooter>{footer}</LayoutFooter> : undefined}
85
+ />
86
+ );
87
+
88
+ return (
89
+ <Dialog
90
+ isOpen={isOpen}
91
+ onOpenChange={onOpenChange}
92
+ purpose={purpose}
93
+ width={width}
94
+ maxHeight={maxHeight}
95
+ variant={
96
+ resolvedPresentation === 'fullscreen' ? 'fullscreen' : 'standard'
97
+ }>
98
+ {dialogContent}
99
+ </Dialog>
100
+ );
101
+ }
102
+
103
+ export default function DialogAdaptivePresentation() {
104
+ const [isOpen, setIsOpen] = useState(false);
105
+ const [name, setName] = useState('Ruby Cheung');
106
+ const [email, setEmail] = useState('ruby@example.com');
107
+ const [notes, setNotes] = useState('');
108
+
109
+ return (
110
+ <>
111
+ <Button label="Edit profile" onClick={() => setIsOpen(true)} />
112
+ {/*
113
+ touchPresentation examples:
114
+ - "dialog" keeps Dialog even in touch-oriented <=lg contexts.
115
+ - "fullscreen" uses fullscreen Dialog there.
116
+ - "bottom-sheet" uses BottomSheet there.
117
+ presentation="dialog" | "fullscreen" | "bottom-sheet" overrides
118
+ the media query for tests and unusual environments.
119
+ */}
120
+ <AdaptiveDialog
121
+ isOpen={isOpen}
122
+ onOpenChange={setIsOpen}
123
+ title="Edit profile"
124
+ purpose="form"
125
+ touchPresentation="bottom-sheet"
126
+ bottomSheetHeight="tall"
127
+ footer={
128
+ <HStack gap={2} hAlign="end" wrap="wrap">
129
+ <Button
130
+ label="Cancel"
131
+ variant="secondary"
132
+ onClick={() => setIsOpen(false)}
133
+ />
134
+ <Button
135
+ label="Save profile"
136
+ variant="primary"
137
+ onClick={() => setIsOpen(false)}
138
+ />
139
+ </HStack>
140
+ }>
141
+ <VStack gap={4}>
142
+ <Text type="supporting" color="secondary">
143
+ Dialog remains the default presentation. This example explicitly
144
+ opts into a Bottom Sheet only at lg and below when the device has a
145
+ coarse pointer and no hover. Pass the presentation prop to make
146
+ tests or unusual environments deterministic.
147
+ </Text>
148
+ <Text type="supporting" color="secondary">
149
+ In Bottom Sheet presentation, purpose="form" blocks scrim clicks and
150
+ swipe dismissal while preserving Escape. Use this opt-in only when
151
+ that contract is acceptable; keep AlertDialog/destructive
152
+ confirmations on Dialog unless a product deliberately chooses
153
+ otherwise.
154
+ </Text>
155
+ <TextInput label="Name" value={name} onChange={setName} />
156
+ <TextInput
157
+ label="Email"
158
+ type="email"
159
+ value={email}
160
+ onChange={setEmail}
161
+ />
162
+ <TextArea label="Notes" rows={6} value={notes} onChange={setNotes} />
163
+ </VStack>
164
+ </AdaptiveDialog>
165
+ </>
166
+ );
167
+ }
@@ -69,7 +69,7 @@ function Content({onClose}: {onClose: () => void}) {
69
69
 
70
70
  // Remove isInline for production — dialogs should be modal.
71
71
  export default function DialogScrollingContent() {
72
- const dialog = useImperativeDialog({maxHeight: '50vh'});
72
+ const dialog = useImperativeDialog({maxHeight: '50dvh'});
73
73
 
74
74
  return (
75
75
  <>
@@ -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: 'Step',
7
+ name: 'Step — Content Slot',
8
+ displayName: 'Step — Content Slot',
9
+ description:
10
+ 'Children passed to a Step render below its description, indented to line up with the label rather than the indicator, and stay outside the clickable label area so buttons inside remain their own targets. In a full flow you gate the slot on the step being active — that is what turns a vertical stepper into an expanding one.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper', 'Step', 'TextInput', 'Button'],
14
+ };
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {Stepper, Step} from '@astryxdesign/core/Stepper';
6
+ import {TextInput} from '@astryxdesign/core/TextInput';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+
9
+ export default function StepContent() {
10
+ // Anything passed as children renders below the description, indented to line
11
+ // up with the label rather than the indicator. In a full flow you would gate
12
+ // this on the step being active; here it is a single Step so the slot is
13
+ // always shown.
14
+ return (
15
+ <div style={{width: 400}}>
16
+ <Stepper activeStep={1} orientation="vertical">
17
+ <Step
18
+ step={1}
19
+ label="Billing address"
20
+ description="Used for invoices and tax calculation">
21
+ <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
22
+ <TextInput label="Street" placeholder="1 Hacker Way" value="" />
23
+ <TextInput label="City" placeholder="Menlo Park" value="" />
24
+ <div>
25
+ <Button label="Save address" variant="primary" />
26
+ </div>
27
+ </div>
28
+ </Step>
29
+ </Stepper>
30
+ </div>
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: 'Step',
7
+ name: 'Step — Indicator',
8
+ displayName: 'Step — Indicator',
9
+ description:
10
+ 'Everything the indicator prop accepts: the auto default, an always-number badge, a custom ReactNode, and none — each on its own completed Step so the prop is the only difference between them. Every variant occupies the same 16px box, so a step swapping its number for a check as it completes never shifts the label beside it. The last cell shows that a custom node can be live rather than static: a Spinner on a step that is in progress, shaded `inherit` so it picks up the step\u2019s own tint like any other glyph.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper', 'Step', 'Icon', 'Spinner', 'Text'],
14
+ };
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {Stepper, Step} from '@astryxdesign/core/Stepper';
6
+ import {Text} from '@astryxdesign/core/Text';
7
+ import {Icon} from '@astryxdesign/core/Icon';
8
+ import {Spinner} from '@astryxdesign/core/Spinner';
9
+
10
+ export default function StepIndicator() {
11
+ // The first four steps are all completed — same progress, so the indicator
12
+ // prop is the only difference between them. `auto` is the one that reacts to
13
+ // progress: a numbered badge until the step is reached, then a check. The
14
+ // last step is in progress instead, because the node it passes to `indicator`
15
+ // is a spinner and a spinner only makes sense on work that is running.
16
+ return (
17
+ <div style={{display: 'flex', flexWrap: 'wrap', gap: 24}}>
18
+ <div style={{width: 190}}>
19
+ <Text type="label">auto (default)</Text>
20
+ <Stepper activeStep={1} orientation="vertical">
21
+ <Step step={0} label="Verify email" />
22
+ </Stepper>
23
+ </div>
24
+ <div style={{width: 190}}>
25
+ <Text type="label">number</Text>
26
+ <Stepper activeStep={1} orientation="vertical">
27
+ <Step step={0} label="Verify email" indicator="number" />
28
+ </Stepper>
29
+ </div>
30
+ <div style={{width: 190}}>
31
+ <Text type="label">Custom node</Text>
32
+ <Stepper activeStep={1} orientation="vertical">
33
+ <Step
34
+ step={0}
35
+ label="Verify email"
36
+ indicator={<Icon icon="wrench" size="sm" />}
37
+ />
38
+ </Stepper>
39
+ </div>
40
+ <div style={{width: 190}}>
41
+ <Text type="label">none</Text>
42
+ <Stepper activeStep={1} orientation="vertical">
43
+ <Step step={0} label="Verify email" indicator="none" />
44
+ </Stepper>
45
+ </div>
46
+ <div style={{width: 190}}>
47
+ <Text type="label">Spinner (in progress)</Text>
48
+ <Stepper activeStep={0} orientation="vertical">
49
+ <Step
50
+ step={0}
51
+ label="Verify email"
52
+ // `inherit` picks up the indicator's own tint, so the spinner is
53
+ // colored by the step's progress and status like any other glyph.
54
+ indicator={<Spinner size="md" shade="inherit" />}
55
+ />
56
+ </Stepper>
57
+ </div>
58
+ </div>
59
+ );
60
+ }
@@ -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: 'Step',
7
+ name: 'Step',
8
+ displayName: 'Step',
9
+ description:
10
+ 'A single Step, with every part it can render: the indicator, the label with its optional marker and trailing endContent, and the description beneath. A Step never sets its own completed/current state — it declares its index and derives the rest from the parent Stepper, so one Step in one Stepper is a complete example.',
11
+ isReady: true,
12
+ isShowcase: true,
13
+ aspectRatio: 16 / 9,
14
+ componentsUsed: ['Stepper', 'Step', 'Text'],
15
+ };
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {Stepper, Step} from '@astryxdesign/core/Stepper';
6
+ import {Text} from '@astryxdesign/core/Text';
7
+
8
+ export default function StepShowcase() {
9
+ // One Step, and every part it renders: the indicator, the label with its
10
+ // optional marker and trailing slot, and the description beneath. A Step is
11
+ // always wrapped in a Stepper — that is where it reads its progress from —
12
+ // so a single Step in a single Stepper is the smallest complete example.
13
+ return (
14
+ <div style={{width: 380}}>
15
+ <Stepper activeStep={1} orientation="vertical">
16
+ <Step
17
+ step={1}
18
+ label="Connect a repository"
19
+ description="Astryx reads your build settings from the default branch"
20
+ endContent={<Text type="supporting">2 min</Text>}
21
+ isOptional
22
+ />
23
+ </Stepper>
24
+ </div>
25
+ );
26
+ }
@@ -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: 'Step',
7
+ name: 'Step — States',
8
+ displayName: 'Step — States',
9
+ description:
10
+ 'Every state a single Step can land in, each shown as one Step in its own Stepper. Completed, current, and upcoming are derived by comparing the step index against the parent activeStep, so they are never set directly; isDisabled and status are the two a step declares itself. Status is a separate axis from progress, which is why a completed step can still carry a warning.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper', 'Step', 'Text'],
14
+ };
@@ -0,0 +1,46 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {Stepper, Step} from '@astryxdesign/core/Stepper';
6
+ import {Text} from '@astryxdesign/core/Text';
7
+
8
+ export default function StepStates() {
9
+ // A Step holds no state of its own — it compares its `step` index against the
10
+ // Stepper's `activeStep`. Each state below is therefore one Step in its own
11
+ // Stepper, with activeStep placed on one side of it or the other.
12
+ return (
13
+ <div style={{display: 'flex', flexWrap: 'wrap', gap: 32}}>
14
+ <div style={{width: 220}}>
15
+ <Text type="label">Completed</Text>
16
+ <Stepper activeStep={1} orientation="vertical">
17
+ <Step step={0} label="Verify email" />
18
+ </Stepper>
19
+ </div>
20
+ <div style={{width: 220}}>
21
+ <Text type="label">Current</Text>
22
+ <Stepper activeStep={0} orientation="vertical">
23
+ <Step step={0} label="Verify email" />
24
+ </Stepper>
25
+ </div>
26
+ <div style={{width: 220}}>
27
+ <Text type="label">Upcoming</Text>
28
+ <Stepper activeStep={0} orientation="vertical">
29
+ <Step step={1} label="Add a payment method" />
30
+ </Stepper>
31
+ </div>
32
+ <div style={{width: 220}}>
33
+ <Text type="label">Disabled</Text>
34
+ <Stepper activeStep={0} orientation="vertical" onStepClick={() => {}}>
35
+ <Step step={1} label="Add a payment method" isDisabled />
36
+ </Stepper>
37
+ </div>
38
+ <div style={{width: 220}}>
39
+ <Text type="label">Completed, with a status</Text>
40
+ <Stepper activeStep={1} orientation="vertical">
41
+ <Step step={0} label="Verify email" status="warning" />
42
+ </Stepper>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — Custom Content',
8
+ displayName: 'Stepper — Custom Content',
9
+ description:
10
+ 'A vertical stepper where each step owns a slice of the page. The content slot takes any node — form fields, a summary panel, a banner — so a stepper is not limited to multi-step forms. Rendering the slot only for the active step is what makes the flow expand one step at a time.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: [
14
+ 'Stepper',
15
+ 'TextInput',
16
+ 'Button',
17
+ 'Text',
18
+ 'Card',
19
+ 'Badge',
20
+ 'Banner',
21
+ ],
22
+ };