@astryxdesign/cli 0.1.2-canary.b712653 → 0.1.2-canary.bbf77ea

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 (111) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/README.md +49 -55
  3. package/docs/theme.doc.mjs +2 -2
  4. package/package.json +19 -7
  5. package/src/api/component.mjs +253 -12
  6. package/src/api/discover.mjs +14 -7
  7. package/src/api/doctor.mjs +10 -25
  8. package/src/api/layout.mjs +11 -10
  9. package/src/api/layout.test.mjs +4 -1
  10. package/src/api/template-integration.test.mjs +225 -0
  11. package/src/api/template.mjs +242 -84
  12. package/src/api/validate-integration.mjs +370 -0
  13. package/src/api/validate-integration.test.mjs +222 -0
  14. package/src/codemod.mjs +93 -0
  15. package/src/codemod.test.mjs +134 -0
  16. package/src/codemods/__tests__/registry.test.mjs +1 -0
  17. package/src/codemods/__tests__/runner.test.mjs +103 -0
  18. package/src/codemods/integration-discovery.mjs +168 -0
  19. package/src/codemods/integration-discovery.test.mjs +234 -0
  20. package/src/codemods/integration-runner.mjs +109 -0
  21. package/src/codemods/registry.mjs +1 -0
  22. package/src/codemods/run-codemod.mjs +207 -0
  23. package/src/codemods/runner.mjs +73 -96
  24. package/src/codemods/transforms/v0.0.15/index.mjs +0 -13
  25. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
  26. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
  27. package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +104 -0
  28. package/src/codemods/transforms/{v0.0.15 → v0.1.0}/drop-xds-prefix-imports.mjs +15 -4
  29. package/src/codemods/transforms/v0.1.0/index.mjs +43 -6
  30. package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
  31. package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
  32. package/src/codemods/transforms/v0.1.3/__tests__/migrate-layout-components-to-experimental.test.mjs +360 -0
  33. package/src/codemods/transforms/v0.1.3/index.mjs +19 -0
  34. package/src/codemods/transforms/v0.1.3/migrate-layout-components-to-experimental.mjs +260 -0
  35. package/src/commands/agent-docs.mjs +1 -1
  36. package/src/commands/component/index.mjs +44 -10
  37. package/src/commands/component-ownership.test.mjs +227 -0
  38. package/src/commands/discover.mjs +5 -16
  39. package/src/commands/doctor.test.mjs +3 -3
  40. package/src/commands/json-contract.test.mjs +0 -32
  41. package/src/commands/swizzle.mjs +224 -235
  42. package/src/commands/swizzle.path-safety.test.mjs +3 -3
  43. package/src/commands/swizzle.routing.test.mjs +279 -0
  44. package/src/commands/template.mjs +33 -37
  45. package/src/commands/upgrade.config-ordering.test.mjs +273 -0
  46. package/src/commands/upgrade.integration-policy.test.mjs +204 -0
  47. package/src/commands/upgrade.mjs +349 -170
  48. package/src/commands/validate-integration.mjs +110 -0
  49. package/src/commands/validate-integration.test.mjs +124 -0
  50. package/src/config.mjs +7 -20
  51. package/src/config.test.mjs +89 -13
  52. package/src/index.mjs +7 -3
  53. package/src/integration.mjs +19 -0
  54. package/src/lib/component-discovery.mjs +177 -0
  55. package/src/lib/config-cache.mjs +91 -0
  56. package/src/lib/config-cache.test.mjs +83 -0
  57. package/src/lib/config-schema.mjs +41 -55
  58. package/src/lib/error-codes.mjs +7 -12
  59. package/src/lib/integration-warnings.mjs +62 -0
  60. package/src/lib/integration-warnings.test.mjs +102 -0
  61. package/src/lib/integrations.mjs +92 -115
  62. package/src/lib/integrations.test.mjs +88 -107
  63. package/src/lib/manifest.mjs +5 -2
  64. package/src/lib/module-loader.mjs +80 -0
  65. package/src/lib/module-loader.test.mjs +106 -0
  66. package/src/lib/project.mjs +502 -0
  67. package/src/lib/project.test.mjs +308 -0
  68. package/src/template.mjs +73 -0
  69. package/src/template.test.mjs +127 -0
  70. package/src/types/api.d.ts +2 -6
  71. package/src/types/base.d.ts +3 -9
  72. package/src/types/codemod.d.ts +81 -0
  73. package/src/types/component.d.ts +27 -2
  74. package/src/types/config.d.ts +56 -85
  75. package/src/types/error-codes.d.ts +5 -3
  76. package/src/types/index.d.ts +0 -1
  77. package/src/types/integration.d.ts +29 -0
  78. package/src/types/swizzle.d.ts +9 -2
  79. package/src/types/template-api.d.ts +54 -0
  80. package/src/types/template.d.ts +10 -7
  81. package/src/types/upgrade.d.ts +29 -0
  82. package/src/types/validate-integration.d.ts +24 -0
  83. package/src/utils/github.mjs +0 -237
  84. package/src/utils/interactive.mjs +2 -2
  85. package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.doc.mjs +14 -0
  86. package/templates/blocks/components/DateRangeInput/DateRangeInputWithPresets.tsx +46 -0
  87. package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.doc.mjs +14 -0
  88. package/templates/blocks/components/DateRangeInput/DateRangeInputWithValidation.tsx +52 -0
  89. package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.doc.mjs +14 -0
  90. package/templates/blocks/components/DateTimeInput/DateTimeInputWithValidation.tsx +43 -0
  91. package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -1
  92. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +0 -1
  93. package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.doc.mjs +1 -1
  94. package/templates/blocks/components/HoverCard/HoverCardInteractiveContent.tsx +9 -4
  95. package/templates/blocks/components/LinkProvider/LinkProviderCustomLink.doc.mjs +1 -1
  96. package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -1
  97. package/templates/blocks/components/Tab/TabWithSelectedIcon.doc.mjs +13 -0
  98. package/templates/blocks/components/Tab/TabWithSelectedIcon.tsx +39 -0
  99. package/templates/blocks/components/Table/StickyColumnsHookUsage.doc.mjs +1 -1
  100. package/templates/pages/shell-side-nav/page.tsx +0 -1
  101. package/templates/pages/shell-side-nav/template.doc.mjs +1 -1
  102. package/templates/pages/shell-top-nav/template.doc.mjs +1 -1
  103. package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-config-surfaces.test.mjs +0 -116
  104. package/src/codemods/transforms/v0.1.0/migrate-xds-config-surfaces.mjs +0 -230
  105. package/src/commands/gap-report.mjs +0 -464
  106. package/src/commands/gap-report.test.mjs +0 -168
  107. package/src/commands/swizzle-gap-safety.test.mjs +0 -273
  108. package/src/lib/config.mjs +0 -113
  109. package/src/lib/config.test.mjs +0 -91
  110. package/src/types/gap-report.d.ts +0 -29
  111. /package/src/codemods/transforms/{v0.0.15 → v0.1.0}/__tests__/drop-xds-prefix-imports.test.mjs +0 -0
@@ -4,69 +4,9 @@
4
4
  * @file GitHub utilities for Astryx CLI
5
5
  *
6
6
  * Shared helpers for interacting with the GitHub CLI (`gh`).
7
- * Used by gap-report and swizzle commands.
8
- *
9
- * Gap reporting can be configured via astryx.config.mjs:
10
- * gapReport: false — disable entirely
11
- * gapReport: { command: './scripts/report-gap.sh' } — run custom script (receives JSON on stdin)
12
- *
13
- * Or via environment variable:
14
- * ASTRYX_GAP_REPORT=off — disable entirely
15
- * ASTRYX_GAP_REPORT=./scripts/report-gap.sh — run custom script
16
7
  */
17
8
 
18
9
  import {execFileSync} from 'node:child_process';
19
- import * as path from 'node:path';
20
- import {loadConfig} from '../lib/config.mjs';
21
-
22
- /** Default target repository for gap report issues. */
23
- const DEFAULT_REPO = 'facebookexperimental/xds';
24
-
25
- /** Gap report category options. */
26
- export const GAP_CATEGORIES = [
27
- {value: 'missing_component', label: 'Missing component'},
28
- {value: 'missing_variant', label: 'Missing variant or prop'},
29
- {value: 'layout_gap', label: 'Layout gap'},
30
- {value: 'styling_gap', label: 'Styling gap'},
31
- {value: 'a11y_gap', label: 'Accessibility gap'},
32
- {value: 'api_friction', label: 'API friction'},
33
- {value: 'docs_gap', label: 'Documentation gap'},
34
- {value: 'other', label: 'Other'},
35
- ];
36
-
37
- /**
38
- * Load gap report configuration.
39
- *
40
- * Priority: env var > astryx.config.mjs > default (GitHub issue)
41
- *
42
- * Returns { enabled: boolean, command?: string }
43
- * - enabled: false → gap reporting disabled
44
- * - command: string → run this script instead of creating a GitHub issue
45
- * - neither → default behavior (GitHub issue to facebookexperimental/xds)
46
- */
47
- export async function loadGapReportConfig() {
48
- // 1. Check environment variable
49
- const envVar = process.env.ASTRYX_GAP_REPORT;
50
- if (envVar) {
51
- if (envVar === 'off' || envVar === 'false' || envVar === '0') {
52
- return {enabled: false};
53
- }
54
- // Treat as custom command
55
- return {enabled: true, command: envVar};
56
- }
57
-
58
- // 2. Check astryx.config.mjs + integration manifests
59
- const config = await loadConfig(process.cwd());
60
- if (config.gapReport !== undefined) {
61
- if (config.gapReport === false) return {enabled: false};
62
- if (config.gapReport?.command) {
63
- return {enabled: true, command: config.gapReport.command};
64
- }
65
- }
66
-
67
- // 3. Default
68
- return {enabled: true};
69
- }
70
10
 
71
11
  /**
72
12
  * Check if `gh` CLI is installed and authenticated.
@@ -80,180 +20,3 @@ export function checkGhCli() {
80
20
  return false;
81
21
  }
82
22
  }
83
-
84
- /**
85
- * Idempotently create the `gap-report` and `gap-analysis` labels.
86
- * Uses `--force` to avoid errors if labels already exist.
87
- */
88
- function ensureGapReportLabel() {
89
- try {
90
- execFileSync(
91
- 'gh',
92
- [
93
- 'label',
94
- 'create',
95
- 'gap-report',
96
- '--repo',
97
- DEFAULT_REPO,
98
- '--color',
99
- 'D4C5F9',
100
- '--description',
101
- 'Auto-filed gap report from swizzle/gap-report CLI',
102
- '--force',
103
- ],
104
- {stdio: 'ignore'},
105
- );
106
- execFileSync(
107
- 'gh',
108
- [
109
- 'label',
110
- 'create',
111
- 'gap-analysis',
112
- '--repo',
113
- DEFAULT_REPO,
114
- '--color',
115
- '0E8A16',
116
- '--description',
117
- 'Curated gap analysis summary',
118
- '--force',
119
- ],
120
- {stdio: 'ignore'},
121
- );
122
- } catch {
123
- // Best-effort — labels may already exist or user may lack permissions
124
- }
125
- }
126
-
127
- /**
128
- * Run a custom gap report command, piping report data as JSON to stdin.
129
- *
130
- * @param {string} command Script path to execute
131
- * @param {object} report Report data (component, category, intention, detail, source)
132
- * @returns {string} stdout from the command
133
- */
134
- function runCustomCommand(command, report) {
135
- const resolved = path.resolve(process.cwd(), command);
136
- const json = JSON.stringify(report, null, 2);
137
-
138
- const result = execFileSync(resolved, [], {
139
- input: json,
140
- encoding: 'utf-8',
141
- });
142
-
143
- return result.trim();
144
- }
145
-
146
- /**
147
- * Build the report payload + rendered title/body for a gap report.
148
- * Pure function — no side effects, never calls `gh` or external scripts.
149
- *
150
- * @returns {{
151
- * enabled: boolean,
152
- * mode: 'disabled' | 'custom' | 'github',
153
- * command?: string,
154
- * report: object,
155
- * title: string,
156
- * body: string,
157
- * repo: string,
158
- * }}
159
- */
160
- export async function buildGapReportPreview({
161
- component,
162
- category,
163
- intention,
164
- detail,
165
- source = 'cli',
166
- }) {
167
- const config = await loadGapReportConfig();
168
- const categoryLabel =
169
- GAP_CATEGORIES.find(c => c.value === category)?.label ?? category;
170
-
171
- const report = {
172
- component,
173
- category,
174
- categoryLabel,
175
- intention,
176
- detail: detail || null,
177
- source,
178
- timestamp: new Date().toISOString(),
179
- };
180
-
181
- const title = `[gap] ${component}: ${intention.slice(0, 70)}`;
182
- const body = [
183
- '| Field | Value |',
184
- '|-------|-------|',
185
- `| **Component** | ${component} |`,
186
- `| **Category** | ${categoryLabel} |`,
187
- `| **Source** | ${source} |`,
188
- `| **Timestamp** | ${report.timestamp} |`,
189
- '',
190
- '## User Intention',
191
- '',
192
- intention,
193
- ...(detail ? ['', '## Additional Context', '', detail] : []),
194
- ].join('\n');
195
-
196
- let mode = 'github';
197
- if (!config.enabled) mode = 'disabled';
198
- else if (config.command) mode = 'custom';
199
-
200
- return {
201
- enabled: config.enabled,
202
- mode,
203
- command: config.command,
204
- report,
205
- title,
206
- body,
207
- repo: DEFAULT_REPO,
208
- };
209
- }
210
-
211
- /**
212
- * Create a gap report — either via GitHub issue or custom command.
213
- *
214
- * SAFETY: This actually invokes `gh issue create` (or runs the custom command).
215
- * Callers MUST gate this behind explicit user confirmation or a `--commit`-style
216
- * flag in non-interactive contexts. Use `buildGapReportPreview` for dry-run.
217
- *
218
- * @param {object} opts
219
- * @param {string} opts.component Component name (e.g. "Button")
220
- * @param {string} opts.category One of GAP_CATEGORIES values
221
- * @param {string} opts.intention What the user was trying to achieve
222
- * @param {string} [opts.detail] Additional context
223
- * @param {string} [opts.source] Who filed it: "interactive", "llm-auto", "cli"
224
- * @returns {string|null} URL of the created issue (or custom command output), null if disabled
225
- */
226
- export async function createGapReport(opts) {
227
- const preview = await buildGapReportPreview(opts);
228
-
229
- if (!preview.enabled) {
230
- return null;
231
- }
232
-
233
- // Custom command mode — pipe JSON to the script
234
- if (preview.mode === 'custom') {
235
- return runCustomCommand(preview.command, preview.report);
236
- }
237
-
238
- // Default mode — create GitHub issue
239
- ensureGapReportLabel();
240
-
241
- const result = execFileSync(
242
- 'gh',
243
- [
244
- 'issue',
245
- 'create',
246
- '--repo',
247
- preview.repo,
248
- '--title',
249
- preview.title,
250
- '--body',
251
- preview.body,
252
- '--label',
253
- 'gap-report',
254
- ],
255
- {encoding: 'utf-8'},
256
- );
257
-
258
- return result.trim();
259
- }
@@ -18,8 +18,8 @@
18
18
  * fail fast (exit 1) with actionable, non-interactive guidance.
19
19
  *
20
20
  * - `isInteractive()` — for commands with an OPTIONAL secondary prompt
21
- * (e.g. swizzle's post-copy "report a gap?"). The primary work already
22
- * succeeded; callers use this to skip the prompt gracefully.
21
+ * that runs after the primary work has already succeeded; callers use
22
+ * this to skip the prompt gracefully in non-interactive contexts.
23
23
  */
24
24
 
25
25
  /**
@@ -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: 'DateRangeInput',
7
+ name: 'DateRangeInput — With Presets',
8
+ displayName: 'DateRangeInput — With Presets',
9
+ description:
10
+ 'Date range picker with quick-select presets for common periods. Use for analytics dashboards, report filters, or any context where users frequently select standard time windows.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['DateRangeInput', 'Layout', 'Text'],
14
+ };
@@ -0,0 +1,46 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {DateRangeInput} from '@astryxdesign/core/DateRangeInput';
7
+ import type {DateRange} from '@astryxdesign/core/DateRangeInput';
8
+ import type {ISODateString} from '@astryxdesign/core/Calendar';
9
+ import {Stack} from '@astryxdesign/core/Layout';
10
+ import {Text} from '@astryxdesign/core/Text';
11
+
12
+ function daysAgo(n: number): ISODateString {
13
+ const d = new Date();
14
+ d.setDate(d.getDate() - n);
15
+ return d.toISOString().slice(0, 10) as ISODateString;
16
+ }
17
+
18
+ function today(): ISODateString {
19
+ return new Date().toISOString().slice(0, 10) as ISODateString;
20
+ }
21
+
22
+ const presets = [
23
+ {label: 'Last 7 days', getRange: () => ({start: daysAgo(7), end: today()})},
24
+ {label: 'Last 14 days', getRange: () => ({start: daysAgo(14), end: today()})},
25
+ {label: 'Last 30 days', getRange: () => ({start: daysAgo(30), end: today()})},
26
+ {label: 'Last 90 days', getRange: () => ({start: daysAgo(90), end: today()})},
27
+ ];
28
+
29
+ export default function DateRangeInputWithPresets() {
30
+ const [range, setRange] = useState<DateRange | null>(null);
31
+
32
+ return (
33
+ <Stack direction="vertical" gap={4} width="100%" style={{maxWidth: 400}}>
34
+ <Text type="supporting" color="secondary">
35
+ {range ? `${range.start} → ${range.end}` : 'No range selected'}
36
+ </Text>
37
+ <DateRangeInput
38
+ label="Report period"
39
+ description="Use a preset or pick a custom range"
40
+ value={range}
41
+ onChange={setRange}
42
+ presets={presets}
43
+ />
44
+ </Stack>
45
+ );
46
+ }
@@ -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: 'DateRangeInput',
7
+ name: 'DateRangeInput — Validation',
8
+ displayName: 'DateRangeInput — Validation',
9
+ description:
10
+ 'Date range input in all three status states: error, warning, and success. Use to surface booking conflicts, flag high-demand periods, or confirm an available range.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['DateRangeInput', 'Layout'],
14
+ };
@@ -0,0 +1,52 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {DateRangeInput} from '@astryxdesign/core/DateRangeInput';
7
+ import type {DateRange} from '@astryxdesign/core/DateRangeInput';
8
+ import {Stack} from '@astryxdesign/core/Layout';
9
+
10
+ export default function DateRangeInputWithValidation() {
11
+ const [errorRange, setErrorRange] = useState<DateRange | null>({
12
+ start: '2026-01-01',
13
+ end: '2026-01-31',
14
+ });
15
+ const [warningRange, setWarningRange] = useState<DateRange | null>({
16
+ start: '2026-06-01',
17
+ end: '2026-06-30',
18
+ });
19
+ const [successRange, setSuccessRange] = useState<DateRange | null>({
20
+ start: '2026-03-01',
21
+ end: '2026-03-31',
22
+ });
23
+
24
+ return (
25
+ <Stack direction="vertical" gap={4} width="100%" style={{maxWidth: 400}}>
26
+ <DateRangeInput
27
+ label="Booking period"
28
+ value={errorRange}
29
+ onChange={setErrorRange}
30
+ status={{
31
+ type: 'error',
32
+ message: 'Selected dates are no longer available',
33
+ }}
34
+ />
35
+ <DateRangeInput
36
+ label="Preferred period"
37
+ value={warningRange}
38
+ onChange={setWarningRange}
39
+ status={{
40
+ type: 'warning',
41
+ message: 'High demand — limited availability',
42
+ }}
43
+ />
44
+ <DateRangeInput
45
+ label="Confirmed period"
46
+ value={successRange}
47
+ onChange={setSuccessRange}
48
+ status={{type: 'success', message: 'Dates confirmed and available'}}
49
+ />
50
+ </Stack>
51
+ );
52
+ }
@@ -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: 'DateTimeInput',
7
+ name: 'DateTimeInput — Validation',
8
+ displayName: 'DateTimeInput — Validation',
9
+ description:
10
+ 'DateTimeInput in all three status states: error, warning, and success. Use to surface scheduling conflicts, caution the user about edge cases, or confirm a valid datetime.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['DateTimeInput', 'Layout'],
14
+ };
@@ -0,0 +1,43 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {DateTimeInput} from '@astryxdesign/core/DateTimeInput';
7
+ import type {ISODateTimeString} from '@astryxdesign/core/DateTimeInput';
8
+ import {Stack} from '@astryxdesign/core/Layout';
9
+
10
+ export default function DateTimeInputWithValidation() {
11
+ const [errorVal, setErrorVal] = useState<ISODateTimeString | undefined>(
12
+ '2026-01-25T09:00' as ISODateTimeString,
13
+ );
14
+ const [warningVal, setWarningVal] = useState<ISODateTimeString | undefined>(
15
+ '2026-12-25T14:00' as ISODateTimeString,
16
+ );
17
+ const [successVal, setSuccessVal] = useState<ISODateTimeString | undefined>(
18
+ '2026-03-10T10:30' as ISODateTimeString,
19
+ );
20
+
21
+ return (
22
+ <Stack direction="vertical" gap={4} width="100%" style={{maxWidth: 400}}>
23
+ <DateTimeInput
24
+ label="Meeting time"
25
+ value={errorVal}
26
+ onChange={setErrorVal}
27
+ status={{type: 'error', message: 'This time slot is already booked'}}
28
+ />
29
+ <DateTimeInput
30
+ label="Preferred time"
31
+ value={warningVal}
32
+ onChange={setWarningVal}
33
+ status={{type: 'warning', message: 'This falls outside business hours'}}
34
+ />
35
+ <DateTimeInput
36
+ label="Start time"
37
+ value={successVal}
38
+ onChange={setSuccessVal}
39
+ status={{type: 'success', message: 'Time confirmed'}}
40
+ />
41
+ </Stack>
42
+ );
43
+ }
@@ -12,7 +12,6 @@ export default function DropdownMenuShowcase() {
12
12
  <DropdownMenu
13
13
  isMenuOpen={isMenuOpen}
14
14
  onOpenChange={setIsMenuOpen}
15
- hasAutoFocus={false}
16
15
  button={{label: 'Actions'}}
17
16
  items={[
18
17
  {label: 'Edit', onClick: () => {}},
@@ -68,7 +68,6 @@ export default function DropdownMenuItemShowcase() {
68
68
  <DropdownMenu
69
69
  button={{label: 'Actions'}}
70
70
  isMenuOpen
71
- hasAutoFocus={false}
72
71
  onOpenChange={() => {}}>
73
72
  <DropdownMenuItem
74
73
  icon={PencilIcon}
@@ -10,5 +10,5 @@ export const doc = {
10
10
  'Shows a page summary when hovering a link: title, description, and URL. Use for documentation links, article references, or any URL where a preview helps the user decide whether to click.',
11
11
  isReady: true,
12
12
  aspectRatio: 16 / 9,
13
- componentsUsed: ['HoverCard', 'Icon', 'Layout', 'Text'],
13
+ componentsUsed: ['HoverCard', 'Icon', 'Layout', 'Link', 'Text'],
14
14
  };
@@ -5,12 +5,14 @@
5
5
  import {HoverCard} from '@astryxdesign/core/HoverCard';
6
6
  import {Icon} from '@astryxdesign/core/Icon';
7
7
  import {VStack, HStack} from '@astryxdesign/core/Layout';
8
+ import {Link} from '@astryxdesign/core/Link';
8
9
  import {Text} from '@astryxdesign/core/Text';
9
10
  import {LinkIcon} from '@heroicons/react/24/outline';
10
11
 
11
12
  export default function HoverCardInteractiveContent() {
12
13
  return (
13
- <Text type="body">Read more in the{' '}
14
+ <Text type="body">
15
+ Read more in the{' '}
14
16
  <HoverCard
15
17
  placement="below"
16
18
  content={
@@ -30,8 +32,11 @@ export default function HoverCardInteractiveContent() {
30
32
  </HStack>
31
33
  </VStack>
32
34
  }>
33
- Getting Started Guide
34
- </HoverCard>.
35
- </Text>
35
+ <Link href="#" hasUnderline>
36
+ Getting Started Guide
37
+ </Link>
38
+ </HoverCard>
39
+ .
40
+ </Text>
36
41
  );
37
42
  }
@@ -7,7 +7,7 @@ export const doc = {
7
7
  name: 'Link Provider — Custom Link Component',
8
8
  displayName: 'Link Provider — Custom Link Component',
9
9
  description:
10
- 'Routes every Astryx link through a custom component that intercepts the click — the hook frameworks like Next.js use for client-side navigation. Click the link to see the custom handler fire instead of a full-page load.',
10
+ 'Routes every Astryx link through a custom component that intercepts the click, the hook frameworks like Next.js use for client-side navigation. Click the link to see the custom handler fire instead of a full-page load.',
11
11
  isReady: true,
12
12
  aspectRatio: 4 / 3,
13
13
  componentsUsed: ['LinkProvider', 'Link'],
@@ -12,7 +12,6 @@ export default function MoreMenuShowcase() {
12
12
  <MoreMenu
13
13
  isMenuOpen={isMenuOpen}
14
14
  onOpenChange={setIsMenuOpen}
15
- hasAutoFocus={false}
16
15
  items={[
17
16
  {label: 'Edit', onClick: () => {}},
18
17
  {label: 'Duplicate', onClick: () => {}},
@@ -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: 'Tab',
7
+ name: 'Tab — Selected Icon',
8
+ displayName: 'Tab — Selected Icon',
9
+ description: 'A tab that changes its icon when selected.',
10
+ isReady: true,
11
+ aspectRatio: 4 / 3,
12
+ componentsUsed: ['TabList', 'Tab'],
13
+ };
@@ -0,0 +1,39 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {TabList, Tab} from '@astryxdesign/core/TabList';
7
+
8
+ const StarIcon = (
9
+ <svg
10
+ viewBox="0 0 16 16"
11
+ fill="none"
12
+ stroke="currentColor"
13
+ width="100%"
14
+ height="100%">
15
+ <path d="m8 1.5 2 4.1 4.5.7-3.3 3.2.8 4.5-4-2.1L4 14l.8-4.5-3.3-3.2 4.5-.7L8 1.5Z" />
16
+ </svg>
17
+ );
18
+
19
+ const SelectedStarIcon = (
20
+ <svg viewBox="0 0 16 16" fill="currentColor" width="100%" height="100%">
21
+ <path d="m8 1.5 2 4.1 4.5.7-3.3 3.2.8 4.5-4-2.1L4 14l.8-4.5-3.3-3.2 4.5-.7L8 1.5Z" />
22
+ </svg>
23
+ );
24
+
25
+ export default function TabWithSelectedIcon() {
26
+ const [value, setValue] = useState('favorites');
27
+
28
+ return (
29
+ <TabList value={value} onChange={setValue}>
30
+ <Tab
31
+ value="favorites"
32
+ label="Favorites"
33
+ icon={StarIcon}
34
+ selectedIcon={SelectedStarIcon}
35
+ />
36
+ <Tab value="recent" label="Recent" />
37
+ </TabList>
38
+ );
39
+ }
@@ -7,7 +7,7 @@ export const doc = {
7
7
  name: 'useTableStickyColumns — Pinned Columns',
8
8
  displayName: 'useTableStickyColumns — Pinned Columns',
9
9
  description:
10
- 'A Table using useTableStickyColumns to pin the Name column to the start edge and Status to the end edge. Scroll horizontally — pinned columns stay in view with a soft shadow over the scrolling content.',
10
+ 'A Table using useTableStickyColumns to pin the Name column to the start edge and Status to the end edge. Scroll horizontally; pinned columns stay in view with a soft shadow over the scrolling content.',
11
11
  isReady: true,
12
12
  aspectRatio: 16 / 9,
13
13
  componentsUsed: ['Table'],
@@ -142,7 +142,6 @@ function ConversationItem({
142
142
  <MoreMenu
143
143
  size="sm"
144
144
  label="Conversation options"
145
- hasAutoFocus={false}
146
145
  onOpenChange={setIsMenuOpen}
147
146
  items={[
148
147
  {label: 'Pin', onClick: () => {}},
@@ -6,7 +6,7 @@ export const doc = {
6
6
  name: 'Side Nav',
7
7
  displayName: 'Side Nav',
8
8
  description:
9
- 'A side-nav app shell with a collapsible, resizable SideNav — new chat, search, library, and workspace-grouped conversations with status dots — over static grey-card conversation and composer placeholders.',
9
+ 'A side-nav app shell with a collapsible, resizable SideNav (new chat, search, library, and workspace-grouped conversations with status dots) over static grey-card conversation and composer placeholders.',
10
10
  isReady: true,
11
11
  category: 'Shell - Left Sidebar',
12
12
  };
@@ -6,7 +6,7 @@ export const doc = {
6
6
  name: 'Top Nav',
7
7
  displayName: 'Top Nav',
8
8
  description:
9
- 'A top-nav app shell with a centered TopNav — Shop and Brands mega menus (a 2x4 item grid plus a featured card each), Sale/Service links, and a Checkout button — over static grey-card hero and category sections.',
9
+ 'A top-nav app shell with a centered TopNav (Shop and Brands mega menus with a 2x4 item grid plus a featured card each, Sale/Service links, and a Checkout button) over static grey-card hero and category sections.',
10
10
  isReady: true,
11
11
  category: 'Shell - Top Nav',
12
12
  };