@astryxdesign/core 0.1.7 → 0.1.8-canary.c5fa19b

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 (190) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +30 -19
  3. package/dist/Avatar/Avatar.d.ts +10 -5
  4. package/dist/Avatar/Avatar.d.ts.map +1 -1
  5. package/dist/Avatar/Avatar.js +12 -8
  6. package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
  7. package/dist/Avatar/AvatarSizeContext.js +1 -1
  8. package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
  9. package/dist/Avatar/AvatarStatusDot.js +2 -2
  10. package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
  11. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  12. package/dist/AvatarGroup/AvatarGroup.js +2 -2
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  15. package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
  16. package/dist/Carousel/Carousel.d.ts +3 -2
  17. package/dist/Carousel/Carousel.d.ts.map +1 -1
  18. package/dist/Carousel/Carousel.js +32 -2
  19. package/dist/Chat/ChatMessage.d.ts +1 -1
  20. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  21. package/dist/Chat/ChatMessage.js +1 -1
  22. package/dist/Citation/Citation.d.ts.map +1 -1
  23. package/dist/Citation/Citation.js +10 -4
  24. package/dist/Collapsible/Collapsible.d.ts +11 -1
  25. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  26. package/dist/Collapsible/Collapsible.js +36 -4
  27. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  28. package/dist/ContextMenu/ContextMenu.js +4 -3
  29. package/dist/ContextMenu/index.d.ts +3 -0
  30. package/dist/ContextMenu/index.d.ts.map +1 -1
  31. package/dist/ContextMenu/index.js +7 -1
  32. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  33. package/dist/DropdownMenu/DropdownMenu.js +8 -4
  34. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +89 -0
  35. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
  36. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +170 -0
  37. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
  38. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  39. package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
  40. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +69 -0
  41. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
  42. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +70 -0
  43. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
  44. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
  45. package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
  46. package/dist/DropdownMenu/index.d.ts +5 -1
  47. package/dist/DropdownMenu/index.d.ts.map +1 -1
  48. package/dist/DropdownMenu/index.js +10 -1
  49. package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
  50. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
  51. package/dist/DropdownMenu/menuItemRoles.js +15 -0
  52. package/dist/Icon/Icon.d.ts +43 -13
  53. package/dist/Icon/Icon.d.ts.map +1 -1
  54. package/dist/Icon/Icon.js +62 -27
  55. package/dist/Outline/Outline.d.ts +69 -3
  56. package/dist/Outline/Outline.d.ts.map +1 -1
  57. package/dist/Outline/Outline.js +129 -26
  58. package/dist/Outline/useScrollSpy.d.ts +27 -7
  59. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  60. package/dist/Outline/useScrollSpy.js +202 -70
  61. package/dist/TabList/Tab.d.ts.map +1 -1
  62. package/dist/TabList/Tab.js +2 -2
  63. package/dist/TabList/TabList.d.ts.map +1 -1
  64. package/dist/TabList/TabList.js +2 -0
  65. package/dist/TabList/TabMenu.d.ts.map +1 -1
  66. package/dist/TabList/TabMenu.js +1 -1
  67. package/dist/Table/Table.d.ts +1 -1
  68. package/dist/Table/Table.js +1 -1
  69. package/dist/Table/index.d.ts +2 -0
  70. package/dist/Table/index.d.ts.map +1 -1
  71. package/dist/Table/index.js +1 -0
  72. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  73. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
  74. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  75. package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
  76. package/dist/Table/plugins/tree/index.d.ts +5 -0
  77. package/dist/Table/plugins/tree/index.d.ts.map +1 -0
  78. package/dist/Table/plugins/tree/index.js +4 -0
  79. package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
  80. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
  81. package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
  82. package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
  83. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
  84. package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
  85. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  86. package/dist/Table/useBaseTablePlugins.js +6 -3
  87. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  88. package/dist/Toolbar/Toolbar.js +8 -1
  89. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  90. package/dist/TreeList/TreeListItem.js +5 -14
  91. package/dist/astryx.css +24 -4
  92. package/dist/astryx.umd.js +54 -54
  93. package/dist/astryx.umd.js.map +4 -4
  94. package/dist/theme/Theme.js +1 -1
  95. package/dist/theme/defineTheme.d.ts +1 -1
  96. package/dist/theme/defineTheme.js +1 -1
  97. package/dist/theme/hct.d.ts +0 -1
  98. package/dist/theme/hct.d.ts.map +1 -1
  99. package/dist/theme/hct.js +0 -3
  100. package/docs.mjs +32 -461
  101. package/locales/en.json +200 -172
  102. package/locales/pseudo.json +21 -0
  103. package/package.json +3 -1
  104. package/scripts/postinstall.mjs +93 -0
  105. package/src/Avatar/Avatar.doc.mjs +6 -6
  106. package/src/Avatar/Avatar.tsx +16 -11
  107. package/src/Avatar/AvatarSizeContext.ts +1 -1
  108. package/src/Avatar/AvatarStatusDot.tsx +2 -2
  109. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  110. package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
  111. package/src/AvatarGroup/AvatarGroup.tsx +3 -3
  112. package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
  113. package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
  114. package/src/Carousel/Carousel.doc.mjs +1 -0
  115. package/src/Carousel/Carousel.test.tsx +96 -1
  116. package/src/Carousel/Carousel.tsx +29 -2
  117. package/src/Chat/Chat.doc.mjs +1 -1
  118. package/src/Chat/ChatMessage.tsx +4 -2
  119. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  120. package/src/Citation/Citation.test.tsx +51 -0
  121. package/src/Citation/Citation.tsx +8 -2
  122. package/src/Collapsible/Collapsible.doc.mjs +7 -0
  123. package/src/Collapsible/Collapsible.test.tsx +80 -0
  124. package/src/Collapsible/Collapsible.tsx +58 -5
  125. package/src/ContextMenu/ContextMenu.test.tsx +75 -1
  126. package/src/ContextMenu/ContextMenu.tsx +10 -5
  127. package/src/ContextMenu/index.ts +15 -0
  128. package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
  129. package/src/DropdownMenu/DropdownMenu.tsx +11 -6
  130. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
  131. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +237 -0
  132. package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
  133. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
  134. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
  135. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +119 -0
  136. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
  137. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
  138. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
  139. package/src/DropdownMenu/index.ts +23 -3
  140. package/src/DropdownMenu/menuItemRoles.ts +22 -0
  141. package/src/Field/Field.doc.mjs +1 -1
  142. package/src/Field/FieldLabel.doc.mjs +1 -1
  143. package/src/Icon/Icon.doc.mjs +12 -0
  144. package/src/Icon/Icon.test.tsx +142 -0
  145. package/src/Icon/Icon.tsx +98 -30
  146. package/src/Item/Item.doc.mjs +1 -1
  147. package/src/NumberInput/NumberInput.doc.mjs +2 -2
  148. package/src/Outline/Outline.doc.mjs +123 -3
  149. package/src/Outline/Outline.test.tsx +735 -0
  150. package/src/Outline/Outline.tsx +184 -33
  151. package/src/Outline/useScrollSpy.ts +248 -72
  152. package/src/Selector/SelectorOption.doc.mjs +1 -1
  153. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  154. package/src/Switch/Switch.doc.mjs +1 -1
  155. package/src/TabList/Tab.tsx +5 -1
  156. package/src/TabList/TabList.doc.mjs +3 -3
  157. package/src/TabList/TabList.test.tsx +66 -0
  158. package/src/TabList/TabList.tsx +8 -0
  159. package/src/TabList/TabMenu.tsx +4 -1
  160. package/src/Table/Table.doc.mjs +2 -0
  161. package/src/Table/Table.tsx +1 -1
  162. package/src/Table/index.ts +7 -0
  163. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
  164. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
  165. package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
  166. package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
  167. package/src/Table/plugins/tree/index.ts +12 -0
  168. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
  169. package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
  170. package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
  171. package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
  172. package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
  173. package/src/Table/useBaseTablePlugins.ts +6 -2
  174. package/src/Table/useTableTreeData.doc.mjs +75 -0
  175. package/src/Table/useTableTreeState.doc.mjs +98 -0
  176. package/src/TextArea/TextArea.doc.mjs +1 -1
  177. package/src/TextInput/TextInput.doc.mjs +1 -1
  178. package/src/Toolbar/Toolbar.tsx +10 -3
  179. package/src/TreeList/TreeList.test.tsx +32 -1
  180. package/src/TreeList/TreeListItem.tsx +19 -10
  181. package/src/docs-script.test.mjs +18 -13
  182. package/src/postinstall.test.mjs +71 -0
  183. package/src/theme/Theme.doc.mjs +4 -4
  184. package/src/theme/Theme.tsx +1 -1
  185. package/src/theme/defineTheme.ts +1 -1
  186. package/src/theme/hct.ts +0 -4
  187. package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
  188. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
  189. package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
  190. package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
@@ -115,7 +115,7 @@ function useThemeStyleInjection(theme) {
115
115
  // One-time perf hint per theme
116
116
  if (!warnedThemes.has(theme.name)) {
117
117
  warnedThemes.add(theme.name);
118
- console.warn(`[Astryx] Theme "${theme.name}" is using runtime style injection. ` + `For better performance, use the pre-built theme:\n\n` + ` import {${theme.name}Theme} from '@astryxdesign/theme-${theme.name}/built';\n` + ` import '@astryxdesign/theme-${theme.name}/theme.css';\n\n` + `For custom themes, run \`npx astryx theme build <file>\` to generate ` + `the built artifacts.`);
118
+ console.warn(`[Astryx] Theme "${theme.name}" is using runtime style injection. ` + `For better performance, use the pre-built theme:\n\n` + ` import {${theme.name}Theme} from '@astryxdesign/theme-${theme.name}/built';\n` + ` import '@astryxdesign/theme-${theme.name}/theme.css';\n\n` + `For custom themes, run \`npx @astryxdesign/cli theme build <file>\` to generate ` + `the built artifacts.`);
119
119
  }
120
120
  const {
121
121
  prose,
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Two distribution modes:
5
5
  * - Unbuilt: Theme generates CSS and injects a <style> tag at runtime
6
- * - Built: `npx astryx theme build` pre-compiles to a CSS file; Theme just
6
+ * - Built: `astryx theme build` pre-compiles to a CSS file; Theme just
7
7
  * sets the data-astryx-theme attribute
8
8
  *
9
9
  * Token values can be:
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Two distribution modes:
7
7
  * - Unbuilt: Theme generates CSS and injects a <style> tag at runtime
8
- * - Built: `npx astryx theme build` pre-compiles to a CSS file; Theme just
8
+ * - Built: `astryx theme build` pre-compiles to a CSS file; Theme just
9
9
  * sets the data-astryx-theme attribute
10
10
  *
11
11
  * Token values can be:
@@ -4,7 +4,6 @@ export interface HCT {
4
4
  tone: number;
5
5
  }
6
6
  export declare function toneToY(tone: number): number;
7
- export declare function yToTone(y: number): number;
8
7
  /**
9
8
  * Convert a hex color to HCT.
10
9
  * Hue: 0-360, Chroma: 0-~120, Tone: 0-100.
@@ -1 +1 @@
1
- {"version":3,"file":"hct.d.ts","sourceRoot":"","sources":["../../src/theme/hct.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAoFD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAkBD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAkBzC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CA8BzC;AAkDD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/D"}
1
+ {"version":3,"file":"hct.d.ts","sourceRoot":"","sources":["../../src/theme/hct.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAoFD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAkBD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAkBzC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CA8BzC;AAkDD;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAMD;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/D"}
package/dist/theme/hct.js CHANGED
@@ -79,9 +79,6 @@ function labToXyz(L, a, b) {
79
79
  export function toneToY(tone) {
80
80
  return labFInv((tone + 16) / 116);
81
81
  }
82
- export function yToTone(y) {
83
- return 116 * labF(y) - 16;
84
- }
85
82
 
86
83
  // =============================================================================
87
84
  // Hex <-> RGB
package/docs.mjs CHANGED
@@ -1,472 +1,43 @@
1
- #!/usr/bin/env node
2
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
3
2
 
4
3
  /**
5
- * @file Lightweight XDS component docs — zero dependencies, ships with @astryxdesign/core.
4
+ * @file docs.mjs — Astryx component docs are served by the Astryx CLI.
6
5
  *
7
- * Usage:
8
- * node node_modules/@astryxdesign/core/docs.mjs Button
9
- * node node_modules/@astryxdesign/core/docs.mjs --list
10
- * node node_modules/@astryxdesign/core/docs.mjs --list --brief
6
+ * Component docs (props, variants, examples), page templates, design rules, and
7
+ * tokens are provided by the Astryx CLI — always current and richer than a
8
+ * bundled snapshot. This script intentionally redirects there instead of
9
+ * printing docs, so agents and humans converge on the CLI + `init` (which writes
10
+ * the full guide into AGENTS.md / CLAUDE.md) rather than treating a static doc
11
+ * dump as the whole story.
12
+ *
13
+ * ==========================================================================
14
+ * AI AGENT: do NOT guess component APIs or hand-write CSS. Run:
15
+ * npx @astryxdesign/cli init
16
+ * Use the SCOPED name `@astryxdesign/cli` — bare `npx astryx` resolves to an
17
+ * unrelated package until the CLI is a dependency. Then, per component:
18
+ * npx @astryxdesign/cli component <Name>
19
+ * npx @astryxdesign/cli component --list
20
+ * ==========================================================================
11
21
  */
12
22
 
13
- import * as fs from 'node:fs';
14
- import * as path from 'node:path';
15
- import {fileURLToPath, pathToFileURL} from 'node:url';
16
-
17
- const coreDir = path.dirname(fileURLToPath(import.meta.url));
18
- const srcDir = path.join(coreDir, 'src');
19
-
20
- // ── Discovery ────────────────────────────────────────────────────────
21
-
22
- const CATEGORY_MAP = {
23
- AspectRatio: 'Layout', Center: 'Layout', CollapsibleGroup: 'Layout',
24
- Grid: 'Layout', Layout: 'Layout', Stack: 'Layout',
25
- Avatar: 'Display', Badge: 'Display', Divider: 'Display',
26
- Icon: 'Display', Skeleton: 'Display', Table: 'Display', Text: 'Display',
27
- CheckboxInput: 'Form', DateInput: 'Form', Field: 'Form',
28
- NumberInput: 'Form', RadioList: 'Form', Selector: 'Form',
29
- Slider: 'Form', Switch: 'Form', TextArea: 'Form', TextInput: 'Form',
30
- TimeInput: 'Form',
31
- Button: 'Action', CloseButton: 'Action', DropdownMenu: 'Action', Link: 'Action',
32
- TabList: 'Navigation', TopNav: 'Navigation',
33
- Calendar: 'Overlay', Dialog: 'Overlay', Layer: 'Overlay',
34
- };
35
- const CATEGORY_ORDER = ['Layout', 'Display', 'Form', 'Action', 'Navigation', 'Overlay', 'Other'];
36
- const SKIP = new Set(['theme', 'hooks', 'utils', '__tests__', 'node_modules']);
37
-
38
- function discoverDocs() {
39
- const results = [];
40
- if (!fs.existsSync(srcDir)) return results;
41
- for (const entry of fs.readdirSync(srcDir, {withFileTypes: true})) {
42
- if (!entry.isDirectory() || SKIP.has(entry.name)) continue;
43
- const docPath = findDocFile(entry.name);
44
- if (docPath) results.push({dir: entry.name, docPath});
45
- }
46
- return results;
47
- }
48
-
49
- function findDocFile(name) {
50
- const direct = path.join(srcDir, name, `${name}.doc.mjs`);
51
- if (fs.existsSync(direct)) return direct;
52
- const dir = path.join(srcDir, name);
53
- if (!fs.existsSync(dir)) return null;
54
- for (const sub of fs.readdirSync(dir, {withFileTypes: true})) {
55
- if (sub.isDirectory()) {
56
- const nested = path.join(dir, sub.name, `${sub.name}.doc.mjs`);
57
- if (fs.existsSync(nested)) return nested;
58
- }
59
- }
60
- return null;
61
- }
62
-
63
- function findDocByComponent(name) {
64
- let docPath = findDocFile(name);
65
- if (docPath) return docPath;
66
- for (const entry of fs.readdirSync(srcDir, {withFileTypes: true})) {
67
- if (!entry.isDirectory() || SKIP.has(entry.name)) continue;
68
- const nested = path.join(srcDir, entry.name, name, `${name}.doc.mjs`);
69
- if (fs.existsSync(nested)) return nested;
70
- }
71
- // Fuzzy: find XDS{Name}.tsx and use parent doc
72
- for (const entry of fs.readdirSync(srcDir, {withFileTypes: true})) {
73
- if (!entry.isDirectory() || SKIP.has(entry.name)) continue;
74
- const dir = path.join(srcDir, entry.name);
75
- if (hasXDSFile(dir, name)) {
76
- const doc = findDocInDir(dir);
77
- if (doc) return doc;
78
- }
79
- }
80
- return null;
81
- }
82
-
83
- function hasXDSFile(dir, name) {
84
- // Match the current `XDS{Name}.tsx` convention and the post-migration bare
85
- // `{Name}.tsx` form (XDS-prefix migration P2380608025, P4).
86
- const targets = [`XDS${name}.tsx`, `${name}.tsx`];
87
- try {
88
- for (const f of fs.readdirSync(dir, {withFileTypes: true})) {
89
- if (targets.includes(f.name)) return true;
90
- if (f.isDirectory() && hasXDSFile(path.join(dir, f.name), name)) return true;
91
- }
92
- } catch {}
93
- return false;
94
- }
95
-
96
- function findDocInDir(dir) {
97
- try {
98
- for (const f of fs.readdirSync(dir)) {
99
- if (f.endsWith('.doc.mjs')) return path.join(dir, f);
100
- }
101
- } catch {}
102
- return null;
103
- }
104
-
105
- // ── Loading ──────────────────────────────────────────────────────────
106
-
107
- async function loadDoc(docPath) {
108
- const mod = await import(pathToFileURL(docPath).href);
109
- return mod.docs;
110
- }
111
-
112
- // ── Example extraction ───────────────────────────────────────────────
113
-
114
- function extractExamples(docPath) {
115
- const dir = path.dirname(docPath);
116
- const examples = [];
117
- try {
118
- for (const file of fs.readdirSync(dir)) {
119
- if (!file.endsWith('.tsx') || file.includes('.test')) continue;
120
- const content = fs.readFileSync(path.join(dir, file), 'utf-8');
121
- const regex = /@example\s*\n\s*\*\s*```[\w]*\n([\s\S]*?)```/g;
122
- let match;
123
- while ((match = regex.exec(content)) !== null) {
124
- const code = match[1]
125
- .split('\n')
126
- .map(line => line.replace(/^\s*\*\s?/, ''))
127
- .join('\n')
128
- .trim();
129
- if (code.includes('declare module')) continue;
130
- if (code.includes('stylex.create') && !code.includes('XDS')) continue;
131
- if (code.length > 0) examples.push(code);
132
- }
133
- }
134
- } catch {}
135
- return examples;
136
- }
137
-
138
- // ── Formatting ───────────────────────────────────────────────────────
139
-
140
- function dataAttrForName(name) {
141
- return `data-${name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()}`;
142
- }
143
-
144
- function getTargetDataAttributes(target) {
145
- return [
146
- ...(target.visualProps || []).map(dataAttrForName),
147
- ...(target.states || []).map(dataAttrForName),
148
- ];
149
- }
150
-
151
- function formatPropsTable(props) {
152
- if (!props?.length) return '';
153
- const lines = ['| Prop | Type | Default | Description |', '|------|------|---------|-------------|'];
154
- for (const p of props) {
155
- const def = p.default ? `\`${p.default}\`` : '—';
156
- const req = p.required ? ' **(required)**' : '';
157
- lines.push(`| \`${p.name}\` | \`${p.type}\` | ${def} | ${p.description}${req} |`);
158
- }
159
- return lines.join('\n');
160
- }
161
-
162
- function formatFull(docs, docPath) {
163
- const s = [];
164
- s.push(`# ${docs.name}\n`);
165
- s.push((docs.usage?.description || '') + '\n');
166
-
167
- // Examples from @example JSDoc blocks in component source
168
- if (docPath) {
169
- const examples = extractExamples(docPath);
170
- if (examples.length) {
171
- s.push('## Example\n');
172
- s.push('```tsx');
173
- s.push(examples.join('\n\n'));
174
- s.push('```\n');
175
- }
176
- }
177
-
178
- if (docs.usage?.anatomy?.length) {
179
- s.push('## Anatomy\n');
180
- s.push('| Element | Required | Description |');
181
- s.push('|---------|----------|-------------|');
182
- for (const el of docs.usage.anatomy)
183
- s.push(`| ${el.name} | ${el.required ? 'Yes' : 'No'} | ${el.description} |`);
184
- s.push('');
185
- }
186
-
187
- if (docs.usage?.bestPractices?.length) {
188
- s.push('## Best Practices\n');
189
- for (const bp of docs.usage.bestPractices)
190
- s.push(`- ${bp.guidance ? '**Do:**' : "**Don't:**"} ${bp.description}`);
191
- s.push('');
192
- }
193
-
194
- if ('props' in docs) {
195
- s.push('## Props\n');
196
- s.push(formatPropsTable(docs.props) + '\n');
197
- }
198
-
199
- if ('components' in docs) {
200
- s.push('## Components\n');
201
- for (const comp of docs.components) {
202
- s.push(`### ${comp.name}\n`);
203
- s.push(comp.description + '\n');
204
- s.push(formatPropsTable(comp.props) + '\n');
205
- }
206
- }
207
-
208
- if (docs.theming?.targets?.length) {
209
- s.push('## Theming\n');
210
- s.push('| Component class | Preferred data attributes | Props | States |');
211
- s.push('|-----------------|---------------------------|-------|--------|');
212
- for (const t of docs.theming.targets) {
213
- const dataAttrs = getTargetDataAttributes(t);
214
- const attrs = dataAttrs.length ? dataAttrs.map(attr => `\`${attr}\``).join(', ') : '—';
215
- const variants = t.visualProps?.join(', ') || '—';
216
- const states = t.states?.join(', ') || '—';
217
- s.push(`| \`${t.className}\` | ${attrs} | ${variants} | ${states} |`);
218
- }
219
- s.push('');
220
- const publicVars = docs.theming.vars?.filter(v => !v.private && !v.derived) || [];
221
- if (publicVars.length) {
222
- s.push('| CSS Variable | Default | Description |');
223
- s.push('|-------------|---------|-------------|');
224
- for (const v of publicVars)
225
- s.push(`| \`${v.name}\` | \`${v.default}\` | ${v.description} |`);
226
- s.push('');
227
- }
228
- }
229
-
230
- return s.join('\n');
231
- }
232
-
233
- function formatBrief(docs) {
234
- const desc = docs.usage?.description || '';
235
- const short = desc.length > 80 ? desc.slice(0, 77) + '...' : desc;
236
- const props = docs.props || docs.components?.[0]?.props || [];
237
- const sig = props
238
- .filter(p => p.type.includes('|') && !p.type.includes('ReactNode'))
239
- .map(p => `${p.name}: ${p.type.replace(/['"]/g, '').split('|').map(v => v.trim()).join('|')}`)
240
- .join(', ');
241
- return sig ? `${docs.name}(${sig}) ${short}` : `${docs.name} ${short}`;
242
- }
243
-
244
- // ── Main ─────────────────────────────────────────────────────────────
245
-
246
-
247
- // ── Hook Support ─────────────────────────────────────────────────────
248
-
249
- const HOOK_CATEGORY_MAP = {
250
- useFocusTrap: 'Focus',
251
- useGridFocus: 'Focus',
252
- useListFocus: 'Focus',
253
- useMediaQuery: 'Media',
254
- useOverflow: 'Layout',
255
- useScrollOverflow: 'Layout',
256
- useScrollLock: 'Layout',
257
- useEntryAnimation: 'Animation',
258
- useStreamingText: 'Streaming',
259
- useImageMode: 'Media',
260
- useClickableContainer: 'Interaction',
261
- useInputContainer: 'Interaction',
262
- };
263
- const HOOK_CATEGORY_ORDER = ['Focus', 'Layout', 'Animation', 'Interaction', 'Media', 'Streaming', 'Other'];
264
-
265
- function discoverHookDocs() {
266
- const results = [];
267
- const hooksDir = path.join(srcDir, 'hooks');
268
- if (fs.existsSync(hooksDir)) {
269
- for (const file of fs.readdirSync(hooksDir)) {
270
- if (file.endsWith('.doc.mjs')) {
271
- results.push({name: file.replace('.doc.mjs', ''), docPath: path.join(hooksDir, file)});
272
- }
273
- }
274
- }
275
- // Also scan component directories for use*.doc.mjs
276
- if (fs.existsSync(srcDir)) {
277
- for (const entry of fs.readdirSync(srcDir, {withFileTypes: true})) {
278
- if (!entry.isDirectory() || SKIP.has(entry.name)) continue;
279
- const dir = path.join(srcDir, entry.name);
280
- try {
281
- for (const file of fs.readdirSync(dir)) {
282
- if (file.startsWith('use') && file.endsWith('.doc.mjs')) {
283
- results.push({name: file.replace('.doc.mjs', ''), docPath: path.join(dir, file)});
284
- }
285
- }
286
- } catch {}
287
- }
288
- }
289
- return results;
290
- }
291
-
292
- function findHookDocByName(hookName) {
293
- const normalized = hookName.replace(/^use/i, '');
294
- const hooksDir = path.join(srcDir, 'hooks');
295
-
296
- // Direct match in hooks/
297
- for (const variant of [hookName, `use${normalized}`, `useXDS${normalized}`]) {
298
- const p = path.join(hooksDir, `${variant}.doc.mjs`);
299
- if (fs.existsSync(p)) return p;
300
- }
301
-
302
- // Search component directories
303
- if (fs.existsSync(srcDir)) {
304
- for (const entry of fs.readdirSync(srcDir, {withFileTypes: true})) {
305
- if (!entry.isDirectory() || SKIP.has(entry.name)) continue;
306
- const dir = path.join(srcDir, entry.name);
307
- for (const variant of [hookName, `use${normalized}`, `useXDS${normalized}`]) {
308
- const p = path.join(dir, `${variant}.doc.mjs`);
309
- if (fs.existsSync(p)) return p;
310
- }
311
- }
312
- }
313
- return null;
314
- }
315
-
316
- function formatHookParamsTable(params) {
317
- if (!params?.length) return '';
318
- const lines = ['| Param | Type | Default | Description |', '|-------|------|---------|-------------|'];
319
- for (const p of params) {
320
- const def = p.default ? `\`${p.default}\`` : '\u2014';
321
- const req = p.required ? ' **(required)**' : '';
322
- lines.push(`| \`${p.name}\` | \`${p.type}\` | ${def} | ${p.description}${req} |`);
323
- }
324
- return lines.join('\n');
325
- }
326
-
327
- function formatHookReturnsTable(returns) {
328
- if (!returns?.length) return '';
329
- const lines = ['| Field | Type | Description |', '|-------|------|-------------|'];
330
- for (const r of returns) {
331
- lines.push(`| \`${r.name}\` | \`${r.type}\` | ${r.description} |`);
332
- }
333
- return lines.join('\n');
334
- }
335
-
336
- function formatHookFull(docs) {
337
- const s = [];
338
- s.push(`# ${docs.name}\n`);
339
- s.push((docs.usage?.description || '') + '\n');
340
-
341
- if (docs.params?.length) {
342
- s.push('## Parameters\n');
343
- s.push(formatHookParamsTable(docs.params) + '\n');
344
- }
345
-
346
- if (docs.returns?.length) {
347
- s.push('## Returns\n');
348
- s.push(formatHookReturnsTable(docs.returns) + '\n');
349
- }
350
-
351
- if (docs.usage?.bestPractices?.length) {
352
- s.push('## Best Practices\n');
353
- for (const bp of docs.usage.bestPractices)
354
- s.push(`- ${bp.guidance ? '**Do:**' : "**Don't:**"} ${bp.description}`);
355
- s.push('');
356
- }
357
-
358
- const refs = [];
359
- if (docs.relatedComponents?.length) refs.push(`Components: ${docs.relatedComponents.map(c => c).join(', ')}`);
360
- if (docs.relatedHooks?.length) refs.push(`Hooks: ${docs.relatedHooks.join(', ')}`);
361
- if (docs.importPath) refs.push(`Import: ${docs.importPath}`);
362
- if (refs.length) {
363
- s.push('## Related\n');
364
- for (const r of refs) s.push(r);
365
- s.push('');
366
- }
23
+ const BANNER = `
24
+ ==========================================================================
25
+ Astryx component docs are served by the Astryx CLI (accurate + current).
26
+ Do not guess component APIs or hand-write CSS — set up first (one time):
367
27
 
368
- return s.join('\n');
369
- }
28
+ npx @astryxdesign/cli init
370
29
 
371
- function formatHookBrief(docs) {
372
- const desc = docs.usage?.description || '';
373
- const short = desc.length > 80 ? desc.slice(0, 77) + '...' : desc;
374
- const params = (docs.params || []).filter(p => p.required).map(p => `${p.name}: ${p.type}`).join(', ');
375
- const ret = (docs.returns || []).map(r => r.type).join(' | ');
376
- const sig = params ? `${docs.name}(${params})` : docs.name;
377
- return ret ? `${sig}: ${ret} ${short}` : `${sig} ${short}`;
378
- }
30
+ (Use the scoped name @astryxdesign/cli. Bare \`npx astryx\` resolves to an
31
+ unrelated package until the CLI is installed as a dependency.)
379
32
 
380
- // ── Main ─────────────────────────────────────────────────────────────
33
+ init writes the full component guide, page templates, design rules & tokens
34
+ into your AGENTS.md / CLAUDE.md. Then, for any component:
381
35
 
382
- const args = process.argv.slice(2);
383
- const isList = args.includes('--list');
384
- const isBrief = args.includes('--brief');
385
- const isHooks = args.includes('--hooks');
386
- const name = args.find(a => !a.startsWith('--'));
36
+ npx @astryxdesign/cli component <Name> # full props, variants, examples
37
+ npx @astryxdesign/cli component --list # every component
38
+ npx @astryxdesign/cli build "<what you're building>" # starter kit
39
+ ==========================================================================
40
+ `;
387
41
 
388
- if (isHooks) {
389
- // Hook listing mode
390
- const hookDocs = discoverHookDocs();
391
- const grouped = {};
392
- for (const {name: hookName, docPath} of hookDocs) {
393
- const cat = HOOK_CATEGORY_MAP[hookName] || 'Other';
394
- if (!grouped[cat]) grouped[cat] = [];
395
- grouped[cat].push({name: hookName, docPath});
396
- }
397
- for (const cat of HOOK_CATEGORY_ORDER) {
398
- if (!grouped[cat]) continue;
399
- console.log(`\n${cat}:`);
400
- for (const {name: hookName, docPath} of grouped[cat]) {
401
- if (isBrief) {
402
- try {
403
- const d = await loadDoc(docPath);
404
- console.log(' ' + formatHookBrief(d));
405
- } catch {
406
- console.log(` ${hookName} (error loading docs)`);
407
- }
408
- } else {
409
- console.log(` ${hookName}`);
410
- }
411
- }
412
- }
413
- } else if (isList) {
414
- const docs = discoverDocs();
415
- const grouped = {};
416
- for (const {dir, docPath} of docs) {
417
- const cat = CATEGORY_MAP[dir] || 'Other';
418
- if (!grouped[cat]) grouped[cat] = [];
419
- grouped[cat].push({dir, docPath});
420
- }
421
- for (const cat of CATEGORY_ORDER) {
422
- if (!grouped[cat]) continue;
423
- console.log(`\n${cat}:`);
424
- for (const {dir, docPath} of grouped[cat]) {
425
- if (isBrief) {
426
- try {
427
- const d = await loadDoc(docPath);
428
- console.log(' ' + formatBrief(d));
429
- } catch {
430
- console.log(` ${dir} (error loading docs)`);
431
- }
432
- } else {
433
- console.log(` ${dir}`);
434
- }
435
- }
436
- }
437
- } else if (name) {
438
- // Try hook lookup first for names starting with 'use'
439
- if (name.startsWith('use') || name.startsWith('Use')) {
440
- const hookDocPath = findHookDocByName(name);
441
- if (hookDocPath) {
442
- const docs = await loadDoc(hookDocPath);
443
- console.log(formatHookFull(docs));
444
- } else {
445
- // Fall through to component lookup
446
- const docPath = findDocByComponent(name);
447
- if (!docPath) {
448
- console.error(`Hook or component "${name}" not found.`);
449
- console.error('Run with --list to see components, or --hooks to see hooks.');
450
- process.exit(1);
451
- }
452
- const docs = await loadDoc(docPath);
453
- console.log(formatFull(docs, docPath));
454
- }
455
- } else {
456
- const docPath = findDocByComponent(name);
457
- if (!docPath) {
458
- console.error(`Component "${name}" not found. Run with --list to see available components.`);
459
- process.exit(1);
460
- }
461
- const docs = await loadDoc(docPath);
462
- console.log(formatFull(docs, docPath));
463
- }
464
- } else {
465
- console.log('Usage:');
466
- console.log(' node docs.mjs <ComponentName> Show full component docs');
467
- console.log(' node docs.mjs <hookName> Show full hook docs');
468
- console.log(' node docs.mjs --list List all components');
469
- console.log(' node docs.mjs --list --brief List with brief descriptions');
470
- console.log(' node docs.mjs --hooks List all hooks');
471
- console.log(' node docs.mjs --hooks --brief List hooks with brief descriptions');
472
- }
42
+ console.log(BANNER);
43
+ process.exit(0);