@astryxdesign/cli 0.1.6 → 0.1.7-canary.04cd8f7

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 (107) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +115 -19
  3. package/docs/cli-integrations.doc.mjs +150 -0
  4. package/docs/getting-started.doc.mjs +9 -9
  5. package/docs/internationalization.doc.mjs +243 -0
  6. package/docs/layout.doc.dense.mjs +5 -0
  7. package/docs/migration.doc.mjs +18 -18
  8. package/docs/principles.doc.dense.mjs +5 -5
  9. package/docs/principles.doc.mjs +6 -6
  10. package/docs/principles.doc.zh.mjs +5 -5
  11. package/docs/styling-libraries.doc.mjs +3 -3
  12. package/docs/styling.doc.mjs +4 -4
  13. package/docs/theme.doc.dense.mjs +12 -12
  14. package/docs/theme.doc.mjs +7 -7
  15. package/docs/theme.doc.zh.mjs +10 -10
  16. package/docs/tokens.doc.dense.mjs +6 -7
  17. package/docs/tokens.doc.mjs +1 -1
  18. package/docs/tokens.doc.zh.mjs +6 -7
  19. package/docs/working-with-ai.doc.mjs +18 -18
  20. package/package.json +13 -10
  21. package/src/api/docOverlays.test.mjs +133 -0
  22. package/src/api/docs.mjs +14 -2
  23. package/src/api/doctor.mjs +3 -3
  24. package/src/api/integration-block-exports.test.mjs +240 -0
  25. package/src/api/template-suffix.test.mjs +246 -0
  26. package/src/api/template.mjs +104 -28
  27. package/src/api/validate-integration.mjs +0 -8
  28. package/src/codemods/__tests__/registry.test.mjs +1 -0
  29. package/src/codemods/ensure-jscodeshift.mjs +11 -27
  30. package/src/codemods/registry.mjs +1 -0
  31. package/src/codemods/run-codemod.mjs +1 -1
  32. package/src/codemods/runner.mjs +2 -2
  33. package/src/codemods/transforms/v0.1.7/__tests__/migrate-table-tableprops-to-direct-props.test.mjs +120 -0
  34. package/src/codemods/transforms/v0.1.7/__tests__/rename-table-renderprops-styles-to-xstyle.test.mjs +112 -0
  35. package/src/codemods/transforms/v0.1.7/index.mjs +27 -0
  36. package/src/codemods/transforms/v0.1.7/migrate-table-tableprops-to-direct-props.mjs +188 -0
  37. package/src/codemods/transforms/v0.1.7/rename-table-renderprops-styles-to-xstyle.mjs +197 -0
  38. package/src/commands/agent-docs.mjs +21 -8
  39. package/src/commands/agent-docs.test.mjs +31 -4
  40. package/src/commands/build-theme.mjs +10 -71
  41. package/src/commands/build.mjs +15 -15
  42. package/src/commands/component/index.mjs +4 -4
  43. package/src/commands/discover.mjs +7 -5
  44. package/src/commands/docs.mjs +4 -4
  45. package/src/commands/hook/index.mjs +4 -4
  46. package/src/commands/init.mjs +48 -152
  47. package/src/commands/init.next-steps.test.mjs +1 -1
  48. package/src/commands/interactive-guard.test.mjs +19 -22
  49. package/src/commands/json-contract.test.mjs +1 -1
  50. package/src/commands/layout.mjs +1 -1
  51. package/src/commands/search.mjs +4 -4
  52. package/src/commands/swizzle.mjs +11 -34
  53. package/src/commands/template.mjs +11 -31
  54. package/src/commands/upgrade.mjs +9 -6
  55. package/src/commands/upgrade.test.mjs +1 -1
  56. package/src/config.mjs +5 -14
  57. package/src/doc.mjs +27 -0
  58. package/src/doc.test.mjs +383 -0
  59. package/src/index.mjs +5 -6
  60. package/src/integration.mjs +4 -15
  61. package/src/lib/component-discovery.importpath.test.mjs +59 -0
  62. package/src/lib/component-discovery.mjs +15 -5
  63. package/src/lib/component-format.mjs +47 -14
  64. package/src/lib/component-format.test.mjs +95 -1
  65. package/src/lib/component-loader.mjs +104 -2
  66. package/src/lib/componentDocOverlay.test.mjs +111 -0
  67. package/src/lib/config-schema.mjs +0 -30
  68. package/src/lib/hook-format.mjs +8 -3
  69. package/src/lib/term-log.mjs +48 -0
  70. package/src/lib/xle/registry.mjs +0 -5
  71. package/src/schemas/doc-schema.mjs +226 -0
  72. package/src/schemas/template-schema.mjs +47 -0
  73. package/src/template.mjs +9 -67
  74. package/src/types/config.d.ts +11 -66
  75. package/src/types/doc.d.ts +23 -0
  76. package/src/types/integration.d.ts +7 -18
  77. package/src/types/template-api.d.ts +14 -50
  78. package/src/utils/package-manager.mjs +78 -0
  79. package/src/utils/package-manager.test.mjs +108 -1
  80. package/src/utils/path-safety.mjs +0 -18
  81. package/src/utils/update-check.mjs +2 -1
  82. package/templates/blocks/components/Avatar/AvatarGroup.tsx +5 -7
  83. package/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -6
  84. package/templates/blocks/components/Avatar/AvatarUserCard.tsx +3 -5
  85. package/templates/blocks/components/Avatar/AvatarWithImage.tsx +8 -6
  86. package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -5
  87. package/templates/blocks/components/ChatComposerInput/ChatComposerInputControlledInput.tsx +1 -1
  88. package/templates/blocks/components/ChatComposerInput/ChatComposerInputDisabled.tsx +1 -1
  89. package/templates/blocks/components/ChatComposerInput/ChatComposerInputMentionTrigger.tsx +1 -1
  90. package/templates/blocks/components/ChatComposerInput/ChatComposerInputMultipleTriggers.tsx +1 -1
  91. package/templates/blocks/components/ChatComposerInput/ChatComposerInputShowcase.tsx +1 -1
  92. package/templates/blocks/components/ChatComposerInput/ChatComposerInputSlashCommands.tsx +1 -1
  93. package/templates/blocks/components/TabList/TabListTabsWithActions.doc.mjs +1 -1
  94. package/templates/blocks/components/TabList/TabListTabsWithActions.tsx +2 -7
  95. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenLiveRegion.doc.mjs +14 -0
  96. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenLiveRegion.tsx +41 -0
  97. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenShowcase.doc.mjs +13 -0
  98. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenShowcase.tsx +78 -0
  99. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenStructuralHeading.doc.mjs +14 -0
  100. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenStructuralHeading.tsx +38 -0
  101. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenSupplementaryContext.doc.mjs +14 -0
  102. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenSupplementaryContext.tsx +47 -0
  103. package/templates/pages/ide/page.tsx +35 -41
  104. package/templates/pages/theme-showcase/page.tsx +7 -7
  105. package/templates/themes/neutral/neutralTheme.ts +63 -32
  106. package/src/utils/interactive.mjs +0 -76
  107. package/src/utils/interactive.test.mjs +0 -70
@@ -4,9 +4,16 @@ import {describe, it, expect, afterEach, vi} from 'vitest';
4
4
  import * as fs from 'node:fs';
5
5
  import * as path from 'node:path';
6
6
  import * as os from 'node:os';
7
- import {detectPackageManager} from './package-manager.mjs';
7
+ import {
8
+ detectPackageManager,
9
+ getDlxPrefix,
10
+ isCliOneOff,
11
+ getCliInvocation,
12
+ formatCliCommand,
13
+ } from './package-manager.mjs';
8
14
 
9
15
  let tmpDir;
16
+ const ORIGINAL_ARGV1 = process.argv[1];
10
17
 
11
18
  afterEach(() => {
12
19
  if (tmpDir) {
@@ -15,6 +22,7 @@ afterEach(() => {
15
22
  }
16
23
  vi.restoreAllMocks();
17
24
  delete process.env.npm_config_user_agent;
25
+ process.argv[1] = ORIGINAL_ARGV1;
18
26
  });
19
27
 
20
28
  function makeTmpDir() {
@@ -111,3 +119,102 @@ describe('detectPackageManager', () => {
111
119
  expect(detectPackageManager(dir)).toBe('bun');
112
120
  });
113
121
  });
122
+
123
+ describe('getDlxPrefix', () => {
124
+ it('returns "pnpm dlx" for pnpm projects', () => {
125
+ const dir = makeTmpDir();
126
+ fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
127
+ expect(getDlxPrefix(dir)).toBe('pnpm dlx');
128
+ });
129
+
130
+ it('returns "yarn dlx" for yarn projects', () => {
131
+ const dir = makeTmpDir();
132
+ fs.writeFileSync(path.join(dir, 'yarn.lock'), '');
133
+ expect(getDlxPrefix(dir)).toBe('yarn dlx');
134
+ });
135
+
136
+ it('returns "bunx" for bun projects', () => {
137
+ const dir = makeTmpDir();
138
+ fs.writeFileSync(path.join(dir, 'bun.lockb'), '');
139
+ expect(getDlxPrefix(dir)).toBe('bunx');
140
+ });
141
+
142
+ it('falls back to "npx" with no signals', () => {
143
+ const dir = makeTmpDir();
144
+ delete process.env.npm_config_user_agent;
145
+ expect(getDlxPrefix(dir)).toBe('npx');
146
+ });
147
+ });
148
+
149
+ describe('isCliOneOff', () => {
150
+ it('detects an npm npx cache entry', () => {
151
+ process.argv[1] = '/home/u/.npm/_npx/a1b2/node_modules/.bin/astryx';
152
+ expect(isCliOneOff()).toBe(true);
153
+ });
154
+
155
+ it('detects a pnpm dlx cache entry', () => {
156
+ process.argv[1] = '/home/u/.cache/pnpm/dlx/9f/node_modules/@astryxdesign/cli/bin/astryx.mjs';
157
+ expect(isCliOneOff()).toBe(true);
158
+ });
159
+
160
+ it('detects a bunx cache entry', () => {
161
+ process.argv[1] = '/home/u/.bun/install/cache/@astryxdesign/cli/bin/astryx.mjs';
162
+ expect(isCliOneOff()).toBe(true);
163
+ });
164
+
165
+ it('is false for an installed node_modules entry', () => {
166
+ process.argv[1] = '/proj/node_modules/@astryxdesign/cli/bin/astryx.mjs';
167
+ expect(isCliOneOff()).toBe(false);
168
+ });
169
+
170
+ it('is false for a source checkout (dev) entry', () => {
171
+ process.argv[1] = '/repo/packages/cli/bin/astryx.mjs';
172
+ expect(isCliOneOff()).toBe(false);
173
+ });
174
+ });
175
+
176
+ describe('getCliInvocation', () => {
177
+ it('uses the run-prefix + bare bin when installed (not one-off)', () => {
178
+ process.argv[1] = '/proj/node_modules/@astryxdesign/cli/bin/astryx.mjs';
179
+ const dir = makeTmpDir();
180
+ fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
181
+ expect(getCliInvocation(dir)).toBe('pnpm exec astryx');
182
+ });
183
+
184
+ it('uses the dlx runner + scoped package when run one-off', () => {
185
+ process.argv[1] = '/home/u/.npm/_npx/a1b2/node_modules/.bin/astryx';
186
+ const dir = makeTmpDir();
187
+ delete process.env.npm_config_user_agent;
188
+ expect(getCliInvocation(dir)).toBe('npx @astryxdesign/cli');
189
+ });
190
+
191
+ it('pairs the dlx runner with the scoped package for pnpm one-off', () => {
192
+ process.argv[1] = '/home/u/.cache/pnpm/dlx/9f/node_modules/@astryxdesign/cli/bin/astryx.mjs';
193
+ const dir = makeTmpDir();
194
+ fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
195
+ expect(getCliInvocation(dir)).toBe('pnpm dlx @astryxdesign/cli');
196
+ });
197
+ });
198
+
199
+ describe('formatCliCommand', () => {
200
+ it('strips a leading "astryx" token and prepends the invocation stem', () => {
201
+ process.argv[1] = '/proj/node_modules/@astryxdesign/cli/bin/astryx.mjs';
202
+ const dir = makeTmpDir();
203
+ fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
204
+ expect(formatCliCommand('astryx component Button', dir)).toBe('pnpm exec astryx component Button');
205
+ });
206
+
207
+ it('accepts a bare subcommand (no leading astryx)', () => {
208
+ process.argv[1] = '/proj/node_modules/@astryxdesign/cli/bin/astryx.mjs';
209
+ const dir = makeTmpDir();
210
+ fs.writeFileSync(path.join(dir, 'package-lock.json'), '{}');
211
+ expect(formatCliCommand('docs tokens', dir)).toBe('npx astryx docs tokens');
212
+ });
213
+
214
+ it('rewrites to the scoped package for one-off invocations', () => {
215
+ process.argv[1] = '/home/u/.npm/_npx/a1b2/node_modules/.bin/astryx';
216
+ const dir = makeTmpDir();
217
+ fs.writeFileSync(path.join(dir, 'package-lock.json'), '{}');
218
+ expect(formatCliCommand('astryx component Button', dir)).toBe('npx @astryxdesign/cli component Button');
219
+ });
220
+ });
@@ -165,21 +165,3 @@ export function isFilePathArg(pathArg) {
165
165
  const ext = path.extname(base).toLowerCase();
166
166
  return ext.length > 0 && FILE_EXTENSIONS.has(ext);
167
167
  }
168
-
169
- /**
170
- * True when the process is running non-interactively (no TTY) or when the
171
- * caller has signaled JSON / scripted use. Commands consult this before
172
- * prompting for confirmation; in scripted mode they require an explicit
173
- * `--overwrite` flag instead.
174
- *
175
- * @param {object} [options]
176
- * @param {boolean} [options.json] - Caller's --json flag.
177
- * @returns {boolean}
178
- */
179
- export function isNonInteractive({json = false} = {}) {
180
- if (json) return true;
181
- // stdin not a TTY means piped input or scripted execution.
182
- if (process.stdin && process.stdin.isTTY === false) return true;
183
- if (process.stdout && process.stdout.isTTY === false) return true;
184
- return false;
185
- }
@@ -14,6 +14,7 @@
14
14
  import * as fs from 'node:fs';
15
15
  import * as path from 'node:path';
16
16
  import {semverGt} from './semver.mjs';
17
+ import {getCliInvocation} from './package-manager.mjs';
17
18
 
18
19
  /**
19
20
  * Read the latest available version from local signals.
@@ -75,7 +76,7 @@ export function checkForUpdate(cwd = process.cwd()) {
75
76
  // Use semver-aware comparison so '0.0.20' is correctly treated as greater
76
77
  // than '0.0.5' (lexicographic compare gets that backwards).
77
78
  if (semverGt(latest, installed)) {
78
- return `FYI: A newer version of @astryxdesign/core (${latest}) is available. Install the new package version, then run: astryx upgrade --from <old-version> --apply`;
79
+ return `FYI: A newer version of @astryxdesign/core (${latest}) is available. Install the new package version, then run: ${getCliInvocation()} upgrade --from <old-version> --apply`;
79
80
  }
80
81
 
81
82
  return null;
@@ -7,28 +7,26 @@ import {AvatarGroup, AvatarGroupOverflow} from '@astryxdesign/core/AvatarGroup';
7
7
  import {Stack} from '@astryxdesign/core/Layout';
8
8
  import {Text} from '@astryxdesign/core/Text';
9
9
 
10
- const CDN = 'https://lookaside.facebook.com/assets/astryx';
11
-
12
10
  const USERS = [
13
11
  {
14
12
  name: 'Ami Pena',
15
- src: `${CDN}/DATA-Ami-Pena.png`,
13
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Ami-Pena.png',
16
14
  },
17
15
  {
18
16
  name: 'Drew Young',
19
- src: `${CDN}/DATA-Drew-Young.png`,
17
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Drew-Young.png',
20
18
  },
21
19
  {
22
20
  name: 'Gabriela Fernandez',
23
- src: `${CDN}/DATA-Gabriela-Fernandez.png`,
21
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Gabriela-Fernandez.png',
24
22
  },
25
23
  {
26
24
  name: 'Jihoo Song',
27
- src: `${CDN}/DATA-Jihoo-Song.png`,
25
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Jihoo-Song.png',
28
26
  },
29
27
  {
30
28
  name: 'Nam Tran',
31
- src: `${CDN}/DATA-Nam-Tran.png`,
29
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Nam-Tran.png',
32
30
  },
33
31
  ];
34
32
 
@@ -5,29 +5,27 @@
5
5
  import {Avatar, AvatarStatusDot} from '@astryxdesign/core/Avatar';
6
6
  import {Stack} from '@astryxdesign/core/Layout';
7
7
 
8
- const CDN = 'https://lookaside.facebook.com/assets/astryx';
9
-
10
8
  export default function AvatarShowcase() {
11
9
  return (
12
10
  <Stack direction="horizontal" gap={4} vAlign="center">
13
11
  <Avatar
14
- src={`${CDN}/DATA-Ana-Thomas.png`}
12
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Ana-Thomas.png"
15
13
  name="Ana Thomas"
16
14
  size="large"
17
15
  status={<AvatarStatusDot variant="success" label="Online" />}
18
16
  />
19
17
  <Avatar
20
- src={`${CDN}/DATA-Drew-Young.png`}
18
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Drew-Young.png"
21
19
  name="Drew Young"
22
20
  size="large"
23
21
  />
24
22
  <Avatar
25
- src={`${CDN}/DATA-Jihoo-Song.png`}
23
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Jihoo-Song.png"
26
24
  name="Jihoo Song"
27
25
  size="large"
28
26
  />
29
27
  <Avatar
30
- src={`${CDN}/DATA-Nam-Tran.png`}
28
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Nam-Tran.png"
31
29
  name="Nam Tran"
32
30
  size="large"
33
31
  status={<AvatarStatusDot variant="error" label="Online" />}
@@ -6,24 +6,22 @@ import {Avatar, AvatarStatusDot} from '@astryxdesign/core/Avatar';
6
6
  import {Stack} from '@astryxdesign/core/Layout';
7
7
  import {Text} from '@astryxdesign/core/Text';
8
8
 
9
- const CDN = 'https://lookaside.facebook.com/assets/astryx';
10
-
11
9
  const USERS = [
12
10
  {
13
11
  name: 'Itai Jordaan',
14
- src: `${CDN}/DATA-Itai-Jordaan.png`,
12
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Itai-Jordaan.png',
15
13
  role: 'Engineering Lead',
16
14
  variant: 'success' as const,
17
15
  },
18
16
  {
19
17
  name: 'Margot Schroder',
20
- src: `${CDN}/DATA-Margot-Schroder.png`,
18
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Margot-Schroder.png',
21
19
  role: 'Product Designer',
22
20
  variant: 'neutral' as const,
23
21
  },
24
22
  {
25
23
  name: 'Daniela Gimenez',
26
- src: `${CDN}/DATA-Daniela-Gimenez.png`,
24
+ src: 'https://lookaside.facebook.com/assets/astryx/DATA-Daniela-Gimenez.png',
27
25
  role: 'Engineering Manager',
28
26
  variant: 'error' as const,
29
27
  },
@@ -5,24 +5,26 @@
5
5
  import {Avatar} from '@astryxdesign/core/Avatar';
6
6
  import {Stack} from '@astryxdesign/core/Layout';
7
7
 
8
- const CDN = 'https://lookaside.facebook.com/assets/astryx';
9
-
10
8
  export default function AvatarWithImage() {
11
9
  return (
12
10
  <Stack direction="horizontal" gap={4} vAlign="center">
13
- <Avatar src={`${CDN}/DATA-Ami-Pena.png`} name="Ami Pena" size="tiny" />
14
11
  <Avatar
15
- src={`${CDN}/DATA-Ana-Thomas.png`}
12
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Ami-Pena.png"
13
+ name="Ami Pena"
14
+ size="tiny"
15
+ />
16
+ <Avatar
17
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Ana-Thomas.png"
16
18
  name="Ana Thomas"
17
19
  size="small"
18
20
  />
19
21
  <Avatar
20
- src={`${CDN}/DATA-Daniela-Gimenez.png`}
22
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Daniela-Gimenez.png"
21
23
  name="Daniela Gimenez"
22
24
  size="medium"
23
25
  />
24
26
  <Avatar
25
- src={`${CDN}/DATA-Gabriela-Fernandez.png`}
27
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Gabriela-Fernandez.png"
26
28
  name="Gabriela Fernandez"
27
29
  size="large"
28
30
  />
@@ -5,25 +5,23 @@
5
5
  import {Avatar, AvatarStatusDot} from '@astryxdesign/core/Avatar';
6
6
  import {Stack} from '@astryxdesign/core/Layout';
7
7
 
8
- const CDN = 'https://lookaside.facebook.com/assets/astryx';
9
-
10
8
  export default function AvatarWithStatus() {
11
9
  return (
12
10
  <Stack direction="horizontal" gap={4} vAlign="center">
13
11
  <Avatar
14
- src={`${CDN}/DATA-Itai-Jordaan.png`}
12
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Itai-Jordaan.png"
15
13
  name="Itai Jordaan"
16
14
  size="large"
17
15
  status={<AvatarStatusDot variant="success" label="Online" />}
18
16
  />
19
17
  <Avatar
20
- src={`${CDN}/DATA-Margot-Schroder.png`}
18
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Margot-Schroder.png"
21
19
  name="Margot Schroder"
22
20
  size="large"
23
21
  status={<AvatarStatusDot variant="neutral" label="Offline" />}
24
22
  />
25
23
  <Avatar
26
- src={`${CDN}/DATA-Pablo-Morales.png`}
24
+ src="https://lookaside.facebook.com/assets/astryx/DATA-Pablo-Morales.png"
27
25
  name="Pablo Morales"
28
26
  size="large"
29
27
  status={<AvatarStatusDot variant="error" label="Busy" />}
@@ -10,7 +10,7 @@ import {Text} from '@astryxdesign/core/Text';
10
10
  export default function ChatComposerInputControlledInput() {
11
11
  const [value, setValue] = useState('');
12
12
  return (
13
- <Stack direction="vertical" gap={3} style={{width: '100%', maxWidth: 450}}>
13
+ <Stack direction="vertical" gap={3} style={{width: 450, maxWidth: '100%'}}>
14
14
  <ChatComposer
15
15
  onSubmit={() => setValue('')}
16
16
  value={value}
@@ -7,7 +7,7 @@ import {Stack} from '@astryxdesign/core/Layout';
7
7
 
8
8
  export default function ChatComposerInputDisabled() {
9
9
  return (
10
- <Stack direction="vertical" style={{width: '100%', maxWidth: 450}}>
10
+ <Stack direction="vertical" style={{width: 450, maxWidth: '100%'}}>
11
11
  <ChatComposer
12
12
  onSubmit={() => {}}
13
13
  isDisabled
@@ -43,7 +43,7 @@ export default function ChatComposerInputMentionTrigger() {
43
43
  };
44
44
 
45
45
  return (
46
- <Stack direction="vertical" gap={3} style={{width: '100%', maxWidth: 450}}>
46
+ <Stack direction="vertical" gap={3} style={{width: 450, maxWidth: '100%'}}>
47
47
  <ChatComposer
48
48
  onSubmit={() => setValue('')}
49
49
  input={
@@ -54,7 +54,7 @@ export default function ChatComposerInputMultipleTriggers() {
54
54
  };
55
55
 
56
56
  return (
57
- <Stack direction="vertical" gap={3} style={{width: '100%', maxWidth: 450}}>
57
+ <Stack direction="vertical" gap={3} style={{width: 450, maxWidth: '100%'}}>
58
58
  <Text type="supporting" color="secondary">
59
59
  Type @ for mentions (blue) or / for commands (yellow)
60
60
  </Text>
@@ -7,7 +7,7 @@ import {Stack} from '@astryxdesign/core/Layout';
7
7
 
8
8
  export default function ChatComposerInputShowcase() {
9
9
  return (
10
- <Stack direction="vertical" style={{width: '100%', maxWidth: 450}}>
10
+ <Stack direction="vertical" style={{width: 450, maxWidth: '100%'}}>
11
11
  <ChatComposer
12
12
  onSubmit={() => {}}
13
13
  input={
@@ -40,7 +40,7 @@ export default function ChatComposerInputSlashCommands() {
40
40
  };
41
41
 
42
42
  return (
43
- <Stack direction="vertical" style={{width: '100%', maxWidth: 450}}>
43
+ <Stack direction="vertical" style={{width: 450, maxWidth: '100%'}}>
44
44
  <ChatComposer
45
45
  onSubmit={() => {}}
46
46
  input={
@@ -7,7 +7,7 @@ export const doc = {
7
7
  name: 'TabList — With Actions',
8
8
  displayName: 'TabList — With Actions',
9
9
  description:
10
- 'Page header pattern with tabs on the left and action buttons pushed to the right. When hasDivider is true, pair with a smaller button size (sm) so actions don\'t overpower the tab row.',
10
+ 'Page header pattern with tabs on the left and action buttons pushed to the right. When hasDivider is true, match the Button size to the TabList size so the tabs and actions align to a shared baseline above the divider.',
11
11
  isReady: true,
12
12
  aspectRatio: 4 / 3,
13
13
  componentsUsed: ['TabList', 'Tab', 'Button'],
@@ -55,16 +55,11 @@ export default function TabListTabsWithActions() {
55
55
  <Button
56
56
  label="Filter"
57
57
  variant="ghost"
58
- size="sm"
58
+ size="lg"
59
59
  icon={FilterIcon}
60
60
  isIconOnly
61
61
  />
62
- <Button
63
- label="New item"
64
- variant="primary"
65
- size="sm"
66
- icon={PlusIcon}
67
- />
62
+ <Button label="New item" variant="primary" size="lg" icon={PlusIcon} />
68
63
  </div>
69
64
  </TabList>
70
65
  );
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'VisuallyHidden',
7
+ name: 'VisuallyHidden — Live Region',
8
+ displayName: 'VisuallyHidden — Live Region',
9
+ description:
10
+ 'A polite aria-live region announces visual-only state changes to assistive technology.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['VisuallyHidden', 'Button', 'HStack', 'VStack', 'Text'],
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 {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
7
+ import {Button} from '@astryxdesign/core/Button';
8
+ import {HStack, VStack} from '@astryxdesign/core/Layout';
9
+ import {Text} from '@astryxdesign/core/Text';
10
+
11
+ const columns = ['Backlog', 'In progress', 'Done'] as const;
12
+
13
+ export default function VisuallyHiddenLiveRegion() {
14
+ const [column, setColumn] = useState(0);
15
+ const current = columns[column];
16
+
17
+ function move() {
18
+ setColumn(c => (c + 1) % columns.length);
19
+ }
20
+
21
+ return (
22
+ <VStack gap={4} hAlign="start">
23
+ <Text type="supporting" color="secondary">
24
+ Drag-and-drop and other visual-only changes are silent to screen
25
+ readers. A live region narrates them.
26
+ </Text>
27
+ <HStack gap={3} vAlign="center">
28
+ <Button label="Move task" variant="secondary" onClick={move} />
29
+ <Text type="body">
30
+ Task is in{' '}
31
+ <Text as="span" weight="bold">
32
+ {current}
33
+ </Text>
34
+ </Text>
35
+ </HStack>
36
+ <VisuallyHidden as="div" aria-live="polite">
37
+ {`Task moved to ${current}`}
38
+ </VisuallyHidden>
39
+ </VStack>
40
+ );
41
+ }
@@ -0,0 +1,13 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'VisuallyHidden',
7
+ name: 'VisuallyHidden',
8
+ displayName: 'VisuallyHidden',
9
+ isReady: true,
10
+ aspectRatio: 16 / 9,
11
+ isShowcase: true,
12
+ componentsUsed: ['VisuallyHidden', 'IconButton', 'Card', 'HStack', 'VStack', 'Text', 'Icon'],
13
+ };
@@ -0,0 +1,78 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
6
+ import {IconButton} from '@astryxdesign/core/IconButton';
7
+ import {Card} from '@astryxdesign/core/Card';
8
+ import {HStack, VStack} from '@astryxdesign/core/Layout';
9
+ import {Text} from '@astryxdesign/core/Text';
10
+ import {Icon} from '@astryxdesign/core/Icon';
11
+ import {
12
+ ArrowDownTrayIcon,
13
+ ShareIcon,
14
+ TrashIcon,
15
+ } from '@heroicons/react/24/outline';
16
+ import {SpeakerWaveIcon} from '@heroicons/react/24/solid';
17
+
18
+ const actions = [
19
+ {label: 'Download', icon: ArrowDownTrayIcon},
20
+ {label: 'Share', icon: ShareIcon},
21
+ {label: 'Delete', icon: TrashIcon},
22
+ ] as const;
23
+
24
+ /**
25
+ * VisuallyHidden is invisible by design, so this hero teaches the concept by
26
+ * contrast: the icon-only buttons are all a sighted user sees, while the
27
+ * caption spells out the accessible name each one exposes. A live
28
+ * <VisuallyHidden> region below announces the same names to assistive tech,
29
+ * so the demo genuinely exercises the component it documents.
30
+ */
31
+ export default function VisuallyHiddenShowcase() {
32
+ return (
33
+ <VStack gap={5} hAlign="center">
34
+ <HStack gap={6} vAlign="stretch" wrap="wrap" hAlign="center">
35
+ <Card variant="muted">
36
+ <VStack gap={4} hAlign="center">
37
+ <Text type="supporting" color="secondary">
38
+ What you see
39
+ </Text>
40
+ <HStack gap={2}>
41
+ {actions.map(({label, icon}) => (
42
+ <IconButton
43
+ key={label}
44
+ label={label}
45
+ icon={<Icon icon={icon} />}
46
+ variant="ghost"
47
+ />
48
+ ))}
49
+ </HStack>
50
+ </VStack>
51
+ </Card>
52
+
53
+ <Card variant="muted">
54
+ <VStack gap={4} hAlign="start">
55
+ <HStack gap={2} vAlign="center">
56
+ <Icon icon={SpeakerWaveIcon} size="sm" />
57
+ <Text type="supporting" color="secondary">
58
+ What a screen reader hears
59
+ </Text>
60
+ </HStack>
61
+ <VStack gap={2}>
62
+ {actions.map(({label}) => (
63
+ <Text key={label} type="body">
64
+ {label}, button
65
+ </Text>
66
+ ))}
67
+ </VStack>
68
+ </VStack>
69
+ </Card>
70
+ </HStack>
71
+
72
+ {/* A real live region: silent to sighted users, announced by AT. */}
73
+ <VisuallyHidden as="div" aria-live="polite">
74
+ Actions available: Download, Share, Delete.
75
+ </VisuallyHidden>
76
+ </VStack>
77
+ );
78
+ }
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'VisuallyHidden',
7
+ name: 'VisuallyHidden — Structural Heading',
8
+ displayName: 'VisuallyHidden — Structural Heading',
9
+ description:
10
+ 'Give a visually implicit section an accessible name so screen-reader users can navigate to it.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['VisuallyHidden', 'Card', 'HStack', 'VStack', 'Text', 'Badge'],
14
+ };
@@ -0,0 +1,38 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
6
+ import {Card} from '@astryxdesign/core/Card';
7
+ import {HStack, VStack} from '@astryxdesign/core/Layout';
8
+ import {Text} from '@astryxdesign/core/Text';
9
+ import {Badge} from '@astryxdesign/core/Badge';
10
+
11
+ const items = [
12
+ {name: 'astryx-core', status: 'Passing', variant: 'success'},
13
+ {name: 'astryx-charts', status: 'Failing', variant: 'error'},
14
+ {name: 'astryx-cli', status: 'Passing', variant: 'success'},
15
+ ] as const;
16
+
17
+ export default function VisuallyHiddenStructuralHeading() {
18
+ return (
19
+ <VStack gap={3} hAlign="start">
20
+ <Text type="supporting" color="secondary">
21
+ The layout makes this group obvious to sighted users. A hidden heading
22
+ gives screen-reader users the same landmark to jump to.
23
+ </Text>
24
+ {/* No visible heading is needed here, but AT users navigate by heading. */}
25
+ <VisuallyHidden as="h2">Build status</VisuallyHidden>
26
+ <VStack gap={2}>
27
+ {items.map(({name, status, variant}) => (
28
+ <Card key={name} variant="muted" padding={3}>
29
+ <HStack gap={3} vAlign="center">
30
+ <Text type="body">{name}</Text>
31
+ <Badge label={status} variant={variant} />
32
+ </HStack>
33
+ </Card>
34
+ ))}
35
+ </VStack>
36
+ </VStack>
37
+ );
38
+ }
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'VisuallyHidden',
7
+ name: 'VisuallyHidden — Supplementary Context',
8
+ displayName: 'VisuallyHidden — Supplementary Context',
9
+ description:
10
+ 'Add screen-reader-only context to terse visual data, like spelling out what a trend arrow means.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['VisuallyHidden', 'Card', 'HStack', 'VStack', 'Text', 'Icon'],
14
+ };