@astryxdesign/cli 0.1.2-canary.f03e2cc → 0.1.2-canary.f330539
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.
- package/CHANGELOG.md +0 -1
- package/README.md +49 -55
- package/package.json +7 -7
- package/src/api/doctor.mjs +3 -3
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-css-surfaces.test.mjs +67 -0
- package/src/codemods/transforms/v0.1.0/__tests__/migrate-xds-declare-module.test.mjs +61 -0
- package/src/codemods/transforms/v0.1.0/__tests__/v0.1.0-ordering.test.mjs +23 -0
- package/src/codemods/transforms/v0.1.0/index.mjs +28 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-css-surfaces.mjs +77 -0
- package/src/codemods/transforms/v0.1.0/migrate-xds-declare-module.mjs +78 -0
- package/src/commands/agent-docs.mjs +1 -1
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +34 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +14 -0
- package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +21 -0
- package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +0 -1
- package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +0 -1
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +14 -0
- package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +57 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxGallery.tsx +53 -0
- package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +4 -3
- package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxVideo.tsx +27 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +14 -0
- package/templates/blocks/components/Lightbox/LightboxZoom.tsx +33 -0
- package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +0 -1
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +14 -0
- package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +47 -0
- package/templates/pages/shell-side-nav/page.tsx +0 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -62,8 +62,7 @@ Options:
|
|
|
62
62
|
| `upgrade` | Run codemods to migrate between versions |
|
|
63
63
|
| `theme build` | Compile a defineTheme file to production CSS and JS |
|
|
64
64
|
| `discover` | Discover external packages and components |
|
|
65
|
-
| `
|
|
66
|
-
| `doctor` | Diagnose your XDS setup and report problems with fixes (CI-friendly via exit code) |
|
|
65
|
+
| `doctor` | Diagnose your Astryx setup and report problems with fixes (CI-friendly via exit code) |
|
|
67
66
|
|
|
68
67
|
### Global options
|
|
69
68
|
|
|
@@ -161,7 +160,6 @@ if (isError(result)) {
|
|
|
161
160
|
| `ERR_INVALID_VERSION` | A `--from`/`--to` value was not a valid semver string. |
|
|
162
161
|
| `ERR_DEP_MISSING` | A required external dependency (e.g. jscodeshift) is missing. |
|
|
163
162
|
| `ERR_GH_CLI` | GitHub CLI (`gh`) is not installed or not authenticated. |
|
|
164
|
-
| `ERR_GAP_REPORT_FAILED` | Filing a gap report failed (disabled, or the integration errored). |
|
|
165
163
|
|
|
166
164
|
## Capability manifest (agent discovery)
|
|
167
165
|
|
|
@@ -172,7 +170,7 @@ discriminators each command can emit. Think of it as an OpenAPI spec for the CLI
|
|
|
172
170
|
|
|
173
171
|
```bash
|
|
174
172
|
astryx manifest --json # dedicated surface — type: "manifest"
|
|
175
|
-
|
|
173
|
+
astryx --json # bare invocation — embeds the same payload under data.manifest
|
|
176
174
|
```
|
|
177
175
|
|
|
178
176
|
Shape:
|
|
@@ -182,7 +180,7 @@ Shape:
|
|
|
182
180
|
"apiVersion": 1,
|
|
183
181
|
"type": "manifest",
|
|
184
182
|
"data": {
|
|
185
|
-
"name": "
|
|
183
|
+
"name": "astryx",
|
|
186
184
|
"version": "0.0.14",
|
|
187
185
|
"description": "Design system CLI — components, themes, and tooling",
|
|
188
186
|
"globalOptions": [
|
|
@@ -249,14 +247,14 @@ the `JSON_SUPPORTED` allowlist and a small declarative `RESPONSE_TYPES` map in
|
|
|
249
247
|
`src/lib/manifest.mjs`, guarded by a drift test (`manifest.test.mjs`) so adding a
|
|
250
248
|
command without describing it fails CI.
|
|
251
249
|
|
|
252
|
-
**Backwards-compat:** the bare `
|
|
250
|
+
**Backwards-compat:** the bare `astryx --json` envelope keeps `type: "help"` and its
|
|
253
251
|
original shallow fields (`name`, `version`, `commands` as a `string[]` of names,
|
|
254
252
|
`jsonSupported`); the full structured manifest is additive under `data.manifest`.
|
|
255
253
|
For the standalone manifest envelope (`type: "manifest"`), use `astryx manifest --json`.
|
|
256
254
|
|
|
257
255
|
## Programmatic API
|
|
258
256
|
|
|
259
|
-
The same logic that powers `
|
|
257
|
+
The same logic that powers `astryx --json` is available as importable, type-safe functions:
|
|
260
258
|
|
|
261
259
|
```typescript
|
|
262
260
|
import {
|
|
@@ -269,20 +267,20 @@ import {
|
|
|
269
267
|
AstryxError,
|
|
270
268
|
} from '@astryxdesign/cli/api';
|
|
271
269
|
|
|
272
|
-
// Same result as:
|
|
270
|
+
// Same result as: astryx --json component Button
|
|
273
271
|
const btn = await component('Button');
|
|
274
272
|
btn.type; // 'component.detail'
|
|
275
273
|
btn.data.name; // 'Button' (typed as ComponentDoc)
|
|
276
274
|
|
|
277
|
-
// Same result as:
|
|
275
|
+
// Same result as: astryx --json component --list
|
|
278
276
|
const list = await component(undefined, {list: true});
|
|
279
277
|
list.data; // Record<string, string[]>
|
|
280
278
|
|
|
281
|
-
// Same result as:
|
|
279
|
+
// Same result as: astryx --json docs principles
|
|
282
280
|
const principles = await docs('principles');
|
|
283
|
-
principles.data.title; // '
|
|
281
|
+
principles.data.title; // 'Principles'
|
|
284
282
|
|
|
285
|
-
// Same result as:
|
|
283
|
+
// Same result as: astryx --json hook useMediaQuery
|
|
286
284
|
const useMediaQuery = await hook('useMediaQuery');
|
|
287
285
|
useMediaQuery.data.params; // typed as HookParamDoc[]
|
|
288
286
|
|
|
@@ -296,7 +294,7 @@ try {
|
|
|
296
294
|
}
|
|
297
295
|
```
|
|
298
296
|
|
|
299
|
-
The CLI command handlers are thin wrappers around these functions: they parse args, call the API, then format the output (JSON or text). This guarantees that `@astryxdesign/cli/api` and `
|
|
297
|
+
The CLI command handlers are thin wrappers around these functions: they parse args, call the API, then format the output (JSON or text). This guarantees that `@astryxdesign/cli/api` and `astryx --json` always return identical data.
|
|
300
298
|
|
|
301
299
|
### Consumer utilities
|
|
302
300
|
|
|
@@ -326,43 +324,41 @@ detail.data.name; // already narrowed
|
|
|
326
324
|
|
|
327
325
|
Every response has a `type` string that uniquely identifies it:
|
|
328
326
|
|
|
329
|
-
| Command
|
|
330
|
-
|
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `
|
|
335
|
-
| `
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
362
|
-
|
|
|
363
|
-
|
|
|
364
|
-
| any error | — | `CLIError` |
|
|
365
|
-
| unsupported command | — | `CLIUnsupportedError` |
|
|
327
|
+
| Command | Type | Response |
|
|
328
|
+
| ------------------------------------------------- | --------------------------- | --------------------------------- |
|
|
329
|
+
| `astryx --json component [--list]` | `component.list` | `ComponentListResponse` |
|
|
330
|
+
| `astryx --json component --list --detail compact` | `component.brief` | `ComponentBriefResponse` |
|
|
331
|
+
| `astryx --json component --list --detail full` | `component.full` | `ComponentFullResponse` |
|
|
332
|
+
| `astryx --json component <name>` | `component.detail` | `ComponentDetailResponse` |
|
|
333
|
+
| `astryx --json component <name> --props` | `component.detail.props` | `ComponentDetailPropsResponse` |
|
|
334
|
+
| `astryx --json component <name> --source` | `component.detail.source` | `ComponentDetailSourceResponse` |
|
|
335
|
+
| `astryx --json component <name> --showcase` | `component.detail.showcase` | `ComponentDetailShowcaseResponse` |
|
|
336
|
+
| `astryx --json component <name> --blocks` | `component.detail.blocks` | `ComponentDetailBlocksResponse` |
|
|
337
|
+
| `astryx --json discover` | `discover.list` | `DiscoverListResponse` |
|
|
338
|
+
| `astryx --json discover @scope/name` | `discover.detail` | `DiscoverDetailResponse` |
|
|
339
|
+
| `astryx --json discover @scope/name/Comp` | `discover.detail.doc` | `DiscoverDetailDocResponse` |
|
|
340
|
+
| `astryx --json discover <search>` | `discover.search` | `DiscoverSearchResponse` |
|
|
341
|
+
| `astryx --json docs` | `docs.list` | `DocsListResponse` |
|
|
342
|
+
| `astryx --json docs <topic>` | `docs.detail` | `DocsDetailResponse` |
|
|
343
|
+
| `astryx --json docs <topic> <section>` | `docs.detail.section` | `DocsDetailSectionResponse` |
|
|
344
|
+
| `astryx --json template [--list]` | `template.list` | `TemplateListResponse` |
|
|
345
|
+
| `astryx --json template <name>` | `template.show` | `TemplateShowResponse` |
|
|
346
|
+
| `astryx --json template <name> --skeleton` | `template.skeleton` | `TemplateSkeletonResponse` |
|
|
347
|
+
| `astryx --json template <name> [path]` | `template.copy` | `TemplateCopyResponse` |
|
|
348
|
+
| `astryx --json hook [--list]` | `hook.list` | `HookListResponse` |
|
|
349
|
+
| `astryx --json hook --list --detail compact` | `hook.brief` | `HookBriefResponse` |
|
|
350
|
+
| `astryx --json hook --list --detail full` | `hook.full` | `HookFullResponse` |
|
|
351
|
+
| `astryx --json hook <name>` | `hook.detail` | `HookDetailResponse` |
|
|
352
|
+
| `astryx --json hook <name> --params` | `hook.detail.params` | `HookDetailParamsResponse` |
|
|
353
|
+
| `astryx --json search <query>` | `search` | `SearchResponse` |
|
|
354
|
+
| `astryx --json swizzle [--list]` | `swizzle.list` | `SwizzleListResponse` |
|
|
355
|
+
| `astryx --json swizzle <component>` | `swizzle.copy` | `SwizzleCopyResponse` |
|
|
356
|
+
| `astryx --json theme build <file>` | `theme.build` | `ThemeBuildResponse` |
|
|
357
|
+
| `astryx --json upgrade --list` | `upgrade.list` | `UpgradeListResponse` |
|
|
358
|
+
| `astryx --json upgrade [--apply]` | `upgrade.run` | `UpgradeRunResponse` |
|
|
359
|
+
| `astryx --json doctor` | `doctor` | `DoctorResponse` |
|
|
360
|
+
| any error | — | `CLIError` |
|
|
361
|
+
| unsupported command | — | `CLIUnsupportedError` |
|
|
366
362
|
|
|
367
363
|
## Doctor
|
|
368
364
|
|
|
@@ -383,7 +379,7 @@ astryx doctor — diagnosing your setup
|
|
|
383
379
|
@astryxdesign/core v0.0.14 is in step with @astryxdesign/cli v0.0.14.
|
|
384
380
|
⚠ Theme packages
|
|
385
381
|
No @astryxdesign/theme-* packages are installed.
|
|
386
|
-
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set
|
|
382
|
+
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set astryx.theme.
|
|
387
383
|
ℹ astryx.config.mjs
|
|
388
384
|
No astryx.config.mjs found — using defaults.
|
|
389
385
|
ℹ AI agent docs
|
|
@@ -408,7 +404,7 @@ No failures — but review the ⚠ warnings above when you can.
|
|
|
408
404
|
| Version alignment | pass / warn / info | Installed `@astryxdesign/core` is in step with `@astryxdesign/cli` |
|
|
409
405
|
| Theme packages | pass / warn | An `@astryxdesign/theme-*` package is installed and a theme is wired |
|
|
410
406
|
| astryx.config.mjs | pass / fail / info | Config (if present) loads cleanly with a valid shape |
|
|
411
|
-
| AI agent docs | pass / warn / info | Agent docs exist and contain the
|
|
407
|
+
| AI agent docs | pass / warn / info | Agent docs exist and contain the Astryx section markers |
|
|
412
408
|
| Peer dependencies | pass / warn / info | `@astryxdesign/core`'s peer deps (react, …) are installed |
|
|
413
409
|
| Package manager | info | Reports the detected package manager |
|
|
414
410
|
|
|
@@ -434,8 +430,6 @@ export default {
|
|
|
434
430
|
templates: {
|
|
435
431
|
get: async id => fetchTemplateFromAPI(id),
|
|
436
432
|
},
|
|
437
|
-
|
|
438
|
-
url: 'https://your-api.com/gaps',
|
|
439
|
-
},
|
|
433
|
+
issuesUrl: 'https://github.com/your-org/your-repo/issues',
|
|
440
434
|
};
|
|
441
435
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.2-canary.
|
|
3
|
+
"version": "0.1.2-canary.f330539",
|
|
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",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"zod": "^4.4.3"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@astryxdesign/core": "0.1.2-canary.
|
|
79
|
-
"@astryxdesign/lab": "0.1.2-canary.
|
|
80
|
-
"@astryxdesign/theme-neutral": "0.1.2-canary.
|
|
78
|
+
"@astryxdesign/core": "0.1.2-canary.f330539",
|
|
79
|
+
"@astryxdesign/lab": "0.1.2-canary.f330539",
|
|
80
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.f330539",
|
|
81
81
|
"gpt-tokenizer": "^2.0.0"
|
|
82
82
|
},
|
|
83
83
|
"peerDependenciesMeta": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@astryxdesign/core": "0.1.2-canary.
|
|
96
|
-
"@astryxdesign/lab": "0.1.2-canary.
|
|
97
|
-
"@astryxdesign/theme-neutral": "0.1.2-canary.
|
|
95
|
+
"@astryxdesign/core": "0.1.2-canary.f330539",
|
|
96
|
+
"@astryxdesign/lab": "0.1.2-canary.f330539",
|
|
97
|
+
"@astryxdesign/theme-neutral": "0.1.2-canary.f330539",
|
|
98
98
|
"gpt-tokenizer": "^2.0.0"
|
|
99
99
|
},
|
|
100
100
|
"scripts": {
|
package/src/api/doctor.mjs
CHANGED
|
@@ -240,7 +240,7 @@ export function checkThemes(ctx) {
|
|
|
240
240
|
label: 'Theme packages',
|
|
241
241
|
status: 'warn',
|
|
242
242
|
message: 'No @astryxdesign/theme-* packages are installed.',
|
|
243
|
-
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set
|
|
243
|
+
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set astryx.theme.',
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -354,7 +354,7 @@ export function checkAgentDocs(ctx) {
|
|
|
354
354
|
label: 'AI agent docs',
|
|
355
355
|
status: 'warn',
|
|
356
356
|
message: `Agent docs present (${present.join(', ')}) but no Astryx section markers found.`,
|
|
357
|
-
fix: 'Add the
|
|
357
|
+
fix: 'Add the Astryx section to your agent docs with `astryx init --features agents`.',
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
360
|
|
|
@@ -362,7 +362,7 @@ export function checkAgentDocs(ctx) {
|
|
|
362
362
|
id: 'agent-docs',
|
|
363
363
|
label: 'AI agent docs',
|
|
364
364
|
status: 'pass',
|
|
365
|
-
message: `
|
|
365
|
+
message: `Astryx agent docs section present in ${withMarkers.join(', ')}.`,
|
|
366
366
|
};
|
|
367
367
|
}
|
|
368
368
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
async function applyTransform(source, filePath = 'test.css') {
|
|
6
|
+
const {default: transform} = await import('../migrate-xds-css-surfaces.mjs');
|
|
7
|
+
// api.jscodeshift is unused by this CSS codemod, but the runner still
|
|
8
|
+
// passes it — mirror that shape.
|
|
9
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
10
|
+
const api = {jscodeshift, stats: () => {}, report: () => {}};
|
|
11
|
+
const file = {source, path: filePath};
|
|
12
|
+
const result = transform(file, api);
|
|
13
|
+
return result ?? source;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('migrate-xds-css-surfaces', () => {
|
|
17
|
+
it('rewrites the .xds- class-selector prefix', async () => {
|
|
18
|
+
const input = '.xds-heading { color: red; }';
|
|
19
|
+
const output = await applyTransform(input);
|
|
20
|
+
expect(output).toBe('.astryx-heading { color: red; }');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('rewrites the [data-xds-theme] attribute selector', async () => {
|
|
24
|
+
const input = '[data-xds-theme="dark"] .xds-card { background: black; }';
|
|
25
|
+
const output = await applyTransform(input);
|
|
26
|
+
expect(output).toContain('[data-astryx-theme="dark"]');
|
|
27
|
+
expect(output).toContain('.astryx-card');
|
|
28
|
+
expect(output).not.toContain('xds-');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('rewrites data-xds-theme-prose and data-xds-media attribute selectors', async () => {
|
|
32
|
+
const input =
|
|
33
|
+
'[data-xds-theme-prose] {} [data-xds-media="print"] {}';
|
|
34
|
+
const output = await applyTransform(input);
|
|
35
|
+
expect(output).toContain('[data-astryx-theme-prose]');
|
|
36
|
+
expect(output).toContain('[data-astryx-media="print"]');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rewrites @layer xds-theme and @layer xds-base', async () => {
|
|
40
|
+
const input = '@layer xds-theme, xds-base;\n@layer xds-theme { a { color: red; } }';
|
|
41
|
+
const output = await applyTransform(input);
|
|
42
|
+
expect(output).toContain('astryx-theme');
|
|
43
|
+
expect(output).toContain('astryx-base');
|
|
44
|
+
expect(output).not.toContain('xds-theme');
|
|
45
|
+
expect(output).not.toContain('xds-base');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('does NOT rewrite a bare "xds" in a comment or value', async () => {
|
|
49
|
+
const input = '/* xds tokens live here */\n.card { content: "xds"; }';
|
|
50
|
+
const output = await applyTransform(input);
|
|
51
|
+
expect(output).toBe(input);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does NOT rewrite a class-like word missing the leading dot', async () => {
|
|
55
|
+
// e.g. inside a JS-ish string or a comment fragment — not a class selector.
|
|
56
|
+
const input = '/* use xds-heading in markup */';
|
|
57
|
+
const output = await applyTransform(input);
|
|
58
|
+
expect(output).toBe(input);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('leaves already-migrated CSS unchanged', async () => {
|
|
62
|
+
const input =
|
|
63
|
+
'@layer astryx-theme { [data-astryx-theme="dark"] .astryx-card {} }';
|
|
64
|
+
const output = await applyTransform(input);
|
|
65
|
+
expect(output).toBe(input);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
async function applyTransform(source, filePath = 'test.d.ts') {
|
|
6
|
+
const {default: transform} = await import('../migrate-xds-declare-module.mjs');
|
|
7
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
8
|
+
const j = jscodeshift.withParser('tsx');
|
|
9
|
+
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
10
|
+
const file = {source, path: filePath};
|
|
11
|
+
const result = transform(file, api);
|
|
12
|
+
return result ?? source;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('migrate-xds-declare-module', () => {
|
|
16
|
+
it('renames a subpath declare module', async () => {
|
|
17
|
+
const input = [
|
|
18
|
+
"declare module '@xds/core/Heading' {",
|
|
19
|
+
' export const HeadingLevelMap: Record<string, number>;',
|
|
20
|
+
'}',
|
|
21
|
+
].join('\n');
|
|
22
|
+
|
|
23
|
+
const output = await applyTransform(input);
|
|
24
|
+
expect(output).toMatch(/declare module ['"]@astryxdesign\/core\/Heading['"]/);
|
|
25
|
+
expect(output).not.toContain('@xds/core');
|
|
26
|
+
// The augmentation body identifier is untouched.
|
|
27
|
+
expect(output).toContain('HeadingLevelMap');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renames a bare @xds/core declare module', async () => {
|
|
31
|
+
const input = "declare module '@xds/core' {\n const x: number;\n}";
|
|
32
|
+
const output = await applyTransform(input);
|
|
33
|
+
expect(output).toMatch(/declare module ['"]@astryxdesign\/core['"]/);
|
|
34
|
+
expect(output).not.toContain('@xds/core');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renames @xds/lab declare module using the shared map', async () => {
|
|
38
|
+
const input = "declare module '@xds/lab/Thing' {\n const y: number;\n}";
|
|
39
|
+
const output = await applyTransform(input);
|
|
40
|
+
expect(output).toMatch(/declare module ['"]@astryxdesign\/lab\/Thing['"]/);
|
|
41
|
+
expect(output).not.toContain('@xds/lab');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('leaves non-xds declare modules alone', async () => {
|
|
45
|
+
const input = "declare module 'react' {\n const z: number;\n}";
|
|
46
|
+
const output = await applyTransform(input);
|
|
47
|
+
expect(output).toBe(input);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('leaves an already-migrated declare module unchanged', async () => {
|
|
51
|
+
const input = "declare module '@astryxdesign/core/Heading' {\n const a: number;\n}";
|
|
52
|
+
const output = await applyTransform(input);
|
|
53
|
+
expect(output).toBe(input);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('does not touch namespace declarations (Identifier id)', async () => {
|
|
57
|
+
const input = 'declare namespace XDSCore {\n const b: number;\n}';
|
|
58
|
+
const output = await applyTransform(input, 'test.ts');
|
|
59
|
+
expect(output).toBe(input);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -41,6 +41,29 @@ describe('v0.1.0 codemod manifest ordering', () => {
|
|
|
41
41
|
expect(entry.optional).toBeFalsy();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
it('orders declare-module and CSS codemods after migrate-xds-module-specifiers', async () => {
|
|
45
|
+
const {default: manifest} = await import('../index.mjs');
|
|
46
|
+
const names = manifest.map(entry => entry.name);
|
|
47
|
+
expect(names).toEqual([
|
|
48
|
+
'drop-xds-prefix-imports',
|
|
49
|
+
'migrate-xds-module-specifiers',
|
|
50
|
+
'migrate-xds-declare-module',
|
|
51
|
+
'migrate-xds-css-surfaces',
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('marks migrate-xds-declare-module and migrate-xds-css-surfaces mandatory', async () => {
|
|
56
|
+
const {default: manifest} = await import('../index.mjs');
|
|
57
|
+
for (const name of [
|
|
58
|
+
'migrate-xds-declare-module',
|
|
59
|
+
'migrate-xds-css-surfaces',
|
|
60
|
+
]) {
|
|
61
|
+
const entry = manifest.find(e => e.name === name);
|
|
62
|
+
expect(entry, name).toBeDefined();
|
|
63
|
+
expect(entry.optional).toBeFalsy();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
44
67
|
it('un-prefixes useXDSTheme from @xds/core/theme AND renames the scope to @astryxdesign', async () => {
|
|
45
68
|
const input = [
|
|
46
69
|
"import {useXDSTheme} from '@xds/core/theme';",
|
|
@@ -14,6 +14,14 @@ import migrateXdsModuleSpecifiers, {
|
|
|
14
14
|
meta as migrateXdsModuleSpecifiersMeta,
|
|
15
15
|
} from './migrate-xds-module-specifiers.mjs';
|
|
16
16
|
|
|
17
|
+
import migrateXdsDeclareModule, {
|
|
18
|
+
meta as migrateXdsDeclareModuleMeta,
|
|
19
|
+
} from './migrate-xds-declare-module.mjs';
|
|
20
|
+
|
|
21
|
+
import migrateXdsCssSurfaces, {
|
|
22
|
+
meta as migrateXdsCssSurfacesMeta,
|
|
23
|
+
} from './migrate-xds-css-surfaces.mjs';
|
|
24
|
+
|
|
17
25
|
export default [
|
|
18
26
|
{
|
|
19
27
|
// XDS-prefix migration (P2380608025). Mandatory in v0.1.0: the release
|
|
@@ -34,4 +42,24 @@ export default [
|
|
|
34
42
|
transform: migrateXdsModuleSpecifiers,
|
|
35
43
|
meta: migrateXdsModuleSpecifiersMeta,
|
|
36
44
|
},
|
|
45
|
+
{
|
|
46
|
+
// Cleans up TypeScript `declare module "@xds/core/..."` augmentations,
|
|
47
|
+
// which reference the package by string specifier and so are dead after
|
|
48
|
+
// the scope rename. Mandatory in v0.1.0. Ordered AFTER the specifier
|
|
49
|
+
// codemod: it uses the same @xds -> @astryxdesign mapping and repairs an
|
|
50
|
+
// adjacent surface the specifier codemod does not visit (module-decl ids
|
|
51
|
+
// are not import/export/require sources).
|
|
52
|
+
name: 'migrate-xds-declare-module',
|
|
53
|
+
transform: migrateXdsDeclareModule,
|
|
54
|
+
meta: migrateXdsDeclareModuleMeta,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
// Rewrites consumer CSS surfaces broken by the v0.1.0 DOM-namespace rename
|
|
58
|
+
// (core no longer dual-emits `xds-*`): the `.xds-*` class-selector prefix,
|
|
59
|
+
// `[data-xds-*]` attribute selectors, and `@layer xds-*` layer names all
|
|
60
|
+
// become their `astryx-*` forms. Mandatory in v0.1.0.
|
|
61
|
+
name: 'migrate-xds-css-surfaces',
|
|
62
|
+
transform: migrateXdsCssSurfaces,
|
|
63
|
+
meta: migrateXdsCssSurfacesMeta,
|
|
64
|
+
},
|
|
37
65
|
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Codemod: migrate XDS CSS surfaces to their Astryx equivalents
|
|
5
|
+
*
|
|
6
|
+
* The v0.1.0 release renamed the DOM namespace emitted by @astryxdesign/core
|
|
7
|
+
* from `xds-*` to `astryx-*` and dropped the dual-emit compatibility (core no
|
|
8
|
+
* longer emits the legacy `xds-*` classes or `data-xds-*` attributes). Any
|
|
9
|
+
* consumer CSS that targeted those surfaces is now broken.
|
|
10
|
+
*
|
|
11
|
+
* This transform performs precise, targeted string replacements on CSS/SCSS
|
|
12
|
+
* files for the documented surfaces only:
|
|
13
|
+
*
|
|
14
|
+
* .xds-* (class-name prefix in selectors — matched only
|
|
15
|
+
* after a `.` so it is a class selector)
|
|
16
|
+
* [data-xds-theme (theme attribute selector)
|
|
17
|
+
* [data-xds-theme-prose (prose-theme attribute selector)
|
|
18
|
+
* [data-xds-media (media attribute selector)
|
|
19
|
+
* @layer xds-theme (cascade layer name — incl. comma-separated
|
|
20
|
+
* @layer xds-base `@layer a, b;` statement lists and nested blocks)
|
|
21
|
+
*
|
|
22
|
+
* It deliberately does NOT blindly replace every `xds` substring: a bare
|
|
23
|
+
* `xds` in a comment, a custom-property value, or an unrelated identifier is
|
|
24
|
+
* left untouched. CSS has no meaningful AST for this class of edit, so this is
|
|
25
|
+
* a plain string transform — `api.jscodeshift` is available but unused.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const meta = {
|
|
29
|
+
title: 'Migrate .xds-* / [data-xds-*] / @layer xds-* CSS surfaces to astryx',
|
|
30
|
+
description:
|
|
31
|
+
'Rewrites the documented XDS CSS surfaces to their Astryx equivalents: ' +
|
|
32
|
+
'the `.xds-*` class-selector prefix, `[data-xds-theme]` / ' +
|
|
33
|
+
'`[data-xds-theme-prose]` / `[data-xds-media]` attribute selectors, and ' +
|
|
34
|
+
'`@layer xds-theme` / `@layer xds-base` cascade-layer names all become ' +
|
|
35
|
+
'their `astryx-*` / `data-astryx-*` forms.',
|
|
36
|
+
pr: '#3092',
|
|
37
|
+
fileExtensions: ['.css', '.scss'],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// The XDS cascade-layer names we own. A `@layer` prelude can name several
|
|
41
|
+
// layers in a comma-separated list (`@layer a, b;`), so we can't anchor every
|
|
42
|
+
// occurrence to the `@layer` keyword. We instead rewrite these exact layer
|
|
43
|
+
// tokens within a `@layer ...` prelude (up to the next `{` or `;`).
|
|
44
|
+
const XDS_LAYER_NAMES = new Map([
|
|
45
|
+
['xds-theme', 'astryx-theme'],
|
|
46
|
+
['xds-base', 'astryx-base'],
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
function rewriteLayerPrelude(prelude) {
|
|
50
|
+
return prelude.replace(/\bxds-(?:theme|base)\b/g, name =>
|
|
51
|
+
XDS_LAYER_NAMES.get(name) ?? name,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Ordered, non-overlapping replacements. Each pattern is intentionally narrow
|
|
56
|
+
// so we never touch a bare `xds` substring outside these surfaces.
|
|
57
|
+
const REPLACEMENTS = [
|
|
58
|
+
// Class-name prefix: only when preceded by a `.` (a class selector).
|
|
59
|
+
{re: /\.xds-/g, to: '.astryx-'},
|
|
60
|
+
// Attribute selectors: any `[data-xds-` opener (theme, theme-prose, media).
|
|
61
|
+
{re: /\[data-xds-/g, to: '[data-astryx-'},
|
|
62
|
+
// Cascade-layer preludes: `@layer <names>` up to the block `{` or `;`.
|
|
63
|
+
// Rewrite only the recognized xds-* layer tokens inside the prelude.
|
|
64
|
+
{re: /@layer\b[^{;]*/g, to: match => rewriteLayerPrelude(match)},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
export default function transformer(file /*, api */) {
|
|
68
|
+
const source = file.source;
|
|
69
|
+
if (typeof source !== 'string') return undefined;
|
|
70
|
+
|
|
71
|
+
let next = source;
|
|
72
|
+
for (const {re, to} of REPLACEMENTS) {
|
|
73
|
+
next = next.replace(re, to);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return next === source ? undefined : next;
|
|
77
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Codemod: migrate `declare module "@xds/..."` augmentations to
|
|
5
|
+
* `@astryxdesign/...`
|
|
6
|
+
*
|
|
7
|
+
* The v0.1.0 release moved the public package scope from @xds to
|
|
8
|
+
* @astryxdesign. TypeScript module augmentations (`declare module "@xds/core"
|
|
9
|
+
* { ... }`) reference the package by its string specifier, so they are dead
|
|
10
|
+
* after the scope rename: TypeScript resolves the augmentation against a
|
|
11
|
+
* module that no longer exists.
|
|
12
|
+
*
|
|
13
|
+
* This transform rewrites the augmented module string on `TSModuleDeclaration`
|
|
14
|
+
* nodes whose `id` is a StringLiteral matching a renamed @xds package (or one
|
|
15
|
+
* of its subpaths). It only touches the module path — identifiers *inside* the
|
|
16
|
+
* augmentation (e.g. an augmented interface name) are a separate concern
|
|
17
|
+
* handled by drop-xds-prefix-imports.
|
|
18
|
+
*
|
|
19
|
+
* Ordered AFTER migrate-xds-module-specifiers in the v0.1.0 manifest: it uses
|
|
20
|
+
* the same PACKAGE_RENAMES mapping and cleans up the module-augmentation
|
|
21
|
+
* surface alongside the import/export/require specifier rename.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const meta = {
|
|
25
|
+
title: 'Migrate declare-module augmentations from @xds/* to @astryxdesign/*',
|
|
26
|
+
description:
|
|
27
|
+
'Rewrites the module specifier on TypeScript `declare module "@xds/..."` ' +
|
|
28
|
+
'augmentations to the @astryxdesign/* packages used by Astryx v0.1.0. ' +
|
|
29
|
+
'Only the augmented module path is changed; identifiers inside the ' +
|
|
30
|
+
'augmentation are left untouched.',
|
|
31
|
+
pr: '#3092',
|
|
32
|
+
fileExtensions: ['.ts', '.d.ts', '.tsx'],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const PACKAGE_RENAMES = new Map([
|
|
36
|
+
['@xds/build', '@astryxdesign/build'],
|
|
37
|
+
['@xds/cli', '@astryxdesign/cli'],
|
|
38
|
+
['@xds/core', '@astryxdesign/core'],
|
|
39
|
+
['@xds/lab', '@astryxdesign/lab'],
|
|
40
|
+
['@xds/theme-butter', '@astryxdesign/theme-butter'],
|
|
41
|
+
['@xds/theme-chocolate', '@astryxdesign/theme-chocolate'],
|
|
42
|
+
['@xds/theme-daily', '@astryxdesign/theme-neutral'],
|
|
43
|
+
['@xds/theme-default', '@astryxdesign/theme-neutral'],
|
|
44
|
+
['@xds/theme-gothic', '@astryxdesign/theme-gothic'],
|
|
45
|
+
['@xds/theme-matcha', '@astryxdesign/theme-matcha'],
|
|
46
|
+
['@xds/theme-neutral', '@astryxdesign/theme-neutral'],
|
|
47
|
+
['@xds/theme-stone', '@astryxdesign/theme-stone'],
|
|
48
|
+
['@xds/theme-y2k', '@astryxdesign/theme-y2k'],
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
function renamePackageSpecifier(value) {
|
|
52
|
+
if (typeof value !== 'string') return value;
|
|
53
|
+
for (const [from, to] of PACKAGE_RENAMES) {
|
|
54
|
+
if (value === from) return to;
|
|
55
|
+
if (value.startsWith(from + '/')) return to + value.slice(from.length);
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function transformer(file, api) {
|
|
61
|
+
const j = api.jscodeshift;
|
|
62
|
+
const root = j(file.source);
|
|
63
|
+
let hasChanges = false;
|
|
64
|
+
|
|
65
|
+
root.find(j.TSModuleDeclaration).forEach(path => {
|
|
66
|
+
const id = path.node.id;
|
|
67
|
+
// Module augmentations name the module with a string literal id; a
|
|
68
|
+
// `namespace Foo {}` uses an Identifier id, which we ignore.
|
|
69
|
+
if (!id || (id.type !== 'StringLiteral' && id.type !== 'Literal')) return;
|
|
70
|
+
if (typeof id.value !== 'string') return;
|
|
71
|
+
const next = renamePackageSpecifier(id.value);
|
|
72
|
+
if (next === id.value) return;
|
|
73
|
+
id.value = next;
|
|
74
|
+
hasChanges = true;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return hasChanges ? root.toSource() : undefined;
|
|
78
|
+
}
|
|
@@ -207,7 +207,7 @@ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPre
|
|
|
207
207
|
.sort();
|
|
208
208
|
if (topics.length > 0) lines.push(` docs <topic> ${topics.join(', ')}`);
|
|
209
209
|
}
|
|
210
|
-
lines.push(' swizzle <Name> eject component source
|
|
210
|
+
lines.push(' swizzle <Name> eject component source for deep customization');
|
|
211
211
|
lines.push(' upgrade --apply run after any @astryxdesign/core bump');
|
|
212
212
|
lines.push(MARKER_END);
|
|
213
213
|
|
|
@@ -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: 'Blockquote',
|
|
7
|
+
name: 'Blockquote — Testimonials',
|
|
8
|
+
displayName: 'Blockquote — Testimonials',
|
|
9
|
+
description:
|
|
10
|
+
'Multiple quotes arranged in a card grid for a testimonials section. Combine with Card and Grid to create social-proof layouts.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Blockquote', 'Card', 'Grid', 'GridSpan'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {Blockquote} from '@astryxdesign/core/Blockquote';
|
|
6
|
+
import {Grid, GridSpan} from '@astryxdesign/core/Grid';
|
|
7
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
8
|
+
|
|
9
|
+
export default function BlockquoteTestimonials() {
|
|
10
|
+
return (
|
|
11
|
+
<Grid columns={2} gap={3} style={{maxWidth: 600}}>
|
|
12
|
+
<Card padding={4}>
|
|
13
|
+
<Blockquote cite="Sarah K., Lead Engineer">
|
|
14
|
+
Shipping UI has never been this fast. Our team went from design to
|
|
15
|
+
production in a single afternoon.
|
|
16
|
+
</Blockquote>
|
|
17
|
+
</Card>
|
|
18
|
+
<Card padding={4}>
|
|
19
|
+
<Blockquote cite="Marcus T., Product Designer">
|
|
20
|
+
The token system means every new screen just looks right — no manual
|
|
21
|
+
color or spacing decisions.
|
|
22
|
+
</Blockquote>
|
|
23
|
+
</Card>
|
|
24
|
+
<GridSpan columns={2}>
|
|
25
|
+
<Card padding={4}>
|
|
26
|
+
<Blockquote cite="Priya L., Engineering Manager">
|
|
27
|
+
Onboarding a new engineer used to take a week of design review. Now
|
|
28
|
+
they're shipping accessible, polished components on day one.
|
|
29
|
+
</Blockquote>
|
|
30
|
+
</Card>
|
|
31
|
+
</GridSpan>
|
|
32
|
+
</Grid>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -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: 'Blockquote',
|
|
7
|
+
name: 'Blockquote — With Attribution',
|
|
8
|
+
displayName: 'Blockquote — With Attribution',
|
|
9
|
+
description:
|
|
10
|
+
'A plain quote and a quote with a cite attribution. Use cite to credit the original author or source.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Blockquote', 'Layout'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {Blockquote} from '@astryxdesign/core/Blockquote';
|
|
6
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
7
|
+
|
|
8
|
+
export default function BlockquoteWithCite() {
|
|
9
|
+
return (
|
|
10
|
+
<VStack gap={4} style={{maxWidth: 500}}>
|
|
11
|
+
<Blockquote>
|
|
12
|
+
Design is not just what it looks like and feels like. Design is how it
|
|
13
|
+
works.
|
|
14
|
+
</Blockquote>
|
|
15
|
+
<Blockquote cite="Steve Jobs">
|
|
16
|
+
The people who are crazy enough to think they can change the world are
|
|
17
|
+
the ones who do.
|
|
18
|
+
</Blockquote>
|
|
19
|
+
</VStack>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -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: 'useKeyboardHint',
|
|
7
|
+
name: 'useKeyboardHint — Arrow-key Hint',
|
|
8
|
+
displayName: 'useKeyboardHint — Arrow-key Hint',
|
|
9
|
+
description:
|
|
10
|
+
'Toolbar shows an ephemeral "← → to navigate" hint on first keyboard focus via useKeyboardHint, teaching sighted keyboard users that arrows move within the group.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['Toolbar', 'Button', 'Icon', 'Card', 'Section', 'Text', 'Layout'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {Toolbar} from '@astryxdesign/core/Toolbar';
|
|
6
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
7
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
8
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
9
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
10
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
11
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
12
|
+
import {BoldIcon, ItalicIcon, UnderlineIcon} from '@heroicons/react/24/outline';
|
|
13
|
+
|
|
14
|
+
export default function UseKeyboardHintHookUsage() {
|
|
15
|
+
return (
|
|
16
|
+
<Card style={{width: 420}}>
|
|
17
|
+
<Toolbar
|
|
18
|
+
label="Text formatting"
|
|
19
|
+
dividers={['bottom']}
|
|
20
|
+
startContent={
|
|
21
|
+
<>
|
|
22
|
+
<Button
|
|
23
|
+
label="Bold"
|
|
24
|
+
variant="ghost"
|
|
25
|
+
icon={<Icon icon={BoldIcon} />}
|
|
26
|
+
isIconOnly
|
|
27
|
+
/>
|
|
28
|
+
<Button
|
|
29
|
+
label="Italic"
|
|
30
|
+
variant="ghost"
|
|
31
|
+
icon={<Icon icon={ItalicIcon} />}
|
|
32
|
+
isIconOnly
|
|
33
|
+
/>
|
|
34
|
+
<Button
|
|
35
|
+
label="Underline"
|
|
36
|
+
variant="ghost"
|
|
37
|
+
icon={<Icon icon={UnderlineIcon} />}
|
|
38
|
+
isIconOnly
|
|
39
|
+
/>
|
|
40
|
+
</>
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
<Section>
|
|
44
|
+
<VStack gap={1}>
|
|
45
|
+
<Text type="body" weight="bold">
|
|
46
|
+
Keyboard-friendly by default
|
|
47
|
+
</Text>
|
|
48
|
+
<Text type="supporting" color="secondary">
|
|
49
|
+
Tab into the toolbar with your keyboard and Toolbar shows an
|
|
50
|
+
ephemeral "← → to navigate" hint — powered by useKeyboardHint — so
|
|
51
|
+
sighted keyboard users learn that arrows move within the group.
|
|
52
|
+
</Text>
|
|
53
|
+
</VStack>
|
|
54
|
+
</Section>
|
|
55
|
+
</Card>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -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: 'Lightbox',
|
|
7
|
+
name: 'Lightbox — Gallery',
|
|
8
|
+
displayName: 'Lightbox — Gallery',
|
|
9
|
+
description:
|
|
10
|
+
'A thumbnail grid that opens a fullscreen gallery. Clicking any thumbnail opens the lightbox at that index. Prev/next navigation lets users browse all images without closing.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Lightbox', 'Grid', 'Thumbnail'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useLightbox} from '@astryxdesign/core/Lightbox';
|
|
6
|
+
import {Grid} from '@astryxdesign/core/Grid';
|
|
7
|
+
import {Thumbnail} from '@astryxdesign/core/Thumbnail';
|
|
8
|
+
|
|
9
|
+
const PHOTOS = [
|
|
10
|
+
{
|
|
11
|
+
src: 'https://lookaside.facebook.com/assets/astryx/Neutral-Backpack.png',
|
|
12
|
+
alt: 'Backpack',
|
|
13
|
+
caption: 'A backpack displayed on a neutral background.',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
src: 'https://lookaside.facebook.com/assets/astryx/building.png',
|
|
17
|
+
alt: 'Modern building',
|
|
18
|
+
caption: 'A modern building with a contemporary architectural design.',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
src: 'https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png',
|
|
22
|
+
alt: 'Coastal shoreline with ocean waves',
|
|
23
|
+
caption:
|
|
24
|
+
'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
src: 'https://lookaside.facebook.com/assets/astryx/illustrative-vertical-1.png',
|
|
28
|
+
alt: 'Illustrated lakeside landscape at sunset',
|
|
29
|
+
caption:
|
|
30
|
+
'A stylized landscape illustration featuring pink clouds reflected over a calm lake at sunset.',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export default function LightboxGallery() {
|
|
35
|
+
const lightbox = useLightbox({media: PHOTOS});
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Grid columns={2} gap={2} style={{width: 136}}>
|
|
40
|
+
{PHOTOS.map((photo, i) => (
|
|
41
|
+
<Thumbnail
|
|
42
|
+
key={photo.src}
|
|
43
|
+
src={photo.src}
|
|
44
|
+
alt={photo.alt}
|
|
45
|
+
label={photo.alt}
|
|
46
|
+
onClick={() => lightbox.open(i)}
|
|
47
|
+
/>
|
|
48
|
+
))}
|
|
49
|
+
</Grid>
|
|
50
|
+
{lightbox.element}
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -12,9 +12,10 @@ export default function LightboxShowcase() {
|
|
|
12
12
|
isOpen={isOpen}
|
|
13
13
|
onOpenChange={setIsOpen}
|
|
14
14
|
media={{
|
|
15
|
-
src: 'https://
|
|
16
|
-
alt: '
|
|
17
|
-
caption:
|
|
15
|
+
src: 'https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png',
|
|
16
|
+
alt: 'Coastal shoreline with ocean waves',
|
|
17
|
+
caption:
|
|
18
|
+
'A scenic coastline with waves rolling onto a sandy beach beneath a clear sky.',
|
|
18
19
|
}}
|
|
19
20
|
/>
|
|
20
21
|
</>
|
|
@@ -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: 'Lightbox',
|
|
7
|
+
name: 'Lightbox — Video',
|
|
8
|
+
displayName: 'Lightbox — Video',
|
|
9
|
+
description:
|
|
10
|
+
'Opens a video in the lightbox. Native browser controls are available. Zoom and pan are disabled for video items.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Lightbox'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Lightbox} from '@astryxdesign/core/Lightbox';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
|
|
9
|
+
export default function LightboxVideo() {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Button label="Play video" onClick={() => setIsOpen(true)} />
|
|
15
|
+
<Lightbox
|
|
16
|
+
isOpen={isOpen}
|
|
17
|
+
onOpenChange={setIsOpen}
|
|
18
|
+
media={{
|
|
19
|
+
src: 'https://lookaside.facebook.com/assets/?set=astryx&name=Nature-1&density=1',
|
|
20
|
+
alt: 'Flower blooming in time-lapse',
|
|
21
|
+
type: 'video',
|
|
22
|
+
caption: 'A flower blooming in time-lapse',
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -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: 'Lightbox',
|
|
7
|
+
name: 'Lightbox — Zoom',
|
|
8
|
+
displayName: 'Lightbox — Zoom',
|
|
9
|
+
description:
|
|
10
|
+
'A lightbox with zoom and pan enabled. Double-click the image to zoom in; drag to pan around. Double-click again or use the close button to exit.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Lightbox', 'Thumbnail'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Lightbox} from '@astryxdesign/core/Lightbox';
|
|
7
|
+
import {Thumbnail} from '@astryxdesign/core/Thumbnail';
|
|
8
|
+
|
|
9
|
+
export default function LightboxZoom() {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<Thumbnail
|
|
15
|
+
src="https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png"
|
|
16
|
+
alt="Coastal shoreline with ocean waves"
|
|
17
|
+
label="Coastal shoreline with ocean waves"
|
|
18
|
+
onClick={() => setIsOpen(true)}
|
|
19
|
+
/>
|
|
20
|
+
<Lightbox
|
|
21
|
+
isOpen={isOpen}
|
|
22
|
+
onOpenChange={setIsOpen}
|
|
23
|
+
media={{
|
|
24
|
+
src: 'https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png',
|
|
25
|
+
alt: 'Coastal shoreline with ocean waves',
|
|
26
|
+
caption:
|
|
27
|
+
'A scenic coastline. Double-click to zoom in and drag to pan.',
|
|
28
|
+
}}
|
|
29
|
+
hasZoom
|
|
30
|
+
/>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -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: 'ToggleButtonGroup',
|
|
7
|
+
name: 'ToggleButtonGroup — Vertical',
|
|
8
|
+
displayName: 'ToggleButtonGroup — Vertical',
|
|
9
|
+
description:
|
|
10
|
+
'A vertically stacked ToggleButtonGroup using the vertical orientation, shown with both single-select and multi-select behavior — ideal for sidebar-style option lists and vertical toolbars.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: ['ToggleButton', 'ToggleButtonGroup', 'Layout', 'Text'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {ToggleButton, ToggleButtonGroup} from '@astryxdesign/core/ToggleButton';
|
|
7
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
8
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
9
|
+
|
|
10
|
+
export default function ToggleButtonGroupVertical() {
|
|
11
|
+
const [view, setView] = useState<string | null>('grid');
|
|
12
|
+
const [filters, setFilters] = useState<string[]>(['active']);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<VStack gap={4}>
|
|
16
|
+
<VStack gap={1}>
|
|
17
|
+
<Text type="label" color="secondary">
|
|
18
|
+
Single select
|
|
19
|
+
</Text>
|
|
20
|
+
<ToggleButtonGroup
|
|
21
|
+
orientation="vertical"
|
|
22
|
+
value={view}
|
|
23
|
+
onChange={setView}
|
|
24
|
+
label="View mode">
|
|
25
|
+
<ToggleButton value="list" label="List" />
|
|
26
|
+
<ToggleButton value="grid" label="Grid" />
|
|
27
|
+
<ToggleButton value="board" label="Board" />
|
|
28
|
+
</ToggleButtonGroup>
|
|
29
|
+
</VStack>
|
|
30
|
+
<VStack gap={1}>
|
|
31
|
+
<Text type="label" color="secondary">
|
|
32
|
+
Multi select
|
|
33
|
+
</Text>
|
|
34
|
+
<ToggleButtonGroup
|
|
35
|
+
orientation="vertical"
|
|
36
|
+
type="multiple"
|
|
37
|
+
value={filters}
|
|
38
|
+
onChange={setFilters}
|
|
39
|
+
label="Status filters">
|
|
40
|
+
<ToggleButton value="active" label="Active" />
|
|
41
|
+
<ToggleButton value="pending" label="Pending" />
|
|
42
|
+
<ToggleButton value="closed" label="Closed" />
|
|
43
|
+
</ToggleButtonGroup>
|
|
44
|
+
</VStack>
|
|
45
|
+
</VStack>
|
|
46
|
+
);
|
|
47
|
+
}
|