@astryxdesign/cli 0.7.12-canary.ed58688 → 0.7.12-canary.f124c2f

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.
@@ -282,7 +282,7 @@ useOverlay useOverlay Overlay/useOverlay.doc.mjs Overlay/useOverlay.tsx
282
282
  useOwnScrollGesture useOwnScrollGesture DateInput/DateInput.doc.mjs DateInput/useOwnScrollGesture.ts
283
283
  usePager usePager hooks/useAnimatedLayout.doc.mjs hooks/usePager.ts
284
284
  usePointerDragScroll usePointerDragScroll DateInput/DateInput.doc.mjs DateInput/usePointerDragScroll.ts
285
- usePowerSearchConfig usePowerSearchConfig PowerSearch/PowerSearch.doc.mjs PowerSearch/usePowerSearchConfig.ts
285
+ usePowerSearchConfig usePowerSearchConfig PowerSearch/usePowerSearchConfig.doc.mjs PowerSearch/usePowerSearchConfig.ts
286
286
  usePowerSearchSource usePowerSearchSource PowerSearch/PowerSearch.doc.mjs PowerSearch/usePowerSearchSource.ts
287
287
  useRenamedProp useRenamedProp hooks/useAnimatedLayout.doc.mjs hooks/useRenamedProp.ts
288
288
  useResolvedRequired useResolvedRequired hooks/useAnimatedLayout.doc.mjs hooks/useResolvedRequired.ts
@@ -100,6 +100,10 @@
100
100
  "kind": "component",
101
101
  "name": "ChatUnreadDivider"
102
102
  },
103
+ {
104
+ "kind": "component",
105
+ "name": "ChatVirtualizer"
106
+ },
103
107
  {
104
108
  "kind": "component",
105
109
  "name": "CircleCheckIndicator"
@@ -90,7 +90,7 @@ export const docs = {
90
90
  {
91
91
  type: 'code',
92
92
  lang: 'bash',
93
- label: 'Install (add to an existing Tailwind v4 app)',
93
+ label: 'Install',
94
94
  code: '{@pm install @astryxdesign/core @stylexjs/stylex @astryxdesign/theme-neutral}',
95
95
  },
96
96
  {
@@ -113,7 +113,7 @@ export const docs = {
113
113
  },
114
114
  {
115
115
  type: 'prose',
116
- text: 'For Tailwind v3, see the layer workaround in {@link generic:migration-setup}.',
116
+ text: 'A project with Tailwind already set up needs a smaller change, which {@link generic:tailwind} shows. For Tailwind v3, see the layer workaround in {@link generic:migration-setup}.',
117
117
  },
118
118
  ],
119
119
  },
@@ -162,7 +162,7 @@ export const docs = {
162
162
  },
163
163
  {
164
164
  type: 'prose',
165
- text: '`{@command swizzle <Component>}` writes an `astryx-swizzle.json` marker into an output folder it owns (one the copy creates, an empty one, or one it marked before); every module under that folder then compiles as Astryx source, so keep copies in a folder of their own, such as the default `components/astryx`. For a folder that already holds other files it writes no marker and says how to add one by hand. With `@astryxdesign/build`\'s Babel, PostCSS and Vite integrations, or `withAstryx()` under webpack, a copy under that folder compiles into `astryx-base`, below `astryx-theme`, so theme component overrides reach it as they reach the package component. Under Turbopack, and with the stock StyleX plugins above, a copy\'s rules land in your own styles, above `astryx-theme`, and theme component overrides do not reach it.',
165
+ text: "`{@command swizzle <Component>}` writes an `astryx-swizzle.json` marker into an output folder it owns (one the copy creates, an empty one, or one it marked before); every module under that folder then compiles as Astryx source, so keep copies in a folder of their own, such as the default `components/astryx`. For a folder that already holds other files it writes no marker and says how to add one by hand. With `@astryxdesign/build`'s Babel, PostCSS and Vite integrations, or `withAstryx()` under webpack, a copy under that folder compiles into `astryx-base`, below `astryx-theme`, so theme component overrides reach it as they reach the package component. Under Turbopack, and with the stock StyleX plugins above, a copy's rules land in your own styles, above `astryx-theme`, and theme component overrides do not reach it.",
166
166
  },
167
167
  ],
168
168
  },
@@ -19,6 +19,8 @@ export const docs = {
19
19
  'tailwind-theme.css',
20
20
  'utility classes',
21
21
  'preflight',
22
+ 'existing Tailwind app',
23
+ 'cascade layer order',
22
24
  ],
23
25
 
24
26
  sections: [
@@ -69,7 +71,45 @@ import {themes, defaultThemeSlug} from './astryx-themes';`,
69
71
  },
70
72
  {
71
73
  type: 'prose',
72
- text: 'The Tailwind bridge is the concrete example of the general interop pattern: expose another library\'s semantic API, but point the values at system token variables.',
74
+ text: "The Tailwind bridge is the concrete example of the general interop pattern: expose another library's semantic API, but point the values at system token variables.",
75
+ },
76
+ ],
77
+ },
78
+ {
79
+ title: 'Add Astryx to an app that already uses Tailwind',
80
+ content: [
81
+ {
82
+ type: 'prose',
83
+ text: 'The recipe in the Tailwind section is a whole `globals.css` for a new app. An app that already uses Tailwind keeps its own file: its `@import "tailwindcss"`, its `@theme` mapping, and its `:root` variables stay as they are. Add one `@layer` line above the Tailwind import and the Astryx stylesheets below it.',
84
+ },
85
+ {
86
+ type: 'code',
87
+ lang: 'css',
88
+ label: 'globals.css (existing Tailwind app)',
89
+ code: `@layer reset, theme, base, astryx-base, astryx-theme, components, utilities;
90
+
91
+ @import "tailwindcss"; /* the app's own entry, unchanged */
92
+ @import "@astryxdesign/core/reset.css";
93
+ @import "@astryxdesign/core/astryx.css";
94
+ @import "@astryxdesign/theme-neutral/theme.css";
95
+
96
+ /* The app's own @theme and :root rules stay below, unchanged. */`,
97
+ },
98
+ {
99
+ type: 'prose',
100
+ text: "This recipe leaves out `tailwind-theme.css`. The bridge maps Tailwind theme names to system tokens, including Tailwind's own defaults such as `rounded-lg` and `shadow-md`, so in an existing app it changes what the app's own classes paint. Add it only when the app does not use the names it maps.",
101
+ },
102
+ {
103
+ type: 'prose',
104
+ text: "Astryx's theme defines its own CSS custom properties (color, spacing, radius, shadow, font, motion and more), and {@link namespace:tokens} has the full list. An app token with one of those names collides in one of two ways. An app `@theme` key takes Astryx's value, because `astryx.css` sets the tokens on `:root` above Tailwind's `theme` layer: with `@theme { --spacing-4: 1.5rem }`, the app's `gap-4` stays 16px. Marking the block `@theme inline` keeps the app's value in its utilities. An app `:root` variable replaces Astryx's value, so Astryx's own components change: `:root { --spacing-4: 1.5rem }` widens Astryx's spacing step 4 from 16px to 24px. Where the root Theme component sets a token again, such as `--color-warning`, the app's own rules inside it read Astryx's value instead. Rename any app token that shares a name.",
105
+ },
106
+ {
107
+ type: 'prose',
108
+ text: "Put the `@layer` line above the app's `@import \"tailwindcss\"`, never below it. A `@layer` statement orders only the layer names the page has not seen yet, and the Tailwind import names `theme`, `base`, `components`, and `utilities` as it loads. Below that import, the statement can only add `reset`, `astryx-base`, and `astryx-theme` after Tailwind's `utilities`. That inverts the cascade: the Astryx reset then outranks every utility class in the app, and, for example, removes the borders and padding the app's utilities give its controls. Nothing reports it: the build and the browser console stay clean, and the `@layer` line still looks right in the source, so only the rendered page shows the problem.",
109
+ },
110
+ {
111
+ type: 'prose',
112
+ text: 'The load-order rule from the Tailwind section still applies: `globals.css` must be the first stylesheet the page loads, so its `@layer` line names the layers first.',
73
113
  },
74
114
  ],
75
115
  },
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'block',
6
+ exampleFor: 'usePowerSearchConfig',
7
+ name: 'usePowerSearchConfig — Filtered Directory Table',
8
+ displayName: 'usePowerSearchConfig — Filtered Directory Table',
9
+ description:
10
+ 'A people directory with a PowerSearch bar over a Table: typed field definitions feed usePowerSearchConfig, returning the search config and an applyFilters helper that updates Table rows.',
11
+ isReady: true,
12
+ aspectRatio: 4 / 3,
13
+ componentsUsed: ['PowerSearch', 'Table', 'Layout'],
14
+ };
@@ -0,0 +1,83 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ import {useState} from 'react';
6
+ import {VStack} from '@astryxdesign/core/Layout';
7
+ import {
8
+ PowerSearch,
9
+ usePowerSearchConfig,
10
+ } from '@astryxdesign/core/PowerSearch';
11
+ import type {PowerSearchFilter} from '@astryxdesign/core/PowerSearch';
12
+ import {Table} from '@astryxdesign/core/Table';
13
+ import type {TableColumn} from '@astryxdesign/core/Table';
14
+
15
+ const statusValues = [
16
+ {value: 'active', label: 'Active'},
17
+ {value: 'on-leave', label: 'On Leave'},
18
+ {value: 'contractor', label: 'Contractor'},
19
+ ];
20
+
21
+ const fieldDefs = [
22
+ {key: 'name', type: 'string', label: 'Name'},
23
+ {key: 'status', type: 'enum', label: 'Status', enumValues: statusValues},
24
+ {key: 'startDate', type: 'date', label: 'Start date'},
25
+ ] as const;
26
+
27
+ interface Person extends Record<string, unknown> {
28
+ id: string;
29
+ name: string;
30
+ status: string;
31
+ startDate: number;
32
+ }
33
+
34
+ const people: Person[] = [
35
+ {id: '1', name: 'Alice Chen', status: 'active', startDate: 1672531200},
36
+ {id: '2', name: 'Bob Dylan', status: 'on-leave', startDate: 1680307200},
37
+ {id: '3', name: 'Charlie Kim', status: 'active', startDate: 1688169600},
38
+ {id: '4', name: 'Dana Scully', status: 'contractor', startDate: 1696118400},
39
+ {id: '5', name: 'Evan Wright', status: 'active', startDate: 1704067200},
40
+ ];
41
+
42
+ const columns: TableColumn<Person>[] = [
43
+ {key: 'name', header: 'Name'},
44
+ {
45
+ key: 'status',
46
+ header: 'Status',
47
+ renderCell: (person: Person) =>
48
+ statusValues.find(s => s.value === person.status)?.label ?? person.status,
49
+ },
50
+ {
51
+ key: 'startDate',
52
+ header: 'Start Date',
53
+ renderCell: (person: Person) =>
54
+ new Date(person.startDate * 1000).toLocaleDateString('en-US', {
55
+ timeZone: 'UTC',
56
+ year: 'numeric',
57
+ month: 'short',
58
+ day: 'numeric',
59
+ }),
60
+ },
61
+ ];
62
+
63
+ export default function UsePowerSearchConfigHookUsage() {
64
+ const [filters, setFilters] = useState<PowerSearchFilter[]>([]);
65
+ const {config, applyFilters} = usePowerSearchConfig(
66
+ fieldDefs,
67
+ 'PeopleDirectory',
68
+ );
69
+ const filteredPeople = applyFilters(filters, people);
70
+
71
+ return (
72
+ <VStack gap={4}>
73
+ <PowerSearch
74
+ config={config}
75
+ filters={filters}
76
+ onChange={newFilters => setFilters([...newFilters])}
77
+ placeholder="Filter directory by name, status, start date..."
78
+ resultCount={filteredPeople.length}
79
+ />
80
+ <Table data={filteredPeople} columns={columns} idKey="id" hasHover />
81
+ </VStack>
82
+ );
83
+ }
@@ -73,11 +73,22 @@ function extractTheme(mod) {
73
73
  return null;
74
74
  }
75
75
 
76
+ /**
77
+ * Print a theme warning as one inert line. The configured theme value and the
78
+ * generated record come from the checkout, so a newline, an escape sequence or
79
+ * a bidi override in them must not rewrite the terminal. Control and format
80
+ * characters print as U+FFFD.
81
+ * @param {string} message
82
+ */
83
+ function warnTheme(message) {
84
+ console.warn(message.replace(/[\p{Cc}\p{Cf}]/gu, '\uFFFD'));
85
+ }
86
+
76
87
  /** @param {unknown} mod @param {string} specifier */
77
88
  function resolvedThemeData(mod, specifier) {
78
89
  const theme = extractTheme(mod);
79
90
  if (!theme) {
80
- console.warn(
91
+ warnTheme(
81
92
  `⚠ theme: loaded "${specifier}" but could not find a theme object`,
82
93
  );
83
94
  return null;
@@ -100,7 +111,7 @@ export async function resolveTheme(cwd = process.cwd()) {
100
111
  const loaded = await resolveRecordedTheme(cwd);
101
112
  if (loaded.configured) {
102
113
  if (!loaded.module) {
103
- console.warn(
114
+ warnTheme(
104
115
  `⚠ theme: ${loaded.problem ?? `could not resolve the generated default theme (${loaded.specifier})`}`,
105
116
  );
106
117
  return null;
@@ -130,15 +141,13 @@ export async function resolveTheme(cwd = process.cwd()) {
130
141
  if (specifier.startsWith('.') || specifier.startsWith('/')) {
131
142
  mod = tryLoadLegacyModule(specifier, cwd);
132
143
  if (!mod) {
133
- console.warn(
134
- `⚠ theme: could not resolve file "${specifier}" from ${cwd}`,
135
- );
144
+ warnTheme(`⚠ theme: could not resolve file "${specifier}" from ${cwd}`);
136
145
  return null;
137
146
  }
138
147
  } else if (specifier.startsWith('@')) {
139
148
  mod = tryLoadLegacyModule(specifier, cwd);
140
149
  if (!mod) {
141
- console.warn(`⚠ theme: could not resolve package "${specifier}"`);
150
+ warnTheme(`⚠ theme: could not resolve package "${specifier}"`);
142
151
  return null;
143
152
  }
144
153
  } else {
@@ -147,7 +156,7 @@ export async function resolveTheme(cwd = process.cwd()) {
147
156
  tryLoadLegacyModule(conventional, cwd) ??
148
157
  tryLoadLegacyModule(specifier, cwd);
149
158
  if (!mod) {
150
- console.warn(
159
+ warnTheme(
151
160
  `⚠ theme: could not resolve "${specifier}" (tried ${conventional} and ${specifier})`,
152
161
  );
153
162
  return null;
@@ -17,7 +17,11 @@ export function importDocModule(file: string): Promise<any>;
17
17
  export function importTemplateModule(file: string): Promise<any>;
18
18
  /**
19
19
  * Import a module natively, by file URL: how the docs API, search, and the
20
- * unchecked component and hook readers have always imported docs.
20
+ * unchecked component and hook readers have always imported docs. A `.ts`
21
+ * file goes through jiti, as {@link importDocModule} imports it: Node cannot
22
+ * strip types from a file under node_modules, and below 22.18 it does not
23
+ * strip types at all, so a plain import of an installed or linked
24
+ * integration's `.doc.ts` fails.
21
25
  * @param {string} file
22
26
  * @returns {Promise<any>}
23
27
  */
@@ -25,7 +25,11 @@
25
25
  import {pathToFileURL} from 'node:url';
26
26
  import {createJiti} from 'jiti';
27
27
  import {importUrl} from '../fs/import-url.mjs';
28
- import {importUserModule} from '../fs/module-loader.mjs';
28
+ import {
29
+ importUserModule,
30
+ readFreshInTurn,
31
+ readInTurn,
32
+ } from '../fs/module-loader.mjs';
29
33
  import {fileStamp, recordContentRead} from '../fs/read-record.mjs';
30
34
 
31
35
  /** @type {ReturnType<typeof createJiti> | undefined} */
@@ -79,6 +83,28 @@ function freshUrl(file) {
79
83
  return url.href;
80
84
  }
81
85
 
86
+ /**
87
+ * Import `file` from `url`, in its turn with every other read of the file in
88
+ * this process (`readInTurn`, ../fs/module-loader.mjs).
89
+ * @param {string} file
90
+ * @param {string} url
91
+ * @returns {Promise<any>}
92
+ */
93
+ function importInTurn(file, url) {
94
+ return readInTurn(file, () => importUrl(url));
95
+ }
96
+
97
+ /**
98
+ * Import `file` again, for a change, as a new module. Node chooses its format
99
+ * as for the first import. The read takes its turn without the file's
100
+ * CommonJS cache entry (`readFreshInTurn`, ../fs/module-loader.mjs).
101
+ * @param {string} file
102
+ * @returns {Promise<any>}
103
+ */
104
+ function importFresh(file) {
105
+ return readFreshInTurn(file, () => importUrl(freshUrl(file)));
106
+ }
107
+
82
108
  /**
83
109
  * Import a component, hook, topic, or self-doc module: `.ts` through jiti,
84
110
  * anything else natively.
@@ -106,28 +132,35 @@ export function importTemplateModule(file) {
106
132
  jsxJiti ??= createJiti(import.meta.url, {jsx: true});
107
133
  return jsxJiti.import(first);
108
134
  }
109
- return importUrl(`file://${first}`);
135
+ return importInTurn(first, `file://${first}`);
110
136
  },
111
137
  changed =>
112
138
  changed.endsWith('.ts')
113
139
  ? createJiti(import.meta.url, {jsx: true, moduleCache: false}).import(
114
140
  changed,
115
141
  )
116
- : importUrl(freshUrl(changed)),
142
+ : importFresh(changed),
117
143
  );
118
144
  }
119
145
 
120
146
  /**
121
147
  * Import a module natively, by file URL: how the docs API, search, and the
122
- * unchecked component and hook readers have always imported docs.
148
+ * unchecked component and hook readers have always imported docs. A `.ts`
149
+ * file goes through jiti, as {@link importDocModule} imports it: Node cannot
150
+ * strip types from a file under node_modules, and below 22.18 it does not
151
+ * strip types at all, so a plain import of an installed or linked
152
+ * integration's `.doc.ts` fails.
123
153
  * @param {string} file
124
154
  * @returns {Promise<any>}
125
155
  */
126
156
  export function importNativeModule(file) {
157
+ if (file.endsWith('.ts')) {
158
+ return importDocModule(file);
159
+ }
127
160
  return importCurrent(
128
161
  file,
129
162
  'native',
130
- first => importUrl(pathToFileURL(first).href),
131
- changed => importUrl(freshUrl(changed)),
163
+ first => importInTurn(first, pathToFileURL(first).href),
164
+ changed => importFresh(changed),
132
165
  );
133
166
  }
@@ -40,6 +40,16 @@ export function importThemeArtifact(specifier: string, cwd: string): Promise<Rec
40
40
  * @returns {Promise<T>}
41
41
  */
42
42
  export function readInTurn<T>(file: string, read: () => Promise<T>): Promise<T>;
43
+ /**
44
+ * Run `read`, a fresh import of `file`, in the file's turn (`readInTurn`),
45
+ * once the file is gone from Node's CommonJS cache. A new URL names a new ES
46
+ * module only, so a CommonJS module also needs its cache entry gone.
47
+ * @template T
48
+ * @param {string} file absolute path
49
+ * @param {() => Promise<T>} read
50
+ * @returns {Promise<T>}
51
+ */
52
+ export function readFreshInTurn<T>(file: string, read: () => Promise<T>): Promise<T>;
43
53
  /**
44
54
  * Import a user-authored module, take its default export, and validate it
45
55
  * through an authoring parser. This is the single load/validation boundary for
@@ -236,10 +236,7 @@ async function importModule(file, fresh) {
236
236
  // "type", the extension, or the source's syntax. The query names a new ES
237
237
  // module. A CommonJS module also needs its cache entry gone.
238
238
  url.searchParams.set('astryx-fresh', String(++freshImportNonce));
239
- return await readInTurn(file, () => {
240
- delete requireFromHere.cache[cacheKey(file)];
241
- return import(url.href);
242
- });
239
+ return await readFreshInTurn(file, () => import(url.href));
243
240
  }
244
241
 
245
242
  /**
@@ -287,6 +284,22 @@ export function readInTurn(file, read) {
287
284
  return result;
288
285
  }
289
286
 
287
+ /**
288
+ * Run `read`, a fresh import of `file`, in the file's turn (`readInTurn`),
289
+ * once the file is gone from Node's CommonJS cache. A new URL names a new ES
290
+ * module only, so a CommonJS module also needs its cache entry gone.
291
+ * @template T
292
+ * @param {string} file absolute path
293
+ * @param {() => Promise<T>} read
294
+ * @returns {Promise<T>}
295
+ */
296
+ export function readFreshInTurn(file, read) {
297
+ return readInTurn(file, () => {
298
+ delete requireFromHere.cache[cacheKey(file)];
299
+ return read();
300
+ });
301
+ }
302
+
290
303
 
291
304
  /**
292
305
  * Import a user-authored module, take its default export, and validate it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.7.12-canary.ed58688",
3
+ "version": "0.7.12-canary.f124c2f",
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",
@@ -125,7 +125,7 @@
125
125
  "zod": "^4.4.3"
126
126
  },
127
127
  "peerDependencies": {
128
- "@astryxdesign/core": "0.7.12-canary.ed58688"
128
+ "@astryxdesign/core": "0.7.12-canary.f124c2f"
129
129
  },
130
130
  "peerDependenciesMeta": {
131
131
  "@astryxdesign/core": {
@@ -133,10 +133,10 @@
133
133
  }
134
134
  },
135
135
  "devDependencies": {
136
- "@astryxdesign/charts": "0.7.12-canary.ed58688",
137
- "@astryxdesign/core": "0.7.12-canary.ed58688",
138
- "@astryxdesign/lab": "0.7.12-canary.ed58688",
139
- "@astryxdesign/theme-neutral": "0.7.12-canary.ed58688",
136
+ "@astryxdesign/charts": "0.7.12-canary.f124c2f",
137
+ "@astryxdesign/core": "0.7.12-canary.f124c2f",
138
+ "@astryxdesign/lab": "0.7.12-canary.f124c2f",
139
+ "@astryxdesign/theme-neutral": "0.7.12-canary.f124c2f",
140
140
  "@heroicons/react": "^2.2.0",
141
141
  "@stylexjs/stylex": "^0.19.0",
142
142
  "@types/babel__core": "^7.20.5",