@astryxdesign/cli 0.1.2-canary.d8e5285 → 0.1.2-canary.dd00d23

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 (120) hide show
  1. package/package.json +7 -7
  2. package/src/api/index.mjs +0 -1
  3. package/src/commands/build-theme.mjs +0 -117
  4. package/src/index.mjs +0 -1
  5. package/src/lib/error-codes.mjs +0 -8
  6. package/templates/blocks/components/DropdownMenu/DropdownMenuShowcase.tsx +5 -0
  7. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemShowcase.tsx +4 -1
  8. package/templates/blocks/components/Lightbox/LightboxShowcase.tsx +3 -4
  9. package/templates/blocks/components/MoreMenu/MoreMenuShowcase.tsx +5 -0
  10. package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.doc.mjs +14 -0
  11. package/templates/blocks/components/TreeListBranches/TreeListBranchesShowcase.tsx +64 -0
  12. package/templates/blocks/components/TreeListItem/TreeListItemShowcase.doc.mjs +14 -0
  13. package/templates/blocks/components/TreeListItem/TreeListItemShowcase.tsx +60 -0
  14. package/src/api/blog.mjs +0 -192
  15. package/src/api/blog.test.mjs +0 -144
  16. package/src/commands/blog.mjs +0 -60
  17. package/src/commands/build-theme.watch.test.mjs +0 -157
  18. package/src/lib/site.mjs +0 -20
  19. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.doc.mjs +0 -14
  20. package/templates/blocks/components/AvatarStatusDot/AvatarStatusDotVariants.tsx +0 -28
  21. package/templates/blocks/components/Blockquote/BlockquoteTestimonials.doc.mjs +0 -14
  22. package/templates/blocks/components/Blockquote/BlockquoteTestimonials.tsx +0 -34
  23. package/templates/blocks/components/Blockquote/BlockquoteWithCite.doc.mjs +0 -14
  24. package/templates/blocks/components/Blockquote/BlockquoteWithCite.tsx +0 -21
  25. package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.doc.mjs +0 -14
  26. package/templates/blocks/components/BreadcrumbItem/BreadcrumbItemBasic.tsx +0 -15
  27. package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.doc.mjs +0 -14
  28. package/templates/blocks/components/ButtonGroup/ButtonGroupBasic.tsx +0 -16
  29. package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.doc.mjs +0 -19
  30. package/templates/blocks/components/ChatDictationButton/ChatDictationButtonBasic.tsx +0 -34
  31. package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.doc.mjs +0 -14
  32. package/templates/blocks/components/CheckboxListItem/CheckboxListItemBasic.tsx +0 -30
  33. package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.doc.mjs +0 -14
  34. package/templates/blocks/components/CollapsibleGroup/CollapsibleGroupAccordion.tsx +0 -31
  35. package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.doc.mjs +0 -14
  36. package/templates/blocks/components/CommandPaletteEmpty/CommandPaletteEmptyBasic.tsx +0 -26
  37. package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.doc.mjs +0 -14
  38. package/templates/blocks/components/CommandPaletteFooter/CommandPaletteFooterBasic.tsx +0 -32
  39. package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.doc.mjs +0 -18
  40. package/templates/blocks/components/CommandPaletteGroup/CommandPaletteGroupBasic.tsx +0 -32
  41. package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.doc.mjs +0 -14
  42. package/templates/blocks/components/CommandPaletteItem/CommandPaletteItemBasic.tsx +0 -27
  43. package/templates/blocks/components/ContextMenu/ContextMenuBasic.doc.mjs +0 -14
  44. package/templates/blocks/components/ContextMenu/ContextMenuBasic.tsx +0 -32
  45. package/templates/blocks/components/DialogHeader/DialogHeaderBasic.doc.mjs +0 -14
  46. package/templates/blocks/components/DialogHeader/DialogHeaderBasic.tsx +0 -30
  47. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.doc.mjs +0 -14
  48. package/templates/blocks/components/DropdownMenuItem/DropdownMenuItemBasic.tsx +0 -27
  49. package/templates/blocks/components/FieldLabel/FieldLabelBasic.doc.mjs +0 -14
  50. package/templates/blocks/components/FieldLabel/FieldLabelBasic.tsx +0 -20
  51. package/templates/blocks/components/FieldStatus/FieldStatusBasic.doc.mjs +0 -14
  52. package/templates/blocks/components/FieldStatus/FieldStatusBasic.tsx +0 -23
  53. package/templates/blocks/components/FileInput/FileInputBasic.doc.mjs +0 -14
  54. package/templates/blocks/components/FileInput/FileInputBasic.tsx +0 -22
  55. package/templates/blocks/components/GridSpan/GridSpanColumns.doc.mjs +0 -14
  56. package/templates/blocks/components/GridSpan/GridSpanColumns.tsx +0 -38
  57. package/templates/blocks/components/HStack/HStackBasic.doc.mjs +0 -14
  58. package/templates/blocks/components/HStack/HStackBasic.tsx +0 -16
  59. package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.doc.mjs +0 -14
  60. package/templates/blocks/components/Hooks/useKeyboardHintHookUsage.tsx +0 -57
  61. package/templates/blocks/components/InputGroup/InputGroupBasic.doc.mjs +0 -14
  62. package/templates/blocks/components/InputGroup/InputGroupBasic.tsx +0 -27
  63. package/templates/blocks/components/LayoutContent/LayoutContentBasic.doc.mjs +0 -23
  64. package/templates/blocks/components/LayoutContent/LayoutContentBasic.tsx +0 -40
  65. package/templates/blocks/components/LayoutFooter/LayoutFooterActions.doc.mjs +0 -22
  66. package/templates/blocks/components/LayoutFooter/LayoutFooterActions.tsx +0 -41
  67. package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.doc.mjs +0 -23
  68. package/templates/blocks/components/LayoutHeader/LayoutHeaderWithActions.tsx +0 -40
  69. package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.doc.mjs +0 -22
  70. package/templates/blocks/components/LayoutPanel/LayoutPanelNavigation.tsx +0 -37
  71. package/templates/blocks/components/Lightbox/LightboxGallery.doc.mjs +0 -14
  72. package/templates/blocks/components/Lightbox/LightboxGallery.tsx +0 -53
  73. package/templates/blocks/components/Lightbox/LightboxVideo.doc.mjs +0 -14
  74. package/templates/blocks/components/Lightbox/LightboxVideo.tsx +0 -27
  75. package/templates/blocks/components/Lightbox/LightboxZoom.doc.mjs +0 -14
  76. package/templates/blocks/components/Lightbox/LightboxZoom.tsx +0 -33
  77. package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.doc.mjs +0 -14
  78. package/templates/blocks/components/MetadataListItem/MetadataListItemBasic.tsx +0 -19
  79. package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.doc.mjs +0 -23
  80. package/templates/blocks/components/MobileNavToggle/MobileNavToggleBasic.tsx +0 -42
  81. package/templates/blocks/components/NavIcon/NavIconBasic.doc.mjs +0 -14
  82. package/templates/blocks/components/NavIcon/NavIconBasic.tsx +0 -16
  83. package/templates/blocks/components/RadioListItem/RadioListItemBasic.doc.mjs +0 -14
  84. package/templates/blocks/components/RadioListItem/RadioListItemBasic.tsx +0 -30
  85. package/templates/blocks/components/Resizable/ResizableSidebar.doc.mjs +0 -25
  86. package/templates/blocks/components/Resizable/ResizableSidebar.tsx +0 -67
  87. package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.doc.mjs +0 -14
  88. package/templates/blocks/components/SegmentedControlItem/SegmentedControlItemBasic.tsx +0 -21
  89. package/templates/blocks/components/SelectorOption/SelectorOptionBasic.doc.mjs +0 -14
  90. package/templates/blocks/components/SelectorOption/SelectorOptionBasic.tsx +0 -39
  91. package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.doc.mjs +0 -14
  92. package/templates/blocks/components/SideNavCollapseButton/SideNavCollapseButtonBasic.tsx +0 -55
  93. package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.doc.mjs +0 -14
  94. package/templates/blocks/components/SideNavHeading/SideNavHeadingBasic.tsx +0 -38
  95. package/templates/blocks/components/SideNavItem/SideNavItemBasic.doc.mjs +0 -14
  96. package/templates/blocks/components/SideNavItem/SideNavItemBasic.tsx +0 -49
  97. package/templates/blocks/components/SideNavSection/SideNavSectionBasic.doc.mjs +0 -14
  98. package/templates/blocks/components/SideNavSection/SideNavSectionBasic.tsx +0 -71
  99. package/templates/blocks/components/StackItem/StackItemFill.doc.mjs +0 -14
  100. package/templates/blocks/components/StackItem/StackItemFill.tsx +0 -28
  101. package/templates/blocks/components/TabMenu/TabMenuBasic.doc.mjs +0 -14
  102. package/templates/blocks/components/TabMenu/TabMenuBasic.tsx +0 -23
  103. package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.doc.mjs +0 -14
  104. package/templates/blocks/components/ToggleButtonGroup/ToggleButtonGroupVertical.tsx +0 -47
  105. package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.doc.mjs +0 -14
  106. package/templates/blocks/components/TopNavHeading/TopNavHeadingBasic.tsx +0 -22
  107. package/templates/blocks/components/TopNavItem/TopNavItemBasic.doc.mjs +0 -14
  108. package/templates/blocks/components/TopNavItem/TopNavItemBasic.tsx +0 -21
  109. package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.doc.mjs +0 -20
  110. package/templates/blocks/components/TopNavMegaMenu/TopNavMegaMenuBasic.tsx +0 -46
  111. package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.doc.mjs +0 -14
  112. package/templates/blocks/components/TopNavMegaMenuFeaturedCard/TopNavMegaMenuFeaturedCardBasic.tsx +0 -16
  113. package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.doc.mjs +0 -14
  114. package/templates/blocks/components/TopNavMegaMenuItem/TopNavMegaMenuItemBasic.tsx +0 -26
  115. package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.doc.mjs +0 -14
  116. package/templates/blocks/components/TopNavMenu/TopNavMenuBasic.tsx +0 -43
  117. package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.doc.mjs +0 -14
  118. package/templates/blocks/components/TypeaheadItem/TypeaheadItemBasic.tsx +0 -43
  119. package/templates/blocks/components/VStack/VStackBasic.doc.mjs +0 -14
  120. package/templates/blocks/components/VStack/VStackBasic.tsx +0 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.2-canary.d8e5285",
3
+ "version": "0.1.2-canary.dd00d23",
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.d8e5285",
79
- "@astryxdesign/lab": "0.1.2-canary.d8e5285",
80
- "@astryxdesign/theme-neutral": "0.1.2-canary.d8e5285",
78
+ "@astryxdesign/core": "0.1.2-canary.dd00d23",
79
+ "@astryxdesign/lab": "0.1.2-canary.dd00d23",
80
+ "@astryxdesign/theme-neutral": "0.1.2-canary.dd00d23",
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.d8e5285",
96
- "@astryxdesign/lab": "0.1.2-canary.d8e5285",
97
- "@astryxdesign/theme-neutral": "0.1.2-canary.d8e5285",
95
+ "@astryxdesign/core": "0.1.2-canary.dd00d23",
96
+ "@astryxdesign/lab": "0.1.2-canary.dd00d23",
97
+ "@astryxdesign/theme-neutral": "0.1.2-canary.dd00d23",
98
98
  "gpt-tokenizer": "^2.0.0"
99
99
  },
100
100
  "scripts": {
package/src/api/index.mjs CHANGED
@@ -21,7 +21,6 @@
21
21
 
22
22
  export {component} from './component.mjs';
23
23
  export {docs} from './docs.mjs';
24
- export {blog} from './blog.mjs';
25
24
  export {discover} from './discover.mjs';
26
25
  export {template} from './template.mjs';
27
26
  export {themeAdd, listThemes} from './theme-add.mjs';
@@ -15,7 +15,6 @@
15
15
  import * as fs from 'node:fs';
16
16
  import * as path from 'node:path';
17
17
  import {pathToFileURL, fileURLToPath} from 'node:url';
18
- import {spawn} from 'node:child_process';
19
18
  import {createJiti} from 'jiti';
20
19
  import {getRunPrefix} from '../utils/package-manager.mjs';
21
20
  import {
@@ -598,104 +597,6 @@ function validatePrivateVars(themeDef) {
598
597
  return errors;
599
598
  }
600
599
 
601
- /**
602
- * Path to this CLI's real entry (bin/astryx.mjs), resolved from this module's
603
- * location (src/commands/build-theme.mjs → ../../bin/astryx.mjs). Used to
604
- * re-invoke `theme build` as a child process in watch mode.
605
- */
606
- function resolveCliBin() {
607
- const commandsDir = path.dirname(fileURLToPath(import.meta.url));
608
- return path.resolve(commandsDir, '../../bin/astryx.mjs');
609
- }
610
-
611
- /**
612
- * Run a single `theme build` as a child process, reusing the exact
613
- * single-build code path (and its error handling) rather than duplicating it.
614
- * Resolves with the child's exit code; never rejects.
615
- *
616
- * @param {string} file - The theme file argument, as the user passed it.
617
- * @param {object} options - Parsed command options (only `out` is forwarded).
618
- * @returns {Promise<number>}
619
- */
620
- function runThemeBuildOnceChild(file, options) {
621
- const cliBin = resolveCliBin();
622
- const args = [cliBin, 'theme', 'build', file];
623
- if (options.out) args.push('--out', options.out);
624
- return new Promise(resolve => {
625
- const child = spawn(process.execPath, args, {
626
- stdio: 'inherit',
627
- env: process.env,
628
- });
629
- child.on('close', code => resolve(code ?? 0));
630
- child.on('error', () => resolve(1));
631
- });
632
- }
633
-
634
- /**
635
- * Watch a theme file and rebuild on change. Runs an initial build, then
636
- * rebuilds (debounced) whenever the file changes, until interrupted with
637
- * Ctrl-C. Each rebuild runs in a child process so a build error (which the
638
- * single-build path reports via a hard exit) is contained and the watcher
639
- * keeps running.
640
- *
641
- * @param {string} file - The theme file argument, as the user passed it.
642
- * @param {string} filePath - Absolute path to the theme file.
643
- * @param {object} options - Parsed command options.
644
- * @returns {Promise<void>} Resolves when the watcher is stopped (Ctrl-C).
645
- */
646
- async function runThemeBuildWatch(file, filePath, options) {
647
- const rel = path.relative(process.cwd(), filePath);
648
-
649
- // Initial build.
650
- await runThemeBuildOnceChild(file, options);
651
-
652
- humanLog(`\n👀 Watching ${rel} for changes — press Ctrl-C to stop.`);
653
-
654
- let building = false;
655
- let queued = false;
656
- let debounce = null;
657
-
658
- const rebuild = async () => {
659
- if (building) {
660
- // Coalesce changes that land mid-build into a single follow-up run.
661
- queued = true;
662
- return;
663
- }
664
- building = true;
665
- humanLog(`\n♻️ Change detected — rebuilding ${rel}...`);
666
- await runThemeBuildOnceChild(file, options);
667
- building = false;
668
- humanLog(`\n👀 Watching ${rel} for changes — press Ctrl-C to stop.`);
669
- if (queued) {
670
- queued = false;
671
- rebuild();
672
- }
673
- };
674
-
675
- // Some editors replace the file (rename) rather than writing in place, which
676
- // can drop the watch. Watch the containing directory and filter to our file
677
- // so edits survive atomic-save/rename.
678
- const watchDir = path.dirname(filePath);
679
- const baseName = path.basename(filePath);
680
- const watcher = fs.watch(watchDir, (_eventType, changed) => {
681
- if (changed && changed !== baseName) return;
682
- clearTimeout(debounce);
683
- // Debounce: editors often emit several events per save.
684
- debounce = setTimeout(rebuild, 100);
685
- });
686
-
687
- await new Promise(resolve => {
688
- const stop = () => {
689
- clearTimeout(debounce);
690
- watcher.close();
691
- humanLog('\nStopped watching.');
692
- resolve();
693
- };
694
- process.once('SIGINT', stop);
695
- process.once('SIGTERM', stop);
696
- });
697
- }
698
-
699
600
  export function registerTheme(program) {
700
601
  const theme = program
701
602
  .command('theme')
@@ -720,10 +621,6 @@ export function registerTheme(program) {
720
621
  .command('build <file>')
721
622
  .description('Compile a defineTheme file to CSS + JS')
722
623
  .option('-o, --out <path>', 'Output CSS file path')
723
- .option(
724
- '-w, --watch',
725
- 'Rebuild automatically when the theme file changes (Ctrl-C to stop)',
726
- )
727
624
  .action(async (file, options) => {
728
625
  const filePath = path.resolve(process.cwd(), file);
729
626
  const json = program.opts().json || false;
@@ -733,20 +630,6 @@ export function registerTheme(program) {
733
630
  return;
734
631
  }
735
632
 
736
- // Watch mode: run an initial build, then rebuild on every change to the
737
- // theme file. Watch is a human-interactive, long-running mode — it is not
738
- // supported in --json (machine) mode, which expects a single envelope.
739
- if (options.watch) {
740
- if (json) {
741
- cliError('--watch is not supported with --json', {
742
- code: ERROR_CODES.ERR_THEME_INVALID,
743
- });
744
- return;
745
- }
746
- await runThemeBuildWatch(file, filePath, options);
747
- return;
748
- }
749
-
750
633
  if (!json) humanLog(`\nBuilding theme from ${path.relative(process.cwd(), filePath)}...`);
751
634
 
752
635
  // Extract theme definition
package/src/index.mjs CHANGED
@@ -245,7 +245,6 @@ const commands = [
245
245
  {name: 'init', path: './commands/init.mjs', register: 'registerInit'},
246
246
  {name: 'component', path: './commands/component/index.mjs', register: 'registerComponent'},
247
247
  {name: 'docs', path: './commands/docs.mjs', register: 'registerDocs'},
248
- {name: 'blog', path: './commands/blog.mjs', register: 'registerBlog'},
249
248
  {name: 'swizzle', path: './commands/swizzle.mjs', register: 'registerSwizzle'},
250
249
  // agent-docs folded into init — functions still importable from agent-docs.mjs
251
250
  {name: 'template', path: './commands/template.mjs', register: 'registerTemplate'},
@@ -75,8 +75,6 @@
75
75
  * | 'ERR_INVALID_VERSION'
76
76
  * | 'ERR_DEP_MISSING'
77
77
  * | 'ERR_GH_CLI'
78
- * | 'ERR_UNKNOWN_POST'
79
- * | 'ERR_FETCH_FAILED'
80
78
  * | 'ERR_LAYOUT_PARSE'
81
79
  * | 'ERR_LAYOUT_INVALID'
82
80
  * )} ErrorCode
@@ -183,12 +181,6 @@ export const ERROR_CODES = Object.freeze({
183
181
  /** GitHub CLI (`gh`) is not installed or not authenticated. */
184
182
  ERR_GH_CLI: 'ERR_GH_CLI',
185
183
 
186
- // ── Blog (read via the published RSS feed) ───────────────────────
187
- /** No blog post matched the requested slug in the feed. */
188
- ERR_UNKNOWN_POST: 'ERR_UNKNOWN_POST',
189
- /** A network fetch (RSS feed or post text) failed. */
190
- ERR_FETCH_FAILED: 'ERR_FETCH_FAILED',
191
-
192
184
  // ── Layout expressions (XLE/XLO) ─────────────────────────────────
193
185
  /** A layout expression failed to parse (syntax error, with line/col). */
194
186
  ERR_LAYOUT_PARSE: 'ERR_LAYOUT_PARSE',
@@ -2,11 +2,16 @@
2
2
 
3
3
  'use client';
4
4
 
5
+ import {useState} from 'react';
5
6
  import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
6
7
 
7
8
  export default function DropdownMenuShowcase() {
9
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
10
+
8
11
  return (
9
12
  <DropdownMenu
13
+ isMenuOpen={isMenuOpen}
14
+ onOpenChange={setIsMenuOpen}
10
15
  button={{label: 'Actions'}}
11
16
  items={[
12
17
  {label: 'Edit', onClick: () => {}},
@@ -65,7 +65,10 @@ const TrashIcon = (props: React.SVGProps<SVGSVGElement>) => (
65
65
 
66
66
  export default function DropdownMenuItemShowcase() {
67
67
  return (
68
- <DropdownMenu button={{label: 'Actions'}}>
68
+ <DropdownMenu
69
+ button={{label: 'Actions'}}
70
+ isMenuOpen
71
+ onOpenChange={() => {}}>
69
72
  <DropdownMenuItem
70
73
  icon={PencilIcon}
71
74
  label="Edit"
@@ -12,10 +12,9 @@ export default function LightboxShowcase() {
12
12
  isOpen={isOpen}
13
13
  onOpenChange={setIsOpen}
14
14
  media={{
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.',
15
+ src: 'https://picsum.photos/id/10/1200/800',
16
+ alt: 'Forest path',
17
+ caption: 'A winding path through the forest',
19
18
  }}
20
19
  />
21
20
  </>
@@ -2,11 +2,16 @@
2
2
 
3
3
  'use client';
4
4
 
5
+ import {useState} from 'react';
5
6
  import {MoreMenu} from '@astryxdesign/core/MoreMenu';
6
7
 
7
8
  export default function MoreMenuShowcase() {
9
+ const [isMenuOpen, setIsMenuOpen] = useState(false);
10
+
8
11
  return (
9
12
  <MoreMenu
13
+ isMenuOpen={isMenuOpen}
14
+ onOpenChange={setIsMenuOpen}
10
15
  items={[
11
16
  {label: 'Edit', onClick: () => {}},
12
17
  {label: 'Duplicate', onClick: () => {}},
@@ -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: 'TreeListBranches',
7
+ name: 'TreeListBranches',
8
+ displayName: 'Tree List Branches',
9
+ description: 'Tree with expandable branches at multiple nesting levels.',
10
+ isReady: true,
11
+ isShowcase: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['TreeList'],
14
+ };
@@ -0,0 +1,64 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {TreeList} from '@astryxdesign/core/TreeList';
6
+
7
+ const noop = () => {};
8
+
9
+ export default function TreeListBranchesShowcase() {
10
+ return (
11
+ <TreeList
12
+ items={[
13
+ {
14
+ id: 'design-system',
15
+ label: 'Design System',
16
+ isExpanded: true,
17
+ children: [
18
+ {
19
+ id: 'components',
20
+ label: 'Components',
21
+ isExpanded: true,
22
+ children: [
23
+ {
24
+ id: 'inputs',
25
+ label: 'Inputs',
26
+ children: [
27
+ {id: 'text-input', label: 'TextInput', onClick: noop},
28
+ {id: 'checkbox', label: 'Checkbox', onClick: noop},
29
+ {id: 'selector', label: 'Selector', onClick: noop},
30
+ ],
31
+ },
32
+ {
33
+ id: 'layout',
34
+ label: 'Layout',
35
+ children: [
36
+ {id: 'stack', label: 'Stack', onClick: noop},
37
+ {id: 'grid', label: 'Grid', onClick: noop},
38
+ ],
39
+ },
40
+ ],
41
+ },
42
+ {
43
+ id: 'tokens',
44
+ label: 'Tokens',
45
+ children: [
46
+ {id: 'colors', label: 'Colors', onClick: noop},
47
+ {id: 'spacing', label: 'Spacing', onClick: noop},
48
+ {id: 'typography', label: 'Typography', onClick: noop},
49
+ ],
50
+ },
51
+ ],
52
+ },
53
+ {
54
+ id: 'utilities',
55
+ label: 'Utilities',
56
+ children: [
57
+ {id: 'hooks', label: 'Hooks', onClick: noop},
58
+ {id: 'helpers', label: 'Helpers', onClick: noop},
59
+ ],
60
+ },
61
+ ]}
62
+ />
63
+ );
64
+ }
@@ -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: 'TreeListItem',
7
+ name: 'TreeListItem',
8
+ displayName: 'Tree List Item',
9
+ description: 'Tree items with descriptions, icons, badges, selection, disabled state, and nested expand/collapse. TreeList is data-driven: no composed children API.',
10
+ isReady: true,
11
+ isShowcase: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['TreeList', 'Badge', 'Icon', 'Layout', 'Text'],
14
+ };
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {TreeList} from '@astryxdesign/core/TreeList';
6
+ import {Badge} from '@astryxdesign/core/Badge';
7
+ import {Icon} from '@astryxdesign/core/Icon';
8
+
9
+ const noop = () => {};
10
+
11
+ export default function TreeListItemShowcase() {
12
+ return (
13
+ <TreeList
14
+ style={{width: 400}}
15
+ items={[
16
+ {
17
+ id: 'inbox',
18
+ label: 'Inbox',
19
+ description: '12 unread messages',
20
+ isExpanded: true,
21
+ startContent: <Icon icon="info" size="sm" />,
22
+ endContent: <Badge label="12" variant="info" />,
23
+ children: [
24
+ {
25
+ id: 'primary',
26
+ label: 'Primary',
27
+ isSelected: true,
28
+ startContent: <Icon icon="check" size="sm" />,
29
+ onClick: noop,
30
+ },
31
+ {
32
+ id: 'updates',
33
+ label: 'Updates',
34
+ endContent: <Badge label="4" variant="info" />,
35
+ onClick: noop,
36
+ },
37
+ ],
38
+ },
39
+ {
40
+ id: 'drafts',
41
+ label: 'Drafts',
42
+ description: '3 draft messages',
43
+ startContent: <Icon icon="clock" size="sm" />,
44
+ onClick: noop,
45
+ },
46
+ {
47
+ id: 'archive',
48
+ label: 'Archive',
49
+ onClick: noop,
50
+ },
51
+ {
52
+ id: 'spam',
53
+ label: 'Spam',
54
+ isDisabled: true,
55
+ startContent: <Icon icon="warning" size="sm" />,
56
+ },
57
+ ]}
58
+ />
59
+ );
60
+ }
package/src/api/blog.mjs DELETED
@@ -1,192 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file Programmatic API for the blog command.
5
- *
6
- * The blog is read the same way any feed reader reads it: over the published
7
- * RSS feed. Listing parses the feed; reading a post fetches the plaintext
8
- * (.txt) alternate the feed advertises for each item. Nothing here touches the
9
- * blog's source files — the CLI is just a consumer of the public feed, so the
10
- * blog's structure can change freely without touching the CLI.
11
- */
12
-
13
- import {AstryxError} from './error.mjs';
14
- import {ERROR_CODES} from '../lib/error-codes.mjs';
15
- import {SITE_URL, SITE_ORIGIN} from '../lib/site.mjs';
16
-
17
- /** Abort a feed/post fetch that hangs, and cap how much we'll read. */
18
- const FETCH_TIMEOUT_MS = 15000;
19
- const MAX_BYTES = 5 * 1024 * 1024; // 5 MB — a blog feed is never larger.
20
-
21
- const FEED_URL = new URL('/rss.xml', SITE_URL).toString();
22
-
23
- async function fetchText(url) {
24
- const controller = new AbortController();
25
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
26
- let res;
27
- try {
28
- res = await fetch(url, {
29
- signal: controller.signal,
30
- redirect: 'follow',
31
- });
32
- } catch (e) {
33
- clearTimeout(timer);
34
- const reason = e.name === 'AbortError' ? 'timed out' : e.message;
35
- throw new AstryxError(
36
- `Could not reach ${url}: ${reason}`,
37
- [],
38
- ERROR_CODES.ERR_FETCH_FAILED,
39
- );
40
- }
41
- clearTimeout(timer);
42
- if (!res.ok) {
43
- throw new AstryxError(
44
- `Request to ${url} failed with ${res.status}`,
45
- [],
46
- ERROR_CODES.ERR_FETCH_FAILED,
47
- );
48
- }
49
- const body = await res.text();
50
- if (body.length > MAX_BYTES) {
51
- throw new AstryxError(
52
- `Response from ${url} exceeded ${MAX_BYTES} bytes`,
53
- [],
54
- ERROR_CODES.ERR_FETCH_FAILED,
55
- );
56
- }
57
- return body;
58
- }
59
-
60
- /**
61
- * Defense in depth: a post's plaintext URL comes from feed content. Require it
62
- * to live on the canonical origin so even a tampered feed can't redirect the
63
- * CLI to another host.
64
- */
65
- function assertCanonicalOrigin(target) {
66
- let targetOrigin;
67
- try {
68
- targetOrigin = new URL(target).origin;
69
- } catch {
70
- throw new AstryxError(
71
- `Post has an invalid plaintext URL: "${target}"`,
72
- [],
73
- ERROR_CODES.ERR_FETCH_FAILED,
74
- );
75
- }
76
- if (targetOrigin !== SITE_ORIGIN) {
77
- throw new AstryxError(
78
- `Refusing to fetch post text from a non-canonical origin (${targetOrigin})`,
79
- [],
80
- ERROR_CODES.ERR_FETCH_FAILED,
81
- );
82
- }
83
- }
84
-
85
- function unescapeXml(value) {
86
- return value
87
- .replace(/&lt;/g, '<')
88
- .replace(/&gt;/g, '>')
89
- .replace(/&quot;/g, '"')
90
- .replace(/&apos;/g, "'")
91
- .replace(/&amp;/g, '&');
92
- }
93
-
94
- function tag(item, name) {
95
- const m = item.match(new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`));
96
- return m ? unescapeXml(m[1].trim()) : '';
97
- }
98
-
99
- function tagAll(item, name) {
100
- const out = [];
101
- const re = new RegExp(`<${name}[^>]*>([\\s\\S]*?)</${name}>`, 'g');
102
- let m;
103
- while ((m = re.exec(item))) out.push(unescapeXml(m[1].trim()));
104
- return out;
105
- }
106
-
107
- /** Extract the plaintext alternate href from an <item>. */
108
- function textHref(item) {
109
- // Match the atom:link alternate regardless of attribute order/quoting; then
110
- // confirm it's the text/plain alternate before trusting the href.
111
- const re = /<atom:link\b[^>]*?\/?>/g;
112
- let m;
113
- while ((m = re.exec(item))) {
114
- const el = m[0];
115
- if (/rel\s*=\s*["']alternate["']/.test(el) &&
116
- /type\s*=\s*["']text\/plain["']/.test(el)) {
117
- const href = el.match(/href\s*=\s*["']([^"']+)["']/);
118
- if (href) return unescapeXml(href[1]);
119
- }
120
- }
121
- return null;
122
- }
123
-
124
- /** Derive a slug from a post link (last path segment). */
125
- function slugFromLink(link) {
126
- try {
127
- const path = new URL(link).pathname.replace(/\/$/, '');
128
- return path.slice(path.lastIndexOf('/') + 1);
129
- } catch {
130
- return link;
131
- }
132
- }
133
-
134
- function parseFeed(xml) {
135
- const items = [];
136
- const re = /<item>([\s\S]*?)<\/item>/g;
137
- let m;
138
- while ((m = re.exec(xml))) {
139
- const raw = m[1];
140
- const link = tag(raw, 'link');
141
- items.push({
142
- slug: slugFromLink(link),
143
- title: tag(raw, 'title'),
144
- description: tag(raw, 'description'),
145
- date: tag(raw, 'pubDate'),
146
- type: tag(raw, 'category'),
147
- authors: tagAll(raw, 'author'),
148
- link,
149
- textUrl: textHref(raw),
150
- });
151
- }
152
- return items;
153
- }
154
-
155
- /**
156
- * List posts (from the feed), or read one post (via its .txt alternate).
157
- * Both envelopes carry `feedUrl` so a caller can hit the RSS feed directly.
158
- * The feed is always the canonical site — there is no user-supplied URL.
159
- *
160
- * @param {string} [slug]
161
- * @returns {Promise<{type: string, data: unknown}>}
162
- */
163
- export async function blog(slug) {
164
- const xml = await fetchText(FEED_URL);
165
- const posts = parseFeed(xml);
166
-
167
- if (!slug) {
168
- return {type: 'blog.list', data: {feedUrl: FEED_URL, posts}};
169
- }
170
-
171
- const normalized = slug.toLowerCase();
172
- const post = posts.find(p => p.slug.toLowerCase() === normalized);
173
- if (!post) {
174
- throw new AstryxError(
175
- `No blog post with slug "${slug}"`,
176
- posts.map(p => ({name: p.slug, reason: 'available post'})),
177
- ERROR_CODES.ERR_UNKNOWN_POST,
178
- );
179
- }
180
-
181
- if (!post.textUrl) {
182
- throw new AstryxError(
183
- `Post "${slug}" has no plaintext alternate in the feed`,
184
- [],
185
- ERROR_CODES.ERR_FETCH_FAILED,
186
- );
187
- }
188
-
189
- assertCanonicalOrigin(post.textUrl);
190
- const text = await fetchText(post.textUrl);
191
- return {type: 'blog.detail', data: {...post, feedUrl: FEED_URL, text}};
192
- }