@astryxdesign/cli 0.1.6 → 0.1.7-canary.0a6de96

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 (69) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +97 -7
  3. package/docs/cli-integrations.doc.mjs +150 -0
  4. package/docs/internationalization.doc.mjs +243 -0
  5. package/docs/layout.doc.dense.mjs +5 -0
  6. package/docs/principles.doc.dense.mjs +5 -5
  7. package/docs/principles.doc.zh.mjs +5 -5
  8. package/docs/theme.doc.dense.mjs +12 -12
  9. package/docs/theme.doc.zh.mjs +10 -10
  10. package/docs/tokens.doc.dense.mjs +6 -7
  11. package/docs/tokens.doc.zh.mjs +6 -7
  12. package/package.json +13 -9
  13. package/src/api/docOverlays.test.mjs +133 -0
  14. package/src/api/docs.mjs +14 -2
  15. package/src/api/integration-block-exports.test.mjs +240 -0
  16. package/src/api/template-suffix.test.mjs +246 -0
  17. package/src/api/template.mjs +104 -28
  18. package/src/api/validate-integration.mjs +0 -8
  19. package/src/codemods/__tests__/registry.test.mjs +1 -0
  20. package/src/codemods/registry.mjs +1 -0
  21. package/src/codemods/transforms/v0.1.7/__tests__/migrate-table-tableprops-to-direct-props.test.mjs +120 -0
  22. package/src/codemods/transforms/v0.1.7/__tests__/rename-table-renderprops-styles-to-xstyle.test.mjs +112 -0
  23. package/src/codemods/transforms/v0.1.7/index.mjs +27 -0
  24. package/src/codemods/transforms/v0.1.7/migrate-table-tableprops-to-direct-props.mjs +188 -0
  25. package/src/codemods/transforms/v0.1.7/rename-table-renderprops-styles-to-xstyle.mjs +197 -0
  26. package/src/commands/agent-docs.mjs +14 -0
  27. package/src/commands/agent-docs.test.mjs +20 -0
  28. package/src/config.mjs +5 -14
  29. package/src/doc.mjs +27 -0
  30. package/src/doc.test.mjs +383 -0
  31. package/src/integration.mjs +4 -15
  32. package/src/lib/component-discovery.importpath.test.mjs +59 -0
  33. package/src/lib/component-discovery.mjs +15 -5
  34. package/src/lib/component-format.mjs +45 -13
  35. package/src/lib/component-format.test.mjs +95 -1
  36. package/src/lib/component-loader.mjs +104 -2
  37. package/src/lib/componentDocOverlay.test.mjs +111 -0
  38. package/src/lib/config-schema.mjs +0 -30
  39. package/src/lib/hook-format.mjs +8 -3
  40. package/src/lib/xle/registry.mjs +0 -5
  41. package/src/schemas/doc-schema.mjs +226 -0
  42. package/src/schemas/template-schema.mjs +47 -0
  43. package/src/template.mjs +9 -67
  44. package/src/types/config.d.ts +11 -66
  45. package/src/types/doc.d.ts +23 -0
  46. package/src/types/integration.d.ts +7 -18
  47. package/src/types/template-api.d.ts +14 -50
  48. package/templates/blocks/components/Avatar/AvatarGroup.tsx +5 -7
  49. package/templates/blocks/components/Avatar/AvatarShowcase.tsx +4 -6
  50. package/templates/blocks/components/Avatar/AvatarUserCard.tsx +3 -5
  51. package/templates/blocks/components/Avatar/AvatarWithImage.tsx +8 -6
  52. package/templates/blocks/components/Avatar/AvatarWithStatus.tsx +3 -5
  53. package/templates/blocks/components/ChatComposerInput/ChatComposerInputControlledInput.tsx +1 -1
  54. package/templates/blocks/components/ChatComposerInput/ChatComposerInputDisabled.tsx +1 -1
  55. package/templates/blocks/components/ChatComposerInput/ChatComposerInputMentionTrigger.tsx +1 -1
  56. package/templates/blocks/components/ChatComposerInput/ChatComposerInputMultipleTriggers.tsx +1 -1
  57. package/templates/blocks/components/ChatComposerInput/ChatComposerInputShowcase.tsx +1 -1
  58. package/templates/blocks/components/ChatComposerInput/ChatComposerInputSlashCommands.tsx +1 -1
  59. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenLiveRegion.doc.mjs +14 -0
  60. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenLiveRegion.tsx +41 -0
  61. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenShowcase.doc.mjs +13 -0
  62. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenShowcase.tsx +78 -0
  63. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenStructuralHeading.doc.mjs +14 -0
  64. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenStructuralHeading.tsx +38 -0
  65. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenSupplementaryContext.doc.mjs +14 -0
  66. package/templates/blocks/components/VisuallyHidden/VisuallyHiddenSupplementaryContext.tsx +47 -0
  67. package/templates/pages/ide/page.tsx +35 -41
  68. package/templates/pages/theme-showcase/page.tsx +7 -7
  69. package/templates/themes/neutral/neutralTheme.ts +63 -32
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @xds/cli
2
2
 
3
+ # 0.1.7
4
+
5
+ #### New Features
6
+
7
+ - Export the authoring factories from `@astryxdesign/core`: `createConfig` at `@astryxdesign/core/config` and `createIntegration`/`createPageTemplate`/`createBlockTemplate`/`createComponentDoc`/`createFunctionDoc`/`createDoc` at `@astryxdesign/core/authoring`. Authoring a config or integration no longer requires depending on the CLI. Existing `@astryxdesign/cli/*` imports keep working via re-export.
8
+ - Add the finalized doc-authoring API to `@astryxdesign/cli/doc`: `createComponentDoc`, `createFunctionDoc` (any function, including hooks), and `createDoc` (generic reference/topic docs). Each factory stamps a `type` discriminant and is validated at the load boundary against a matching per-kind schema. The legacy loose `export const docs = {...}` format keeps loading unchanged, and `.ts`-authored hook/function sources now derive their import path to a tree-shakeable subpath instead of the bare package root.
9
+ - New codemod for the Table `tableProps` deprecation: lifts object-literal `tableProps` keys into direct props on `<Table>`, keeps colliding or dynamic values in place with a TODO note. **Codemod:** `npx astryx upgrade --codemod migrate-table-tableprops-to-direct-props` (#3679)
10
+ - New docs topic `internationalization` covering how to localize astryx components, provide translation catalogs, override default strings, coexist with existing i18n libraries (react-intl, i18next, next-intl), swap languages at runtime, and validate coverage with the shipped pseudo locale. Run `npx astryx docs internationalization` or read it at https://astryx.atmeta.com/docs/internationalization.
11
+ - template: accept `.template.{ts,mjs,js}` as the canonical suffix for template-spec files, alongside the legacy `.doc.*` suffix. Template specs export `createBlockTemplate`/`createPageTemplate` — a scaffoldable template, not documentation — so they now get a descriptive name. Core, external-package, and integration discovery (`findShowcase`, `--blocks`, `astryx template <id>` scaffolding) all treat `Foo.template.ts` identically to a legacy `Foo.doc.mjs`; same-stem `.tsx` source resolves for either suffix, and `.template.ts` authoring is loaded via jiti. Additive only — no existing files are renamed.
12
+
13
+ #### Fixes
14
+
15
+ - Translated component docs no longer drop props
16
+ A `docsZh` / `docsDense` block that carried its own `props` array replaced the English component doc **wholesale** rather than overlaying it, so any prop the translation had not caught up with simply ceased to exist. `astryx component Button --zh` silently omitted `isInterruptible` and `isIconOnly`; ten components were affected, including `MobileNav`, `Popover` and `Stack` through the multi-component `components[]` shape.
17
+ - Anchor --dense / --zh doc overlays to their base sections (#2182)
18
+ The compressed and translated reference docs were merged into the base doc **by array position**, so an overlay whose sections were ordered differently — or which omitted one — grafted every title onto the wrong body.
19
+ - template: inline full demo-image URLs in the Avatar blocks and theme-showcase page so scaffolding strips them to a clean placeholder. Templates that stored only the CDN base in a `const` and appended the filename via interpolation (`` `${CDN}/File.png` ``) previously scaffolded a malformed `src` — the placeholder data URI with the filename glued onto the end — plus a dead `const CDN = 'data:…'`. (#4027)
20
+
21
+ #### Documentation
22
+
23
+ - Document the minimal `package.json#exports` recipe an integration needs so its block templates are importable by a bundler-resolution consumer and type-check under `moduleResolution: bundler`: `"./templates/*.tsx": "./templates/*.tsx"` plus an extensionful `import('@acme/widgets/templates/…/…Showcase.tsx')`. Adds `packages/cli/docs/integration-authoring.md` and a fixture test proving the recipe against the repo's own `tsc` and `esbuild`.
24
+
25
+ #### Contributors
26
+
27
+ Thanks to everyone who contributed to this release:
28
+
29
+ - @AKnassa
30
+ - @ejhammond
31
+ - @imdreamrunner
32
+ - @nynexman4464
33
+
34
+ ---
35
+
3
36
  # 0.1.6
4
37
 
5
38
  ---
package/README.md CHANGED
@@ -423,13 +423,103 @@ data: { checks, summary } }`) that AI agents and scripts can parse.
423
423
 
424
424
  ## Configuration
425
425
 
426
- The CLI reads from an optional `astryx.config.mjs` in your project root:
426
+ The CLI reads an optional `astryx.config.{ts,mjs,js}` from your project root
427
+ (a sibling of `package.json`). Every field is optional; with no config file the
428
+ CLI runs on defaults.
427
429
 
428
- ```javascript
429
- export default {
430
- templates: {
431
- get: async id => fetchTemplateFromAPI(id),
432
- },
430
+ ```typescript
431
+ import {createConfig} from '@astryxdesign/core/config';
432
+
433
+ export default createConfig({
434
+ integrations: ['@acme/astryx-widgets'],
433
435
  issuesUrl: 'https://github.com/your-org/your-repo/issues',
434
- };
436
+ });
437
+ ```
438
+
439
+ `createConfig` is a type-preserving helper: it returns its argument unchanged
440
+ and exists only to give the config file editor autocomplete and type-checking. A
441
+ plain `export default {}` object works identically. It's exported from
442
+ `@astryxdesign/core` (not the CLI) so your config file gets type feedback
443
+ without depending on the CLI; the same helper is re-exported from
444
+ `@astryxdesign/cli/config` for back-compat.
445
+
446
+ | Field | Type | Purpose |
447
+ | ----------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
448
+ | `integrations` | `string[]` | Integration package names to load (see [Integrations](#integrations)). |
449
+ | `issuesUrl` | `string` | Where "report an issue" links point for your project. Defaults to the core issue tracker. |
450
+ | `hooks.postCodemod` | `PostCodemodHook[]` | Commands to run after `astryx upgrade` applies codemods (e.g. reinstall, rebuild, reformat). |
451
+ | `experimental.xle.components` | `Record<string, XleComponent>` | Register app-local components so layout (XLE) expressions can reference them by name. Unstable. |
452
+
453
+ The config is validated against a strict schema when the CLI loads it, so an
454
+ unknown field is a hard error rather than a silent no-op. `astryx doctor`
455
+ reports whether the config loads cleanly.
456
+
457
+ ## Integrations
458
+
459
+ An **integration** is any npm package that contributes its own components,
460
+ templates, and upgrade codemods to Astryx. The CLI surfaces them next to core's,
461
+ through the same commands, so a consumer can `astryx component`,
462
+ `astryx template`, and `astryx upgrade` across core and every integration
463
+ uniformly. Use it to ship a first-party add-on, publish a third-party component
464
+ library, or share an internal design-system package across apps.
465
+
466
+ The system runs on two files, each with a small typed API:
467
+
468
+ | File | Written by | Role |
469
+ | -------------------------------- | ---------- | ----------------------------------------- |
470
+ | `astryx.config.{ts,mjs,js}` | Consumer | Lists which integration packages to load. |
471
+ | `astryx.integration.{ts,mjs,js}` | Author | Declares what a package contributes. |
472
+
473
+ The consumer side is the `integrations` field of [`astryx.config`](#configuration).
474
+ The author side is the integration manifest below.
475
+
476
+ ### The integration manifest
477
+
478
+ A package becomes an integration by exporting a manifest from
479
+ `astryx.integration.{ts,mjs,js}` at its root (a sibling of `package.json`). The
480
+ manifest points at where each kind of contribution lives; identity (name,
481
+ version) comes from `package.json`, not the manifest.
482
+
483
+ ```typescript
484
+ import {createIntegration} from '@astryxdesign/core/authoring';
485
+
486
+ export default createIntegration({
487
+ components: './components',
488
+ templates: './templates',
489
+ codemods: './codemods',
490
+ issuesUrl: 'https://github.com/acme/widgets/issues',
491
+ });
492
+ ```
493
+
494
+ | Field | Type | Purpose |
495
+ | ------------ | -------- | --------------------------------------------------------------------- |
496
+ | `components` | `string` | Directory holding the package's components and their `.doc.*` files. |
497
+ | `templates` | `string` | Directory holding the package's page/block templates. |
498
+ | `codemods` | `string` | Directory holding upgrade codemods run by `astryx upgrade`. |
499
+ | `issuesUrl` | `string` | Where "report an issue" links for this package's contributions point. |
500
+
501
+ Every field is optional; declare only the roots the package ships.
502
+ `createIntegration` is a type-preserving helper (editor autocomplete and
503
+ type-checking); it lives in `@astryxdesign/core/authoring` and is re-exported
504
+ from `@astryxdesign/cli/integration` for back-compat.
505
+
506
+ ### How it works
507
+
508
+ Every command loads the consumer's `astryx.config`, resolves each listed
509
+ integration's manifest from `node_modules`, and discovers its contributions.
510
+ Everything is validated against one strict schema at the load boundary, so the
511
+ CLI presents core and integration contributions through a single, uniform
512
+ surface.
513
+
514
+ Discovery is resilient: a broken or misconfigured integration is skipped with a
515
+ one-line warning on stderr instead of crashing the CLI, and it never corrupts a
516
+ `--json` envelope. To inspect problems, run
517
+ `astryx validate-integration <package>` for a detailed report on one package, or
518
+ `astryx doctor` for an overall health check.
519
+
520
+ For the full authoring walkthrough (component doc format, template packaging
521
+ and `exports` requirements, and codemod authoring), see the guide:
522
+
523
+ ```bash
524
+ astryx docs cli-integrations
435
525
  ```
@@ -0,0 +1,150 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../../core/src/docs-types').ReferenceDoc} */
4
+
5
+ export const docs = {
6
+ name: 'cli-integrations',
7
+ title: 'CLI Integrations',
8
+ category: 'guide',
9
+ description:
10
+ 'Author an npm package that contributes components, templates, and upgrade codemods to Astryx.',
11
+
12
+ sections: [
13
+ {
14
+ title: 'Overview',
15
+ category: 'guide',
16
+ content: [
17
+ {
18
+ type: 'prose',
19
+ text: 'An integration is an npm package that contributes components, templates, and/or upgrade codemods to a consumer\u2019s design-system workflow. Consumers install the package and add it to their `astryx.config`; from then on the integration\u2019s contributions show up alongside core\u2019s in the same CLI commands.',
20
+ },
21
+ {
22
+ type: 'prose',
23
+ text: 'The system runs on two files. The consumer writes `astryx.config.{ts,mjs,js}` at their project root to list which packages to load. The author writes `astryx.integration.{ts,mjs,js}` at the package root to declare what the package contributes. This page is the author\u2019s guide. For the consumer side, run `npx astryx docs getting-started`.',
24
+ },
25
+ {
26
+ type: 'prose',
27
+ text: 'On the consumer side, adding your package is one line:',
28
+ },
29
+ {
30
+ type: 'code',
31
+ lang: 'typescript',
32
+ code: "import {createConfig} from '@astryxdesign/core/config';\n\nexport default createConfig({\n integrations: ['@acme/astryx-widgets'],\n});",
33
+ },
34
+ {
35
+ type: 'prose',
36
+ text: 'Your components and templates then appear next to core\u2019s:',
37
+ },
38
+ {
39
+ type: 'code',
40
+ lang: 'bash',
41
+ code: 'astryx component --list --package @acme/astryx-widgets\nastryx component AcmeCarousel --props',
42
+ },
43
+ ],
44
+ },
45
+ {
46
+ title: 'The Integration File',
47
+ category: 'guide',
48
+ content: [
49
+ {
50
+ type: 'prose',
51
+ text: 'To register your package as an integration, add an `astryx.integration.{ts,mjs,js}` file as a sibling of your `package.json`. It tells the CLI where to find your components, templates, and codemods. Identity (name, version) comes from your `package.json`, not this file.',
52
+ },
53
+ {
54
+ type: 'code',
55
+ lang: 'typescript',
56
+ code: "// astryx.integration.ts\nimport {createIntegration} from '@astryxdesign/core/authoring';\n\nexport default createIntegration({\n components: './components',\n templates: './templates',\n codemods: './codemods',\n issuesUrl: 'https://github.com/acme/widgets/issues',\n});",
57
+ },
58
+ {
59
+ type: 'prose',
60
+ text: 'Every field is optional. Declare only the contribution roots your package ships. `createIntegration` is a type-preserving helper for editor autocomplete and type-checking. It lives in `@astryxdesign/core/authoring` and is also re-exported from `@astryxdesign/cli/integration` for back-compat.',
61
+ },
62
+ ],
63
+ },
64
+ {
65
+ title: 'Components',
66
+ category: 'guide',
67
+ content: [
68
+ {
69
+ type: 'prose',
70
+ text: 'Export your components from your library however you like, and consumers still import them from your package. For each component the CLI should document, ship a `.doc.{ts,mjs,js}` file with the same stem, for example `AcmeCarousel.tsx` alongside `AcmeCarousel.doc.ts`.',
71
+ },
72
+ {
73
+ type: 'code',
74
+ lang: 'typescript',
75
+ code: "// AcmeCarousel.doc.ts\nimport {createComponentDoc} from '@astryxdesign/core/authoring';\n\nexport default createComponentDoc({\n name: 'AcmeCarousel',\n description: 'A carousel that cycles through slides.',\n // props, usage, examples, ...\n});",
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ title: 'Templates',
81
+ category: 'guide',
82
+ content: [
83
+ {
84
+ type: 'prose',
85
+ text: 'Templates are usually not exported from the package directly. Instead, consumers browse them through the CLI and materialize them into their app. Define a template with `createPageTemplate` (full pages) or `createBlockTemplate` (smaller chunks) in a `.template.{ts,mjs,js}` file next to the source, for example `AcmeLandingPage.tsx` and `AcmeLandingPage.template.ts`.',
86
+ },
87
+ {
88
+ type: 'code',
89
+ lang: 'typescript',
90
+ code: "// AcmeLandingPage.template.ts\nimport {createPageTemplate} from '@astryxdesign/core/authoring';\n\nexport default createPageTemplate({\n // name, description, preview, ...\n});",
91
+ },
92
+ {
93
+ type: 'prose',
94
+ text: 'The CLI needs the template source at consume time, so make sure it is included in your published package. This is typically done via the `exports` key in `package.json`. It also lets the docsite render template previews in the future.',
95
+ },
96
+ {
97
+ type: 'code',
98
+ lang: 'jsonc',
99
+ code: '{\n "exports": {\n // ...\n "./templates/*.tsx": "./templates/*.tsx"\n }\n}',
100
+ },
101
+ {
102
+ type: 'prose',
103
+ text: 'To verify it resolves, try importing the template component with its `.tsx` extension. An extensionless specifier will not resolve under `moduleResolution: bundler`, and the extensionful export above is what lets this type-check without consumers enabling `allowImportingTsExtensions`.',
104
+ },
105
+ {
106
+ type: 'code',
107
+ lang: 'typescript',
108
+ code: "import('@acme/astryx-widgets/templates/AcmeLandingPage.tsx');",
109
+ },
110
+ ],
111
+ },
112
+ {
113
+ title: 'Codemods',
114
+ category: 'guide',
115
+ content: [
116
+ {
117
+ type: 'prose',
118
+ text: 'Ship codemods so `astryx upgrade` can migrate consumers across breaking changes in your package. Point the integration file\u2019s `codemods` field at your codemods root, and author each one with `createCodemod` (transforms source files) or `createConfigCodemod` (rewrites the consumer\u2019s `astryx.config`).',
119
+ },
120
+ {
121
+ type: 'code',
122
+ lang: 'typescript',
123
+ code: "// codemods/v2-rename-prop.ts\nimport {createCodemod} from '@astryxdesign/cli/codemod';\n\nexport default createCodemod({\n // version, description, transform, ...\n});",
124
+ },
125
+ {
126
+ type: 'prose',
127
+ text: 'The codemod helpers live in `@astryxdesign/cli/codemod`, not `@astryxdesign/core/authoring` like the doc, integration, and template helpers. Consumers can also run their own post-codemod hooks, such as a reinstall or rebuild, via `hooks.postCodemod` in their `astryx.config`.',
128
+ },
129
+ ],
130
+ },
131
+ {
132
+ title: 'How It Works',
133
+ category: 'guide',
134
+ content: [
135
+ {
136
+ type: 'prose',
137
+ text: 'Every CLI command loads the consumer\u2019s `astryx.config`, resolves each listed integration\u2019s manifest from `node_modules`, and discovers its contributions. Everything is validated against one strict schema at the load boundary. The `create*` helpers do not validate. They are identity functions whose value is their TypeScript surface, so validation happens when the CLI loads the file, not when you author it.',
138
+ },
139
+ {
140
+ type: 'prose',
141
+ text: 'Discovery is resilient. A broken or misconfigured integration is skipped with a single non-blocking warning on stderr instead of crashing the CLI, and it never corrupts a `--json` stdout envelope. Everyday commands keep working with the remaining valid contributions.',
142
+ },
143
+ {
144
+ type: 'prose',
145
+ text: 'To inspect problems, run `astryx validate-integration <package>` for a detailed report on one package, or `astryx doctor` for an overall health check of the setup.',
146
+ },
147
+ ],
148
+ },
149
+ ],
150
+ };
@@ -0,0 +1,243 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('../../core/src/docs-types').ReferenceDoc} */
4
+
5
+ export const docs = {
6
+ name: 'internationalization',
7
+ title: 'Internationalization',
8
+ category: 'guide',
9
+ description:
10
+ 'Localize astryx component strings, provide translation catalogs, override default text, coexist with your own i18n library, swap languages at runtime, and test translations with the pseudo locale.',
11
+
12
+ sections: [
13
+ {
14
+ title: 'Quick Start',
15
+ category: 'guide',
16
+ content: [
17
+ {
18
+ type: 'prose',
19
+ text: 'Internationalization ships with `@astryxdesign/core`. There is nothing to install. Wrap your app in `<InternationalizationProvider>` and set a `locale`, and astryx components pick up localized strings automatically.',
20
+ },
21
+ {
22
+ type: 'code',
23
+ lang: 'tsx',
24
+ label: 'Wrap your app',
25
+ code: `import {InternationalizationProvider} from '@astryxdesign/core';
26
+
27
+ function App() {
28
+ return (
29
+ <InternationalizationProvider locale="en">
30
+ <YourApp />
31
+ </InternationalizationProvider>
32
+ );
33
+ }`,
34
+ },
35
+ {
36
+ type: 'code',
37
+ lang: 'tsx',
38
+ label: 'Read strings inside a component',
39
+ code: `import {useTranslator} from '@astryxdesign/core';
40
+
41
+ function SaveButton() {
42
+ const t = useTranslator();
43
+ return <button>{t('@myapp.actions.save')}</button>;
44
+ }`,
45
+ },
46
+ {
47
+ type: 'prose',
48
+ text: 'The hook is available to consumer components too, but using it is entirely optional: many teams keep their app strings on their existing i18n library (react-intl, i18next, next-intl, LinguiJS) and only use `useTranslator` when reading astryx keys. If you do route your own strings through it, we recommend namespacing them (`@myapp.*` or your npm scope) to keep them separated from `@astryx.*`, but this is a convention, not a requirement; the resolver treats every key as an opaque string.',
49
+ },
50
+ {
51
+ type: 'prose',
52
+ text: "Astryx ships translations only for English today. First-party translations for other locales are on the roadmap; track https://github.com/facebook/astryx/issues/3641. In the meantime, if you want astryx UI translated into another locale, you can ship your own catalog through the `messages` prop (covered in the next section). If you're using `useTranslator` for your own strings, you'll want to ship your own catalog either way, since astryx only carries the fallback for `@astryx.*` keys, not the ones you author.",
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ title: 'Providing locale catalogs',
58
+ category: 'guide',
59
+ content: [
60
+ {
61
+ type: 'prose',
62
+ text: 'Astryx bundles only the English catalog today. To render in any other locale, provide a translation catalog through the `messages` prop and set `locale` accordingly. This matches how MUI, Ant Design, and AG Grid work: the consumer app supplies the catalogs it actually needs so unused translations stay out of the bundle.',
63
+ },
64
+ {
65
+ type: 'code',
66
+ lang: 'tsx',
67
+ label: 'Add French',
68
+ code: `import {InternationalizationProvider} from '@astryxdesign/core';
69
+ import fr from './locales/astryx/fr.json';
70
+
71
+ <InternationalizationProvider locale="fr" messages={{fr}}>
72
+ <App />
73
+ </InternationalizationProvider>;`,
74
+ },
75
+ {
76
+ type: 'prose',
77
+ text: "See `@astryxdesign/core/locales/en.json` for the full inventory of keys to translate. Copy it as the starting point: every key you translate replaces the English default; anything you omit falls back through the locale chain to English (e.g. `pt-BR` walks to `pt` then to shipped `en`), so a partial translation renders as a mix rather than empty text or raw key names.",
78
+ },
79
+ {
80
+ type: 'prose',
81
+ text: 'A community-maintained set of astryx translations is on the roadmap but not shipped yet. For now, consumer apps that ship in multiple languages own their astryx catalogs alongside their app catalogs. Contributions to a first-party set are welcome; track discussion at https://github.com/facebook/astryx/issues/3641.',
82
+ },
83
+ ],
84
+ },
85
+ {
86
+ title: "Overriding astryx's default text",
87
+ category: 'guide',
88
+ content: [
89
+ {
90
+ type: 'prose',
91
+ text: 'Use `overrides` to change individual strings without shipping a full catalog. Overrides are keyed by locale and merged on top of the built-in and user-supplied catalogs.',
92
+ },
93
+ {
94
+ type: 'code',
95
+ lang: 'tsx',
96
+ label: 'Change one string in English',
97
+ code: `<InternationalizationProvider
98
+ locale="en"
99
+ overrides={{en: {'@astryx.pagination.next': 'Next →'}}}
100
+ >
101
+ <App />
102
+ </InternationalizationProvider>`,
103
+ },
104
+ {
105
+ type: 'prose',
106
+ text: 'Overrides win over both bundled English and any `messages` catalog for the same key. Use them for brand voice tweaks or one-off wording changes.',
107
+ },
108
+ ],
109
+ },
110
+ {
111
+ title: 'Using astryx with your own i18n library',
112
+ category: 'guide',
113
+ content: [
114
+ {
115
+ type: 'prose',
116
+ text: "Astryx components render astryx strings through astryx's provider. Consumer components render consumer strings through whatever i18n library you already use: react-intl, i18next, next-intl, LinguiJS, and so on. The two systems coexist and read from the same source of truth for the active locale.",
117
+ },
118
+ {
119
+ type: 'code',
120
+ lang: 'tsx',
121
+ label: 'Astryx + react-intl side by side',
122
+ code: `import {InternationalizationProvider} from '@astryxdesign/core';
123
+ import {Selector} from '@astryxdesign/core/Selector';
124
+ import {Button} from '@astryxdesign/core/Button';
125
+ import {FormattedMessage, IntlProvider, useIntl} from 'react-intl';
126
+ import astryxFr from './locales/astryx/fr.json'; // astryx's UI, in French
127
+ import appFr from './locales/app/fr.json'; // your app strings, in French
128
+
129
+ function Pricing() {
130
+ // Consumer strings — resolved by react-intl.
131
+ const intl = useIntl();
132
+
133
+ return (
134
+ <section>
135
+ <h1><FormattedMessage id="pricing.heading" /></h1>
136
+
137
+ {/* Astryx Selector — trigger placeholder, search-box placeholder,
138
+ clear-button aria-label all resolved by
139
+ <InternationalizationProvider>. Options come from react-intl. */}
140
+ <Selector
141
+ label={intl.formatMessage({id: 'pricing.region.label'})}
142
+ options={[
143
+ {value: 'na', label: intl.formatMessage({id: 'pricing.region.na'})},
144
+ {value: 'eu', label: intl.formatMessage({id: 'pricing.region.eu'})},
145
+ ]}
146
+ hasSearch
147
+ hasClear
148
+ />
149
+
150
+ <Button label={intl.formatMessage({id: 'pricing.cta.subscribe'})} />
151
+ </section>
152
+ );
153
+ }
154
+
155
+ export default function App() {
156
+ return (
157
+ // Same locale, two providers reading their own catalogs.
158
+ <IntlProvider locale="fr" messages={appFr}>
159
+ <InternationalizationProvider locale="fr" messages={{fr: astryxFr}}>
160
+ <Pricing />
161
+ </InternationalizationProvider>
162
+ </IntlProvider>
163
+ );
164
+ }`,
165
+ },
166
+ {
167
+ type: 'prose',
168
+ text: 'Keep the two providers in sync on locale, and each library owns its own catalog. Astryx never sees your app strings, and your i18n library never sees astryx internals. Runtime locale swap works the same way: re-render both providers with a new `locale` prop and the whole tree updates live.',
169
+ },
170
+ {
171
+ type: 'prose',
172
+ text: "Single-catalog usage (where an external i18n runtime like react-intl or i18next resolves both your app strings AND astryx's strings through one provider) is on the roadmap via a `Translator` adapter. Track https://github.com/facebook/astryx/issues/4029. For now, run the two providers side by side as shown above.",
173
+ },
174
+ ],
175
+ },
176
+ {
177
+ title: 'Runtime language swap',
178
+ category: 'guide',
179
+ content: [
180
+ {
181
+ type: 'prose',
182
+ text: 'Re-render `<InternationalizationProvider>` with a new `locale` prop and every astryx string updates live. No reload, no separate API call.',
183
+ },
184
+ {
185
+ type: 'code',
186
+ lang: 'tsx',
187
+ label: 'Toggle between locales',
188
+ code: `const [locale, setLocale] = useState<'en' | 'fr'>('en');
189
+
190
+ <InternationalizationProvider locale={locale} messages={{fr}}>
191
+ <Button
192
+ label={locale === 'en' ? 'Français' : 'English'}
193
+ onClick={() => setLocale(l => (l === 'en' ? 'fr' : 'en'))}
194
+ />
195
+ <App />
196
+ </InternationalizationProvider>;`,
197
+ },
198
+ {
199
+ type: 'prose',
200
+ text: "Persisting the user's choice (localStorage, cookie, URL segment, account setting) is up to the consumer. Astryx reads whatever `locale` you pass in.",
201
+ },
202
+ ],
203
+ },
204
+ {
205
+ title: 'Testing your translations',
206
+ category: 'guide',
207
+ content: [
208
+ {
209
+ type: 'prose',
210
+ text: 'Astryx generates a `pseudo` locale that wraps every string in `⟦…⟧` and replaces letters with accented look-alikes. Switching to it in development instantly reveals any astryx string that isn\'t going through the translator, plus any layout that breaks under longer text.',
211
+ },
212
+ {
213
+ type: 'code',
214
+ lang: 'tsx',
215
+ label: 'Turn on pseudo-localization',
216
+ code: `import pseudo from '@astryxdesign/core/locales/pseudo.json';
217
+
218
+ <InternationalizationProvider locale="pseudo" messages={{pseudo}}>
219
+ <App />
220
+ </InternationalizationProvider>;`,
221
+ },
222
+ {
223
+ type: 'prose',
224
+ text: 'Any bare English text you still see on screen is a hardcoded string that needs to be routed through `useTranslator`.',
225
+ },
226
+ {
227
+ type: 'prose',
228
+ text: "Pseudoloc also has a subtle caveat worth knowing: the pseudo catalog is complete (astryx generates it from every shipped key), so a component using an astryx-shipped key will always render its pseudo version. Your handwritten translation catalogs, on the other hand, only cover the keys you translated; anything missing falls back to English. That means \"looks perfect in pseudo\" is not the same guarantee as \"looks perfect in French.\" Check each real locale by hand for coverage gaps.",
229
+ },
230
+ ],
231
+ },
232
+ {
233
+ title: 'For contributors',
234
+ category: 'guide',
235
+ content: [
236
+ {
237
+ type: 'prose',
238
+ text: "Astryx's own strings live in `packages/core/locales/en.json`. New user-facing strings must go through `useTranslator`; this is enforced by the `@astryx/no-hardcoded-i18n-string` ESLint rule. See the AI contribution guide for the alias-and-resolve pattern used when adding new keys.",
239
+ },
240
+ ],
241
+ },
242
+ ],
243
+ };
@@ -7,6 +7,7 @@ export const docsDense = {
7
7
  'frame-first app layout: shell choice, region budgets, cards vs rows',
8
8
  sections: [
9
9
  {
10
+ section: 'Frame First',
10
11
  title: 'Frame First',
11
12
  content: [
12
13
  {
@@ -26,6 +27,7 @@ export const docsDense = {
26
27
  ],
27
28
  },
28
29
  {
30
+ section: 'App Archetypes',
29
31
  title: 'App Archetypes',
30
32
  content: [
31
33
  {
@@ -40,6 +42,7 @@ export const docsDense = {
40
42
  ],
41
43
  },
42
44
  {
45
+ section: 'Cards vs Rows',
43
46
  title: 'Cards vs Rows',
44
47
  content: [
45
48
  {
@@ -67,6 +70,7 @@ export const docsDense = {
67
70
  ],
68
71
  },
69
72
  {
73
+ section: 'Panels and Inspectors',
70
74
  title: 'Panels and Inspectors',
71
75
  content: [
72
76
  {
@@ -77,6 +81,7 @@ export const docsDense = {
77
81
  ],
78
82
  },
79
83
  {
84
+ section: 'Responsive Contract',
80
85
  title: 'Responsive Contract',
81
86
  content: [
82
87
  {
@@ -5,10 +5,10 @@
5
5
  export const docsDense = {
6
6
  description: 'core design principles + rules for the design system',
7
7
  sections: [
8
- { title: 'Philosophy', content: [{ type: 'list', items: ['components over primitives', 'semantic tokens over hardcoded values', 'theme-agnostic code', 'open internals'] }] },
9
- { title: 'Rules', content: [{ type: 'list', items: ['use components', 'frame-first layout: shell + region budgets before content (astryx docs layout)', 'dense data = rows (Table, List/Item) not Cards; Card = widgets/galleries/settings groups', 'StyleX or Tailwind for styling', 'semantic tokens only', 'CSS vars for colors', 'controlled form inputs', 'useLinkComponent() for navigation'] }] },
10
- { title: 'Styling', content: [{ type: 'prose', text: 'xstyle prop for component overrides. StyleX or Tailwind for layout. See astryx docs styling.' }] },
11
- { title: 'Anti-Patterns', content: [{ type: 'list', items: ['no inline styles on raw elements', 'no hardcoded colors — use tokens or Tailwind semantic classes', 'no hardcoded spacing', 'no hardcoded <a> — use useLinkComponent()', 'no Card-wrapped list items — frame first, rows for dense data (astryx docs layout)', 'no decorative Badge — StatusDot/Token for status', 'read docs before inventing props'] }] },
12
- { title: 'Tokens', content: [{ type: 'prose', text: 'run npx astryx docs tokens for full reference' }] },
8
+ { section: 'Design Philosophy', title: 'Philosophy', content: [{ type: 'list', items: ['components over primitives', 'semantic tokens over hardcoded values', 'theme-agnostic code', 'open internals'] }] },
9
+ { section: 'Rules', title: 'Rules', content: [{ type: 'list', items: ['use components', 'frame-first layout: shell + region budgets before content (astryx docs layout)', 'dense data = rows (Table, List/Item) not Cards; Card = widgets/galleries/settings groups', 'StyleX or Tailwind for styling', 'semantic tokens only', 'CSS vars for colors', 'controlled form inputs', 'useLinkComponent() for navigation'] }] },
10
+ { section: 'Styling Approach', title: 'Styling', content: [{ type: 'prose', text: 'xstyle prop for component overrides. StyleX or Tailwind for layout. See astryx docs styling.' }] },
11
+ { section: 'Anti-Patterns', title: 'Anti-Patterns', content: [{ type: 'list', items: ['no inline styles on raw elements', 'no hardcoded colors — use tokens or Tailwind semantic classes', 'no hardcoded spacing', 'no hardcoded <a> — use useLinkComponent()', 'no Card-wrapped list items — frame first, rows for dense data (astryx docs layout)', 'no decorative Badge — StatusDot/Token for status', 'read docs before inventing props'] }] },
12
+ { section: 'Design Tokens', title: 'Tokens', content: [{ type: 'prose', text: 'run npx astryx docs tokens for full reference' }] },
13
13
  ],
14
14
  };
@@ -5,10 +5,10 @@
5
5
  export const docsZh = {
6
6
  description: 'XDS 核心设计原则和规则。',
7
7
  sections: [
8
- { title: '设计哲学', content: [{ type: 'list', items: ['组件优于原始元素 — 优先使用 XDS 组件', '语义化令牌优于硬编码值', '主题无关的代码 — 深色模式自动生效', '开放的内部机制 — 所有基础组件均可导出和组合'] }] },
9
- { title: '规则', content: [{ type: 'list', items: ['所有支持的场景都使用 XDS 组件', '布局采用框架优先:先选定外壳并规划区域尺寸,再编写内容(见 astryx docs layout)', '密集数据使用行(Table、List/Item)通栏渲染;Card 用于小部件、画廊和设置分组', '使用 StyleX 或 Tailwind 进行样式设置', '使用语义化令牌,不使用硬编码值', '使用 CSS 变量设置颜色,不使用十六进制值', '表单输入为受控组件(value + onChange)', '使用 useLinkComponent() 进行导航'] }] },
10
- { title: '样式方法', content: [{ type: 'prose', text: '组件覆盖使用 xstyle 属性。布局使用 StyleX 或 Tailwind。详见 astryx docs styling。' }] },
11
- { title: '反模式', content: [{ type: 'list', items: ['不要在原始元素上使用内联样式', '不要硬编码颜色 — 使用令牌或 Tailwind 语义类', '不要硬编码间距', '不要硬编码 <a> 元素 — 使用 useLinkComponent()', '不要把每个列表项都包在 Card 里 — 先定框架,密集数据用行渲染(见 astryx docs layout)', '不要把 Badge 当装饰 — 状态请使用 StatusDot 或 Token', '不要自创属性。先阅读组件文档'] }] },
12
- { title: '设计令牌', content: [{ type: 'prose', text: '运行 npx astryx docs tokens 查看完整参考' }] },
8
+ { section: 'Design Philosophy', title: '设计哲学', content: [{ type: 'list', items: ['组件优于原始元素 — 优先使用 XDS 组件', '语义化令牌优于硬编码值', '主题无关的代码 — 深色模式自动生效', '开放的内部机制 — 所有基础组件均可导出和组合'] }] },
9
+ { section: 'Rules', title: '规则', content: [{ type: 'list', items: ['所有支持的场景都使用 XDS 组件', '布局采用框架优先:先选定外壳并规划区域尺寸,再编写内容(见 astryx docs layout)', '密集数据使用行(Table、List/Item)通栏渲染;Card 用于小部件、画廊和设置分组', '使用 StyleX 或 Tailwind 进行样式设置', '使用语义化令牌,不使用硬编码值', '使用 CSS 变量设置颜色,不使用十六进制值', '表单输入为受控组件(value + onChange)', '使用 useLinkComponent() 进行导航'] }] },
10
+ { section: 'Styling Approach', title: '样式方法', content: [{ type: 'prose', text: '组件覆盖使用 xstyle 属性。布局使用 StyleX 或 Tailwind。详见 astryx docs styling。' }] },
11
+ { section: 'Anti-Patterns', title: '反模式', content: [{ type: 'list', items: ['不要在原始元素上使用内联样式', '不要硬编码颜色 — 使用令牌或 Tailwind 语义类', '不要硬编码间距', '不要硬编码 <a> 元素 — 使用 useLinkComponent()', '不要把每个列表项都包在 Card 里 — 先定框架,密集数据用行渲染(见 astryx docs layout)', '不要把 Badge 当装饰 — 状态请使用 StatusDot 或 Token', '不要自创属性。先阅读组件文档'] }] },
12
+ { section: 'Design Tokens', title: '设计令牌', content: [{ type: 'prose', text: '运行 npx astryx docs tokens 查看完整参考' }] },
13
13
  ],
14
14
  };