@astryxdesign/cli 0.3.0-canary.ec85ba0 → 0.3.0-canary.ee705d8

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 (21) hide show
  1. package/assets/codemods/transforms/next/__tests__/next-codemods.test.mjs +92 -0
  2. package/assets/codemods/transforms/next/index.mjs +11 -1
  3. package/assets/codemods/transforms/next/rename-dropdown-menu-radio-dot-target.mjs +152 -0
  4. package/assets/docs/getting-started.doc.mjs +3 -3
  5. package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +14 -0
  6. package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +24 -0
  7. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +14 -0
  8. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +68 -0
  9. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +14 -0
  10. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +92 -0
  11. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +14 -0
  12. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +41 -0
  13. package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +15 -0
  14. package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +25 -0
  15. package/assets/templates/blocks/components/Stepper/StepperStatus.doc.mjs +14 -0
  16. package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +50 -0
  17. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +14 -0
  18. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +40 -0
  19. package/assets/templates/blocks/components/TextArea/TextAreaStates.tsx +1 -1
  20. package/authoring/doctypes/base/type.ts +6 -0
  21. package/package.json +9 -9
@@ -0,0 +1,92 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Unit tests for the staged (next-release) codemods.
5
+ *
6
+ * Mirrors v0.3.0/__tests__/next-codemods.test.mjs, which covers the codemods
7
+ * after promotion. Keeping a copy here means a staged transform is tested from
8
+ * the day it is written rather than the day it is released.
9
+ */
10
+
11
+ import {describe, expect, it} from 'vitest';
12
+ import jscodeshift from 'jscodeshift';
13
+
14
+ const j = jscodeshift.withParser('tsx');
15
+ const api = {jscodeshift: j, stats: () => {}, report: () => {}};
16
+
17
+ async function apply(name, source) {
18
+ const {default: transform} = await import(`../${name}.mjs`);
19
+ return transform({source, path: 'test.tsx'}, api) ?? source;
20
+ }
21
+
22
+ const TRANSFORM = 'rename-dropdown-menu-radio-dot-target';
23
+
24
+ describe('rename-dropdown-menu-radio-dot-target', () => {
25
+ it('renames the theme target key in a defineTheme components map', async () => {
26
+ const input = `import {defineTheme} from '@astryxdesign/core/theme';
27
+ export const theme = defineTheme({
28
+ name: 'brand',
29
+ components: {
30
+ 'dropdown-menu-radio-dot': {base: {backgroundColor: 'var(--color-accent)'}},
31
+ },
32
+ });`;
33
+ const output = await apply(TRANSFORM, input);
34
+ expect(output).toContain("'radio-indicator-dot':");
35
+ // The old name survives only inside the TODO comment the rename attaches.
36
+ expect(output).not.toContain("'dropdown-menu-radio-dot':");
37
+ });
38
+
39
+ it('warns that the new target is app-wide, not menu-only', async () => {
40
+ const input = `const components = {
41
+ 'dropdown-menu-radio-dot': {base: {width: '10px'}},
42
+ };`;
43
+ const output = await apply(TRANSFORM, input);
44
+ // The rename cannot preserve scope — there is no menu-only dot element
45
+ // left — so the author has to decide, and must be told.
46
+ expect(output).toContain('TODO(astryx upgrade)');
47
+ expect(output).toContain('EVERY radio dot');
48
+ });
49
+
50
+ it('renames the rendered class inside a selector string', async () => {
51
+ const input = `const sel = '.astryx-dropdown-menu-radio-dot';
52
+ const nested = '.astryx-dropdown-menu-radio .astryx-dropdown-menu-radio-dot';`;
53
+ const output = await apply(TRANSFORM, input);
54
+ expect(output).toContain("'.astryx-radio-indicator-dot'");
55
+ expect(output).toContain(
56
+ '.astryx-dropdown-menu-radio .astryx-radio-indicator-dot',
57
+ );
58
+ });
59
+
60
+ it('renames the class inside a template literal', async () => {
61
+ const input =
62
+ 'const css = `.astryx-dropdown-menu-radio-dot { background: ${c}; }`;';
63
+ const output = await apply(TRANSFORM, input);
64
+ expect(output).toContain('.astryx-radio-indicator-dot {');
65
+ expect(output).not.toContain('astryx-dropdown-menu-radio-dot');
66
+ });
67
+
68
+ it('leaves the surviving dropdown-menu-radio target alone', async () => {
69
+ // Only the DOT target was removed; the circle still carries the
70
+ // menu-specific target, so a theme keyed on it must not be rewritten.
71
+ const input = `const components = {
72
+ 'dropdown-menu-radio': {base: {borderWidth: '2px'}},
73
+ };`;
74
+ const output = await apply(TRANSFORM, input);
75
+ expect(output).toContain("'dropdown-menu-radio'");
76
+ expect(output).not.toContain('radio-indicator');
77
+ expect(output).not.toContain('TODO(astryx upgrade)');
78
+ });
79
+
80
+ it('is a no-op on files that never mention the target', async () => {
81
+ const input = `const components = {button: {base: {fontWeight: '600'}}};`;
82
+ expect(await apply(TRANSFORM, input)).toBe(input);
83
+ });
84
+
85
+ it('is idempotent', async () => {
86
+ const input = `const components = {
87
+ 'dropdown-menu-radio-dot': {base: {width: '10px'}},
88
+ };`;
89
+ const once = await apply(TRANSFORM, input);
90
+ expect(await apply(TRANSFORM, once)).toBe(once);
91
+ });
92
+ });
@@ -7,4 +7,14 @@
7
7
  * this file into the resolved version folder.
8
8
  */
9
9
 
10
- export default [];
10
+ import renameDropdownMenuRadioDotTarget, {
11
+ meta as renameDropdownMenuRadioDotTargetMeta,
12
+ } from './rename-dropdown-menu-radio-dot-target.mjs';
13
+
14
+ export default [
15
+ {
16
+ name: 'rename-dropdown-menu-radio-dot-target',
17
+ transform: renameDropdownMenuRadioDotTarget,
18
+ meta: renameDropdownMenuRadioDotTargetMeta,
19
+ },
20
+ ];
@@ -0,0 +1,152 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Codemod: rename the removed `dropdown-menu-radio-dot` theme target
5
+ *
6
+ * The menu radio's dot is no longer drawn by DropdownMenuRadioItem. That row
7
+ * now renders the shared radio indicator, so its dot is the indicator's dot and
8
+ * carries `radio-indicator-dot` (plus the legacy `radio-dot`) instead of the
9
+ * menu-specific `dropdown-menu-radio-dot`, which is gone.
10
+ *
11
+ * Runtime themes are not validated: a theme keyed on the old target keeps
12
+ * compiling and simply stops matching, with no error anywhere. That silence is
13
+ * why this rename needs a codemod rather than a changelog line.
14
+ *
15
+ * The rename is NOT scope-preserving, and that cannot be fixed here — there is
16
+ * no menu-only dot element left to address. `radio-indicator-dot` reaches every
17
+ * radio dot in the app, including RadioList's. So every rewritten site also
18
+ * gets a TODO comment (api.report is a stub; comments are the only warning
19
+ * channel) telling the author to check whether the rule was meant to be
20
+ * menu-only, and pointing at the containing-target route if it was.
21
+ */
22
+
23
+ export const meta = {
24
+ title: 'Rename the removed dropdown-menu-radio-dot theme target',
25
+ description:
26
+ 'Renames the `dropdown-menu-radio-dot` theme target (and the ' +
27
+ '`astryx-dropdown-menu-radio-dot` class it rendered) to ' +
28
+ '`radio-indicator-dot` / `astryx-radio-indicator-dot`. Menu radios draw ' +
29
+ 'the shared radio indicator now, so the menu-specific dot target no ' +
30
+ 'longer exists. The new target is app-wide rather than menu-only, so each ' +
31
+ 'rewritten site gets a TODO comment to confirm that widening is intended.',
32
+ pr: '#4712',
33
+ };
34
+
35
+ const OLD_TARGET = 'dropdown-menu-radio-dot';
36
+ const NEW_TARGET = 'radio-indicator-dot';
37
+ const OLD_CLASS = `astryx-${OLD_TARGET}`;
38
+ const NEW_CLASS = `astryx-${NEW_TARGET}`;
39
+
40
+ const TODO_COMMENT =
41
+ ' TODO(astryx upgrade): `dropdown-menu-radio-dot` became `radio-indicator-dot`,' +
42
+ ' which styles EVERY radio dot, not just the ones in a menu. If this rule was' +
43
+ ' meant to be menu-only, scope it under the containing `dropdown-menu-radio`' +
44
+ ' target instead of this one. ';
45
+
46
+ /**
47
+ * Rewrite one string value, or return null when it holds nothing to rename.
48
+ *
49
+ * Handles the target name on its own (a theme's `components` key) and the
50
+ * rendered class inside a larger string (a selector such as
51
+ * `.astryx-dropdown-menu-radio-dot`, or a className list).
52
+ *
53
+ * @param {string} value
54
+ * @returns {string | null}
55
+ */
56
+ function renameIn(value) {
57
+ if (value === OLD_TARGET) {
58
+ return NEW_TARGET;
59
+ }
60
+ if (value.includes(OLD_CLASS)) {
61
+ return value.split(OLD_CLASS).join(NEW_CLASS);
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * @param {import('../../../../authoring/codemod/type').AstryxCodemodFile} file
68
+ * @param {import('../../../../authoring/codemod/type').CodemodTransformApi} api
69
+ * @returns {string | null | undefined}
70
+ */
71
+ export default function transformer(file, api) {
72
+ // Cheap bail-out: most files in a consumer repo mention neither name.
73
+ if (!file.source.includes(OLD_TARGET)) {
74
+ return undefined;
75
+ }
76
+
77
+ const j = api.jscodeshift;
78
+ const root = j(file.source);
79
+ let hasChanges = false;
80
+
81
+ /** Attach the widening warning once to the nearest statement-ish node. */
82
+ function attachTodo(/** @type {any} */ path) {
83
+ // The property (or its statement) reads better than the bare literal, and
84
+ // matches where a human would look for the note.
85
+ const host =
86
+ path.parent?.node?.type === 'ObjectProperty' ||
87
+ path.parent?.node?.type === 'Property'
88
+ ? path.parent.node
89
+ : path.node;
90
+ if (!host.comments) {
91
+ host.comments = [];
92
+ }
93
+ if (
94
+ host.comments.some((/** @type {any} */ c) => c.value === TODO_COMMENT)
95
+ ) {
96
+ return;
97
+ }
98
+ host.comments.push(j.commentBlock(TODO_COMMENT, true, false));
99
+ }
100
+
101
+ root
102
+ .find(j.StringLiteral)
103
+ .forEach((/** @type {any} */ path) => {
104
+ const renamed = renameIn(path.node.value);
105
+ if (renamed == null) {
106
+ return;
107
+ }
108
+ path.node.value = renamed;
109
+ attachTodo(path);
110
+ hasChanges = true;
111
+ });
112
+
113
+ // Older parsers surface string literals as `Literal`.
114
+ root.find(j.Literal).forEach((/** @type {any} */ path) => {
115
+ if (typeof path.node.value !== 'string') {
116
+ return;
117
+ }
118
+ const renamed = renameIn(path.node.value);
119
+ if (renamed == null) {
120
+ return;
121
+ }
122
+ path.node.value = renamed;
123
+ if (typeof path.node.raw === 'string') {
124
+ path.node.raw = path.node.raw
125
+ .split(OLD_TARGET)
126
+ .join(NEW_TARGET);
127
+ }
128
+ attachTodo(path);
129
+ hasChanges = true;
130
+ });
131
+
132
+ // Template literals carry the class in CSS strings: `.${OLD_CLASS} > span`.
133
+ root.find(j.TemplateElement).forEach((/** @type {any} */ path) => {
134
+ const cooked = path.node.value?.cooked;
135
+ if (typeof cooked !== 'string') {
136
+ return;
137
+ }
138
+ const renamed = renameIn(cooked);
139
+ if (renamed == null) {
140
+ return;
141
+ }
142
+ path.node.value.cooked = renamed;
143
+ path.node.value.raw = path.node.value.raw
144
+ .split(OLD_CLASS)
145
+ .join(NEW_CLASS)
146
+ .split(OLD_TARGET)
147
+ .join(NEW_TARGET);
148
+ hasChanges = true;
149
+ });
150
+
151
+ return hasChanges ? root.toSource({quote: 'single'}) : undefined;
152
+ }
@@ -21,7 +21,7 @@ export const docs = {
21
21
  type: 'code',
22
22
  lang: 'text',
23
23
  label: 'Paste this into your AI',
24
- code: 'Install @astryxdesign/core, @astryxdesign/theme-neutral, and @astryxdesign/cli in this project, then run `npx @astryxdesign/cli init` to set up agent docs. Read the generated files to learn the conventions.',
24
+ code: 'Install @astryxdesign/core, @stylexjs/stylex, @astryxdesign/theme-neutral, and @astryxdesign/cli in this project, then run `npx @astryxdesign/cli init` to set up agent docs. Read the generated files to learn the conventions.',
25
25
  },
26
26
  ],
27
27
  },
@@ -34,13 +34,13 @@ export const docs = {
34
34
  },
35
35
  {
36
36
  type: 'prose',
37
- text: 'Add the core package, a theme, and the CLI to your existing project.',
37
+ text: 'Add the core package and its `@stylexjs/stylex` peer dependency, plus a theme and the CLI.',
38
38
  },
39
39
  {
40
40
  type: 'code',
41
41
  lang: 'bash',
42
42
  label: 'Terminal',
43
- code: `npm install @astryxdesign/core @astryxdesign/theme-neutral @astryxdesign/cli`,
43
+ code: `npm install @astryxdesign/core @stylexjs/stylex @astryxdesign/theme-neutral @astryxdesign/cli`,
44
44
  },
45
45
  {
46
46
  type: 'prose',
@@ -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: 'Stepper',
7
+ name: 'Stepper — Horizontal Progress',
8
+ displayName: 'Stepper — Horizontal Progress',
9
+ description:
10
+ 'A horizontal separated stepper: each step owns a segment of the progress bar above its label. Filled segments track how far the flow has progressed.',
11
+ isReady: true,
12
+ aspectRatio: 16 / 9,
13
+ componentsUsed: ['Stepper'],
14
+ };
@@ -0,0 +1,24 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+
8
+ export default function StepperHorizontal() {
9
+ const [active, setActive] = useState(1);
10
+ return (
11
+ <div style={{width: '100%', maxWidth: 600}}>
12
+ <Stepper
13
+ activeStep={active}
14
+ orientation="horizontal"
15
+ onStepClick={setActive}>
16
+ <Step step={0} label="Workspace" />
17
+ <Step step={1} label="Team" />
18
+ <Step step={2} label="Integrations" />
19
+ <Step step={3} label="Import" />
20
+ <Step step={4} label="Launch" />
21
+ </Stepper>
22
+ </div>
23
+ );
24
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — Indicator Modes',
8
+ displayName: 'Stepper — Indicator Modes',
9
+ description:
10
+ 'The indicator prop side by side: auto (check when done, ring when current, number ahead), always-number, and a custom icon per step.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper', 'Text', 'Icon'],
14
+ };
@@ -0,0 +1,68 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+ import {Text} from '@astryxdesign/core/Text';
8
+ import {Icon} from '@astryxdesign/core/Icon';
9
+
10
+ export default function StepperIndicatorModes() {
11
+ const [active, setActive] = useState(2);
12
+ return (
13
+ <div style={{display: 'flex', gap: 48, flexWrap: 'wrap'}}>
14
+ <div style={{maxWidth: 220}}>
15
+ <Text type="label">Auto</Text>
16
+ <Stepper
17
+ activeStep={active}
18
+ orientation="vertical"
19
+ onStepClick={setActive}>
20
+ <Step step={0} label="Account" />
21
+ <Step step={1} label="Profile" />
22
+ <Step step={2} label="Settings" />
23
+ <Step step={3} label="Review" />
24
+ </Stepper>
25
+ </div>
26
+ <div style={{maxWidth: 220}}>
27
+ <Text type="label">Number</Text>
28
+ <Stepper
29
+ activeStep={active}
30
+ orientation="vertical"
31
+ onStepClick={setActive}>
32
+ <Step step={0} label="Account" indicator="number" />
33
+ <Step step={1} label="Profile" indicator="number" />
34
+ <Step step={2} label="Settings" indicator="number" />
35
+ <Step step={3} label="Review" indicator="number" />
36
+ </Stepper>
37
+ </div>
38
+ <div style={{maxWidth: 220}}>
39
+ <Text type="label">Custom icon</Text>
40
+ <Stepper
41
+ activeStep={active}
42
+ orientation="vertical"
43
+ onStepClick={setActive}>
44
+ <Step
45
+ step={0}
46
+ label="Account"
47
+ icon={<Icon icon="info" size="sm" />}
48
+ />
49
+ <Step
50
+ step={1}
51
+ label="Profile"
52
+ icon={<Icon icon="search" size="sm" />}
53
+ />
54
+ <Step
55
+ step={2}
56
+ label="Settings"
57
+ icon={<Icon icon="wrench" size="sm" />}
58
+ />
59
+ <Step
60
+ step={3}
61
+ label="Review"
62
+ icon={<Icon icon="check" size="sm" />}
63
+ />
64
+ </Stepper>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — Multi-Step Form',
8
+ displayName: 'Stepper — Multi-Step Form',
9
+ description:
10
+ 'A vertical stepper driving a multi-step form. Each step renders its own fields in the content slot for the active step, with Back/Continue buttons advancing activeStep.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper', 'TextInput', 'Button', 'Text'],
14
+ };
@@ -0,0 +1,92 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+ import {TextInput} from '@astryxdesign/core/TextInput';
8
+ import {Button} from '@astryxdesign/core/Button';
9
+ import {Text} from '@astryxdesign/core/Text';
10
+
11
+ export default function StepperMultiStepForm() {
12
+ const [active, setActive] = useState(0);
13
+ return (
14
+ <div style={{width: '100%', maxWidth: 480}}>
15
+ <Stepper
16
+ activeStep={active}
17
+ orientation="vertical"
18
+ onStepClick={setActive}>
19
+ <Step step={0} label="Project details" indicator="number">
20
+ {active === 0 && (
21
+ <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
22
+ <TextInput
23
+ label="Project name"
24
+ placeholder="My awesome project"
25
+ value=""
26
+ />
27
+ <TextInput
28
+ label="Repository URL"
29
+ placeholder="https://github.com/..."
30
+ value=""
31
+ />
32
+ <div>
33
+ <Button
34
+ label="Continue"
35
+ variant="primary"
36
+ onClick={() => setActive(1)}
37
+ />
38
+ </div>
39
+ </div>
40
+ )}
41
+ </Step>
42
+ <Step step={1} label="Environment" indicator="number">
43
+ {active === 1 && (
44
+ <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
45
+ <TextInput label="Node version" placeholder="20" value="" />
46
+ <TextInput
47
+ label="Build command"
48
+ placeholder="npm run build"
49
+ value=""
50
+ />
51
+ <div style={{display: 'flex', gap: 8}}>
52
+ <Button
53
+ label="Back"
54
+ variant="secondary"
55
+ onClick={() => setActive(0)}
56
+ />
57
+ <Button
58
+ label="Continue"
59
+ variant="primary"
60
+ onClick={() => setActive(2)}
61
+ />
62
+ </div>
63
+ </div>
64
+ )}
65
+ </Step>
66
+ <Step step={2} label="Deploy" indicator="number">
67
+ {active === 2 && (
68
+ <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
69
+ <Text type="body">
70
+ Ready to deploy. This creates a production build and pushes to
71
+ your configured hosting.
72
+ </Text>
73
+ <div style={{display: 'flex', gap: 8}}>
74
+ <Button
75
+ label="Back"
76
+ variant="secondary"
77
+ onClick={() => setActive(1)}
78
+ />
79
+ <Button
80
+ label="Deploy now"
81
+ variant="primary"
82
+ onClick={() => setActive(3)}
83
+ />
84
+ </div>
85
+ </div>
86
+ )}
87
+ </Step>
88
+ <Step step={3} label="Done" indicator="number" />
89
+ </Stepper>
90
+ </div>
91
+ );
92
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — On-Track Vertical',
8
+ displayName: 'Stepper — On-Track Vertical',
9
+ description:
10
+ 'The on-track layout in vertical orientation: indicators sit inline on a continuous connector rail, with each label and description beside its node.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper'],
14
+ };
@@ -0,0 +1,41 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+
8
+ export default function StepperOnTrackVertical() {
9
+ const [active, setActive] = useState(2);
10
+ return (
11
+ <div style={{width: '100%', maxWidth: 400}}>
12
+ <Stepper
13
+ activeStep={active}
14
+ orientation="vertical"
15
+ indicatorPosition="on-track"
16
+ onStepClick={setActive}>
17
+ <Step
18
+ step={0}
19
+ label="Create workspace"
20
+ description="Name and configure your workspace"
21
+ />
22
+ <Step
23
+ step={1}
24
+ label="Invite team members"
25
+ description="Add collaborators by email"
26
+ />
27
+ <Step
28
+ step={2}
29
+ label="Set up integrations"
30
+ description="Connect Slack, GitHub, Jira"
31
+ />
32
+ <Step
33
+ step={3}
34
+ label="Import data"
35
+ description="Bring in existing projects"
36
+ />
37
+ <Step step={4} label="Launch" description="Go live with your team" />
38
+ </Stepper>
39
+ </div>
40
+ );
41
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — Checkout Progress',
8
+ displayName: 'Stepper — Checkout Progress',
9
+ description:
10
+ 'A horizontal on-track stepper for a checkout flow: numbered nodes sit on a continuous connector, with completed and current steps filled. Click any step to jump.',
11
+ isReady: true,
12
+ isShowcase: true,
13
+ aspectRatio: 16 / 9,
14
+ componentsUsed: ['Stepper'],
15
+ };
@@ -0,0 +1,25 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+
8
+ export default function StepperShowcase() {
9
+ const [active, setActive] = useState(2);
10
+ return (
11
+ <div style={{width: '100%', maxWidth: 640}}>
12
+ <Stepper
13
+ activeStep={active}
14
+ orientation="horizontal"
15
+ indicatorPosition="on-track"
16
+ onStepClick={setActive}>
17
+ <Step step={0} label="Cart" indicator="number" />
18
+ <Step step={1} label="Shipping" indicator="number" />
19
+ <Step step={2} label="Payment" indicator="number" />
20
+ <Step step={3} label="Review" indicator="number" />
21
+ <Step step={4} label="Confirm" indicator="number" />
22
+ </Stepper>
23
+ </div>
24
+ );
25
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — Validation Status',
8
+ displayName: 'Stepper — Validation Status',
9
+ description:
10
+ 'Semantic status per step in a verification flow: success shows a green check, error a red glyph, accent the in-progress step. Status sets the indicator color and glyph only — never the connector — and is announced to assistive tech as text.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper'],
14
+ };
@@ -0,0 +1,50 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+
8
+ export default function StepperStatus() {
9
+ const [active, setActive] = useState(3);
10
+ return (
11
+ <div style={{width: '100%', maxWidth: 400}}>
12
+ <Stepper
13
+ activeStep={active}
14
+ orientation="vertical"
15
+ onStepClick={setActive}>
16
+ <Step
17
+ step={0}
18
+ label="Email verified"
19
+ description="you@example.com"
20
+ status="success"
21
+ />
22
+ <Step
23
+ step={1}
24
+ label="Phone verified"
25
+ description="+1 (555) 012-3456"
26
+ status="success"
27
+ />
28
+ <Step
29
+ step={2}
30
+ label="Identity document"
31
+ description="Passport upload failed"
32
+ status="error"
33
+ />
34
+ <Step
35
+ step={3}
36
+ label="Address verification"
37
+ description="Pending review"
38
+ status="accent"
39
+ />
40
+ <Step
41
+ step={4}
42
+ label="Background check"
43
+ isOptional
44
+ description="Skipped"
45
+ />
46
+ <Step step={5} label="Account activated" />
47
+ </Stepper>
48
+ </div>
49
+ );
50
+ }
@@ -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: 'Stepper',
7
+ name: 'Stepper — Vertical Onboarding',
8
+ displayName: 'Stepper — Vertical Onboarding',
9
+ description:
10
+ 'A vertical stepper for an onboarding flow, with a label and description per step. The auto indicator shows a check for completed steps, a ring for the current step, and a number for upcoming ones.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['Stepper'],
14
+ };
@@ -0,0 +1,40 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {Stepper, Step} from '@astryxdesign/lab';
7
+
8
+ export default function StepperVerticalOnboarding() {
9
+ const [active, setActive] = useState(2);
10
+ return (
11
+ <div style={{width: '100%', maxWidth: 400}}>
12
+ <Stepper
13
+ activeStep={active}
14
+ orientation="vertical"
15
+ onStepClick={setActive}>
16
+ <Step
17
+ step={0}
18
+ label="Create workspace"
19
+ description="Name and configure your workspace"
20
+ />
21
+ <Step
22
+ step={1}
23
+ label="Invite team members"
24
+ description="Add collaborators by email"
25
+ />
26
+ <Step
27
+ step={2}
28
+ label="Set up integrations"
29
+ description="Connect Slack, GitHub, Jira"
30
+ />
31
+ <Step
32
+ step={3}
33
+ label="Import data"
34
+ description="Bring in existing projects"
35
+ />
36
+ <Step step={4} label="Launch" description="Go live with your team" />
37
+ </Stepper>
38
+ </div>
39
+ );
40
+ }
@@ -20,7 +20,7 @@ export default function TextAreaStates() {
20
20
  />
21
21
  <TextArea
22
22
  label="Disabled field"
23
- value="This field is read-only and cannot be edited."
23
+ value="This field is disabled and cannot be edited."
24
24
  onChange={() => {}}
25
25
  isDisabled
26
26
  />
@@ -260,6 +260,8 @@ export interface ComponentSlotElement {
260
260
  * { property: 'padding', vars: ['--_card-padding'] },
261
261
  * ```
262
262
  */
263
+ // SYNC: apps/docsite/scripts/generate-data.mjs (interface DerivedVar) — see the
264
+ // note on ComponentThemingTarget below.
263
265
  export interface ComponentThemingDerivedVar {
264
266
  /** The standard CSS property name (camelCase) that theme authors write.
265
267
  * e.g. `'borderRadius'`, `'padding'`, `'paddingBlock'` */
@@ -291,6 +293,10 @@ export interface ComponentThemingDerivedVar {
291
293
  * {className: 'astryx-card'}
292
294
  * ```
293
295
  */
296
+ // SYNC: When adding a field here, add it to the docsite's generated-registry
297
+ // copy too — apps/docsite/scripts/generate-data.mjs (interface ThemingTarget).
298
+ // `next build` type-checks the emitted componentRegistry.ts against that copy,
299
+ // so a field present in a .doc.mjs but missing there fails the docsite build.
294
300
  export interface ComponentThemingTarget {
295
301
  /** The stable CSS class name rendered by the component.
296
302
  * Always starts with `astryx-`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.3.0-canary.ec85ba0",
3
+ "version": "0.3.0-canary.ee705d8",
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",
@@ -84,10 +84,10 @@
84
84
  "zod": "^4.4.3"
85
85
  },
86
86
  "peerDependencies": {
87
- "@astryxdesign/charts": "0.3.0-canary.ec85ba0",
88
- "@astryxdesign/core": "0.3.0-canary.ec85ba0",
89
- "@astryxdesign/lab": "0.3.0-canary.ec85ba0",
90
- "@astryxdesign/theme-neutral": "0.3.0-canary.ec85ba0",
87
+ "@astryxdesign/charts": "0.3.0-canary.ee705d8",
88
+ "@astryxdesign/core": "0.3.0-canary.ee705d8",
89
+ "@astryxdesign/lab": "0.3.0-canary.ee705d8",
90
+ "@astryxdesign/theme-neutral": "0.3.0-canary.ee705d8",
91
91
  "gpt-tokenizer": "^3.4.0"
92
92
  },
93
93
  "peerDependenciesMeta": {
@@ -105,10 +105,10 @@
105
105
  }
106
106
  },
107
107
  "devDependencies": {
108
- "@astryxdesign/charts": "0.3.0-canary.ec85ba0",
109
- "@astryxdesign/core": "0.3.0-canary.ec85ba0",
110
- "@astryxdesign/lab": "0.3.0-canary.ec85ba0",
111
- "@astryxdesign/theme-neutral": "0.3.0-canary.ec85ba0",
108
+ "@astryxdesign/charts": "0.3.0-canary.ee705d8",
109
+ "@astryxdesign/core": "0.3.0-canary.ee705d8",
110
+ "@astryxdesign/lab": "0.3.0-canary.ee705d8",
111
+ "@astryxdesign/theme-neutral": "0.3.0-canary.ee705d8",
112
112
  "gpt-tokenizer": "^3.4.0"
113
113
  },
114
114
  "scripts": {