@astryxdesign/cli 0.7.12-canary.f543ec7 → 0.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/api/build/blocks/blocks.d.mts +41 -0
- package/api/build/blocks/blocks.mjs +118 -68
- package/api/component/__fixtures__/resolutions/modules.txt +3 -3
- package/api/docs/_adapter.d.mts +22 -0
- package/api/docs/_adapter.mjs +81 -3
- package/api/docs/docs.doc.mjs +5 -5
- package/api/docs/docs.type.d.mts +21 -0
- package/api/docs/docs.type.mjs +11 -0
- package/api/docs/list/list.d.mts +5 -1
- package/api/docs/list/list.mjs +72 -6
- package/api/docs/node/node.mjs +2 -40
- package/api/doctor/doctor.d.mts +4 -4
- package/api/doctor/doctor.mjs +248 -78
- package/api/icon/_authoring.mjs +8 -1
- package/api/icon/add/add.mjs +45 -5
- package/api/icon/icon.type.d.mts +24 -0
- package/api/icon/icon.type.mjs +6 -0
- package/api/icon/iconAdd.doc.mjs +1 -1
- package/api/icon/map/map.mjs +11 -4
- package/api/index.d.mts +1 -0
- package/api/index.mjs +1 -0
- package/api/integration/add-helpers.d.mts +5 -1
- package/api/integration/add-helpers.mjs +1 -1
- package/api/render/_adapter.mjs +9 -0
- package/api/render/render.doc.mjs +3 -3
- package/api/render/render.type.d.mts +1 -1
- package/api/render/render.type.mjs +1 -1
- package/api/search/ORDERING.md +3 -0
- package/api/search/_adapter.d.mts +32 -3
- package/api/search/_adapter.mjs +50 -22
- package/api/search/_fit-order.d.mts +47 -0
- package/api/search/_fit-order.mjs +257 -0
- package/api/search/_main-searcher.mjs +15 -5
- package/api/search/exportSearchIndex.doc.d.mts +10 -0
- package/api/search/exportSearchIndex.doc.mjs +58 -0
- package/api/search/rankSearchIndex.doc.d.mts +10 -0
- package/api/search/rankSearchIndex.doc.mjs +79 -0
- package/api/search/search-index.d.mts +44 -0
- package/api/search/search-index.mjs +270 -0
- package/api/search/search.type.d.mts +28 -0
- package/api/search/search.type.mjs +17 -0
- package/api/swizzle/_moduleGraph.d.mts +7 -0
- package/api/swizzle/_moduleGraph.mjs +191 -83
- package/api/swizzle/copy/copy.mjs +28 -6
- package/api/template/skeleton/skeleton.mjs +1 -1
- package/api/template/template.mjs +1 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/index.mjs +1 -1
- package/assets/discover/official-packages.json +16 -0
- package/assets/docs/styling-overview.doc.mjs +3 -3
- package/assets/docs/tree/icon-sets.doc.mjs +4 -4
- package/assets/docs/tree/render-pages.doc.mjs +3 -3
- package/assets/docs/tree/tailwind.doc.mjs +41 -1
- package/assets/docs/tree/type-scale.doc.mjs +19 -2
- package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
- package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +41 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
- package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
- package/assets/templates/pages/table-filter/page.tsx +15 -23
- package/assets/templates/pages/table-grouped/page.tsx +30 -30
- package/assets/templates/pages/table-inbox/page.tsx +3 -6
- package/assets/templates/themes/butter/butterTheme.ts +1 -1
- package/assets/templates/themes/gothic/gothicTheme.ts +16 -0
- package/assets/templates/themes/matcha/matchaTheme.ts +1 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +6 -0
- package/assets/templates/themes/y2k/y2kPaletteRefs.generated.ts +1 -1
- package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
- package/clients/cli/commands/docs.doc.mjs +5 -1
- package/clients/cli/commands/docs.mjs +30 -2
- package/clients/cli/commands/experimental/icon.mjs +25 -4
- package/clients/cli/commands/experimental-render.doc.mjs +1 -1
- package/clients/cli/commands/template.mjs +204 -181
- package/clients/cli/lib/resolve-theme.mjs +16 -7
- package/foundation/discovery/component-discovery.mjs +1 -1
- package/foundation/discovery/hook-discovery.mjs +31 -8
- package/foundation/discovery/icon-set-writer.d.mts +21 -5
- package/foundation/discovery/icon-set-writer.mjs +114 -13
- package/foundation/discovery/template-adapter.mjs +2 -2
- package/foundation/doc-compiler/import.d.mts +5 -1
- package/foundation/doc-compiler/import.mjs +40 -6
- package/foundation/fs/import-url.d.mts +20 -0
- package/foundation/fs/import-url.mjs +22 -0
- package/foundation/fs/module-loader.d.mts +24 -3
- package/foundation/fs/module-loader.mjs +69 -47
- package/foundation/render/charts.d.mts +59 -0
- package/foundation/render/charts.mjs +216 -0
- package/foundation/render/embedded-imports.d.mts +7 -7
- package/foundation/render/embedded-imports.mjs +63 -54
- package/foundation/render/page-host.d.mts +4 -0
- package/foundation/render/page-host.mjs +45 -0
- package/foundation/render/page-worker.mjs +350 -57
- package/foundation/render/render-page.d.mts +11 -6
- package/foundation/render/render-page.mjs +164 -54
- package/foundation/render/ts-paths.d.mts +63 -0
- package/foundation/render/ts-paths.mjs +138 -0
- package/foundation/response/error-codes.doc.mjs +3 -3
- package/foundation/response/error-codes.mjs +3 -2
- package/foundation/text/levenshtein.d.mts +6 -1
- package/foundation/text/levenshtein.mjs +25 -9
- package/foundation/text/suggest-order.d.mts +8 -1
- package/foundation/text/suggest-order.mjs +72 -6
- package/foundation/xle/registry-core.mjs +1 -1
- package/package.json +7 -6
|
@@ -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:
|
|
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
|
},
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file `astryx docs typography/type-scale`: line height, semantic type scale,
|
|
5
|
-
* display text, headings and text components,
|
|
5
|
+
* display text, headings and text components, custom type scales, and the
|
|
6
|
+
* reader's text size on iOS.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
/** @type {import('@astryxdesign/cli/authoring').ReferenceDoc} */
|
|
@@ -14,13 +15,15 @@ export const docs = {
|
|
|
14
15
|
placement: {parent: 'namespace:typography', slot: 'guides', order: 20},
|
|
15
16
|
category: 'foundations',
|
|
16
17
|
description:
|
|
17
|
-
|
|
18
|
+
"Line height, semantic type scale tokens, display text, Heading and Text components, custom type scales via defineTheme, and following the reader's text size on iOS.",
|
|
18
19
|
keywords: [
|
|
19
20
|
'line height',
|
|
20
21
|
'leading',
|
|
21
22
|
'defineTheme',
|
|
22
23
|
'base',
|
|
23
24
|
'ratio',
|
|
25
|
+
'dynamic type',
|
|
26
|
+
'text size',
|
|
24
27
|
],
|
|
25
28
|
|
|
26
29
|
sections: [
|
|
@@ -143,5 +146,19 @@ const denseTheme = defineTheme({
|
|
|
143
146
|
},
|
|
144
147
|
],
|
|
145
148
|
},
|
|
149
|
+
{
|
|
150
|
+
id: 'dynamic-type',
|
|
151
|
+
title: "Following the reader's text size on iOS",
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: 'prose',
|
|
155
|
+
text: "On iPhone, readers pick a text size in Settings (Display & Text Size, then Larger Text), and native apps grow or shrink their text with it. Every theme does the same in Safari on iPhone, which exposes that choice through `font: -apple-system-body`: each font size follows the reader's choice on the curve of the iOS text style it plays, so body text scales like Body, `heading-1` like Title 1 and display text like Large Title, and every role keeps its order. A size the theme fixes at a length, such as a 12px supporting size, scales on the curve of the step it reads by default; a size in `em` or `%` follows its parent, and one that reads a variable follows that variable. At the default size every font size is the theme's own. The family, spacing, radius and the root font size never change, line heights keep their ratios, text inputs keep their 16px floor on iOS, and on Android the system font scale grows every font size through `env(preferred-text-scale)` where the browser reports it; browsers with neither see no change. There is nothing to turn on: no theme option, prop or document attribute.",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'prose',
|
|
159
|
+
text: 'Fixed-height controls such as the text inputs keep their height, so their text can overflow at the accessibility sizes. A labelled `Button` grows with its label.',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
146
163
|
],
|
|
147
164
|
};
|
|
@@ -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: 'useInteractiveRole',
|
|
7
|
+
name: 'useInteractiveRole — One Tile, Four Roles',
|
|
8
|
+
displayName: 'useInteractiveRole — One Tile, Four Roles',
|
|
9
|
+
description:
|
|
10
|
+
'A polymorphic Tile used four ways: with an href it renders as a link, with an onClick as a button, with neither as plain content, and placed as a Popover trigger it becomes the trigger button through context — the resolution order in one place.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Popover', 'Card', 'Text', 'Layout', 'Link'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useInteractiveRole} from '@astryxdesign/core/hooks';
|
|
6
|
+
import {Link} from '@astryxdesign/core/Link';
|
|
7
|
+
import {Popover} from '@astryxdesign/core/Popover';
|
|
8
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
9
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
10
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
11
|
+
|
|
12
|
+
// A polymorphic Tile: the hook alone decides what it renders as — href wins,
|
|
13
|
+
// then onClick, then a trigger context (Popover), then inert.
|
|
14
|
+
function Tile({
|
|
15
|
+
href,
|
|
16
|
+
onClick,
|
|
17
|
+
children,
|
|
18
|
+
}: {
|
|
19
|
+
href?: string;
|
|
20
|
+
onClick?: () => void;
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
}) {
|
|
23
|
+
const role = useInteractiveRole({href, onClick});
|
|
24
|
+
const body = (
|
|
25
|
+
<Card padding={3} width={200}>
|
|
26
|
+
<Text type="body">{children}</Text>
|
|
27
|
+
</Card>
|
|
28
|
+
);
|
|
29
|
+
switch (role) {
|
|
30
|
+
case 'link':
|
|
31
|
+
return <Link href={href}>{body}</Link>;
|
|
32
|
+
case 'button':
|
|
33
|
+
return <button onClick={onClick}>{body}</button>;
|
|
34
|
+
default:
|
|
35
|
+
return body;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function UseInteractiveRoleHookUsage() {
|
|
40
|
+
return (
|
|
41
|
+
<VStack gap={3} hAlign="center" style={{width: '100%'}}>
|
|
42
|
+
<Tile href="https://example.com/docs">Renders as a link</Tile>
|
|
43
|
+
<Tile onClick={() => {}}>Renders as a button</Tile>
|
|
44
|
+
<Popover
|
|
45
|
+
label="Tile details"
|
|
46
|
+
content={
|
|
47
|
+
<Card padding={3}>
|
|
48
|
+
<Text type="body">Opened through the tile's context role</Text>
|
|
49
|
+
</Card>
|
|
50
|
+
}>
|
|
51
|
+
<Tile>Trigger via context</Tile>
|
|
52
|
+
</Popover>
|
|
53
|
+
<Tile>Plain content</Tile>
|
|
54
|
+
</VStack>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -7,8 +7,8 @@ export const doc = {
|
|
|
7
7
|
name: 'useStreamingText — Streaming Response',
|
|
8
8
|
displayName: 'useStreamingText — Streaming Response',
|
|
9
9
|
description:
|
|
10
|
-
'
|
|
10
|
+
'A chat-style reply card: while the response streams in bursts, useStreamingText reveals it steadily character by character; when streaming ends the full text snaps into place. Regenerate replays the stream.',
|
|
11
11
|
isReady: true,
|
|
12
12
|
aspectRatio: 4 / 3,
|
|
13
|
-
componentsUsed: ['Card', 'Text', 'Layout'],
|
|
13
|
+
componentsUsed: ['Button', 'Card', 'Text', 'Layout'],
|
|
14
14
|
};
|
|
@@ -2,29 +2,85 @@
|
|
|
2
2
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
|
+
import {useCallback, useEffect, useRef, useState} from 'react';
|
|
5
6
|
import {useStreamingText} from '@astryxdesign/core/hooks';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
6
8
|
import {Card} from '@astryxdesign/core/Card';
|
|
7
|
-
import {VStack} from '@astryxdesign/core/Layout';
|
|
8
9
|
import {Text} from '@astryxdesign/core/Text';
|
|
10
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
11
|
+
|
|
12
|
+
const CHUNKS = [
|
|
13
|
+
'Astryx hooks keep behavior reusable ',
|
|
14
|
+
'while components keep visuals consistent. ',
|
|
15
|
+
'Streaming text arrives in bursts; ',
|
|
16
|
+
'the hook smooths it into a steady reveal.',
|
|
17
|
+
];
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
// A fake stream: chunks land in bursts, the way a model streams tokens.
|
|
20
|
+
// Starting again stops the stream in progress, and the last chunk ends it.
|
|
21
|
+
function useFakeStream(onChunk: (chunk: string) => void, onEnd: () => void) {
|
|
22
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
23
|
+
const start = useCallback(() => {
|
|
24
|
+
if (timer.current) {
|
|
25
|
+
clearTimeout(timer.current);
|
|
26
|
+
}
|
|
27
|
+
let index = 0;
|
|
28
|
+
const tick = () => {
|
|
29
|
+
onChunk(CHUNKS[index]);
|
|
30
|
+
index += 1;
|
|
31
|
+
if (index < CHUNKS.length) {
|
|
32
|
+
timer.current = setTimeout(tick, 350);
|
|
33
|
+
} else {
|
|
34
|
+
timer.current = null;
|
|
35
|
+
onEnd();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
timer.current = setTimeout(tick, 350);
|
|
39
|
+
}, [onChunk, onEnd]);
|
|
40
|
+
useEffect(
|
|
41
|
+
() => () => {
|
|
42
|
+
if (timer.current) {
|
|
43
|
+
clearTimeout(timer.current);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[],
|
|
47
|
+
);
|
|
48
|
+
return start;
|
|
49
|
+
}
|
|
12
50
|
|
|
13
51
|
export default function UseStreamingTextHookUsage() {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
52
|
+
const [target, setTarget] = useState('');
|
|
53
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
54
|
+
|
|
55
|
+
const appendChunk = useCallback((chunk: string) => {
|
|
56
|
+
setTarget(previous => previous + chunk);
|
|
57
|
+
}, []);
|
|
58
|
+
// When the last chunk has landed, end the stream so the hook snaps to the
|
|
59
|
+
// full text.
|
|
60
|
+
const endStream = useCallback(() => setIsStreaming(false), []);
|
|
61
|
+
const startStream = useFakeStream(appendChunk, endStream);
|
|
62
|
+
|
|
63
|
+
const regenerate = useCallback(() => {
|
|
64
|
+
setTarget('');
|
|
65
|
+
setIsStreaming(true);
|
|
66
|
+
startStream();
|
|
67
|
+
}, [startStream]);
|
|
68
|
+
|
|
69
|
+
const displayedText = useStreamingText(target, isStreaming, {speed: 'fast'});
|
|
17
70
|
|
|
18
71
|
return (
|
|
19
72
|
<Card width={420} padding={4}>
|
|
20
|
-
<VStack gap={
|
|
73
|
+
<VStack gap={3}>
|
|
21
74
|
<Text type="body" weight="bold">
|
|
22
75
|
Assistant response
|
|
23
76
|
</Text>
|
|
24
77
|
<Text type="body">{displayedText}</Text>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
78
|
+
<Button
|
|
79
|
+
label={isStreaming ? 'Streaming…' : 'Regenerate'}
|
|
80
|
+
variant="secondary"
|
|
81
|
+
size="sm"
|
|
82
|
+
onClick={regenerate}
|
|
83
|
+
/>
|
|
28
84
|
</VStack>
|
|
29
85
|
</Card>
|
|
30
86
|
);
|
|
@@ -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: 'useLinkify',
|
|
7
|
+
name: 'useLinkify — Links in a Comment Thread',
|
|
8
|
+
displayName: 'useLinkify — Links in a Comment Thread',
|
|
9
|
+
description:
|
|
10
|
+
'Plain-text comments where useLinkify turns the URL and the email address into Links, and a custom pattern turns issue references like #123 into tracker links — the patterns option in action. The comment without matches stays plain text.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 4 / 3,
|
|
13
|
+
componentsUsed: ['Link', 'Layout', 'Text'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useLinkify, type LinkifyPattern} from '@astryxdesign/core/Link';
|
|
6
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
7
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
8
|
+
|
|
9
|
+
const COMMENTS = [
|
|
10
|
+
'See https://example.com/spec or email support@example.com.',
|
|
11
|
+
'Fixed by #123, tracking the regression in #456.',
|
|
12
|
+
'No links here, just a plain note.',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// Issue references like #123 link into the tracker; the capture group maps
|
|
16
|
+
// onto its URL scheme, showing the patterns option.
|
|
17
|
+
const ISSUE_PATTERN: LinkifyPattern = {
|
|
18
|
+
pattern: /#(\d+)/g,
|
|
19
|
+
href: match => `https://example.com/issues/${match[1]}`,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function Comment({children}: {children: string}) {
|
|
23
|
+
const nodes = useLinkify(children, {patterns: [ISSUE_PATTERN]});
|
|
24
|
+
return (
|
|
25
|
+
<Text type="body" color="secondary">
|
|
26
|
+
{nodes}
|
|
27
|
+
</Text>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function UseLinkifyHookUsage() {
|
|
32
|
+
return (
|
|
33
|
+
<VStack gap={3} style={{width: '100%', maxWidth: 360}}>
|
|
34
|
+
{COMMENTS.map(comment => (
|
|
35
|
+
<Comment key={comment}>{comment}</Comment>
|
|
36
|
+
))}
|
|
37
|
+
</VStack>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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: 'useTableRowsVirtualization',
|
|
7
|
+
name: 'useTableRowsVirtualization - Fixed Height Rows',
|
|
8
|
+
displayName: 'useTableRowsVirtualization - Fixed Height Rows',
|
|
9
|
+
description:
|
|
10
|
+
"A Table with 1,000 product rows inside a 320px-tall parent, which bounds its scroll area. Give the Table's parent a height, fixed or relative, or a flex child, so windowing can bound the rows; with no height every row renders. Scroll vertically to mount the visible rows. The caller keeps all data and columns; this hook does not virtualize columns.",
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['Table', 'VStack'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Table,
|
|
7
|
+
useTableRowsVirtualization,
|
|
8
|
+
type TableColumn,
|
|
9
|
+
} from '@astryxdesign/core/Table';
|
|
10
|
+
import {VStack} from '@astryxdesign/core/Layout';
|
|
11
|
+
|
|
12
|
+
interface Product extends Record<string, unknown> {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const products: Product[] = Array.from({length: 1000}, (_, index) => ({
|
|
18
|
+
id: `product-${index}`,
|
|
19
|
+
name: `Product ${index + 1}`,
|
|
20
|
+
}));
|
|
21
|
+
const columns: TableColumn<Product>[] = [{key: 'name', header: 'Product'}];
|
|
22
|
+
|
|
23
|
+
export default function RowsVirtualizationHookUsage() {
|
|
24
|
+
const virtualization = useTableRowsVirtualization({
|
|
25
|
+
rowHeight: 56,
|
|
26
|
+
initialViewportHeight: 320,
|
|
27
|
+
});
|
|
28
|
+
// Give the Table's parent a height, fixed or relative, or a flex child, so
|
|
29
|
+
// windowing can bound the rows; with no height every row renders.
|
|
30
|
+
// initialViewportHeight only sizes the first window.
|
|
31
|
+
return (
|
|
32
|
+
<VStack height={320}>
|
|
33
|
+
<Table
|
|
34
|
+
data={products}
|
|
35
|
+
columns={columns}
|
|
36
|
+
idKey="id"
|
|
37
|
+
virtualization={virtualization}
|
|
38
|
+
/>
|
|
39
|
+
</VStack>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -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: 'useTableColumnSettingsState',
|
|
7
|
+
name: 'useTableColumnSettingsState — Column Visibility Picker',
|
|
8
|
+
displayName: 'useTableColumnSettingsState — Column Visibility Picker',
|
|
9
|
+
description:
|
|
10
|
+
'A Table toolbar with a Columns menu of checkboxes: toggling Email updates active columns, Reset restores defaults, and the config is passed directly to useTableColumnSettings.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['Button', 'CheckboxInput', 'Popover', 'Table', 'Layout'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
7
|
+
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
8
|
+
import {HStack, VStack} from '@astryxdesign/core/Layout';
|
|
9
|
+
import {Popover} from '@astryxdesign/core/Popover';
|
|
10
|
+
import {
|
|
11
|
+
Table,
|
|
12
|
+
useTableColumnSettings,
|
|
13
|
+
useTableColumnSettingsState,
|
|
14
|
+
} from '@astryxdesign/core/Table';
|
|
15
|
+
import type {TableColumn} from '@astryxdesign/core/Table';
|
|
16
|
+
|
|
17
|
+
interface Member extends Record<string, unknown> {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
email: string;
|
|
21
|
+
role: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const members: Member[] = [
|
|
25
|
+
{id: '1', name: 'Alice', email: 'alice@example.com', role: 'Engineer'},
|
|
26
|
+
{id: '2', name: 'Bob', email: 'bob@example.com', role: 'Designer'},
|
|
27
|
+
{id: '3', name: 'Charlie', email: 'charlie@example.com', role: 'Manager'},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const allColumns: TableColumn<Member>[] = [
|
|
31
|
+
{key: 'name', header: 'Name'},
|
|
32
|
+
{key: 'email', header: 'Email'},
|
|
33
|
+
{key: 'role', header: 'Role'},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const columnOptions = [
|
|
37
|
+
{key: 'name' as const, label: 'Name', isAlwaysVisible: true},
|
|
38
|
+
{key: 'email' as const, label: 'Email'},
|
|
39
|
+
{key: 'role' as const, label: 'Role'},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const defaultKeys = ['name', 'email', 'role'];
|
|
43
|
+
|
|
44
|
+
export default function UseTableColumnSettingsStateHookUsage() {
|
|
45
|
+
const [activeKeys, setActiveKeys] =
|
|
46
|
+
useState<ReadonlyArray<string>>(defaultKeys);
|
|
47
|
+
|
|
48
|
+
const state = useTableColumnSettingsState({
|
|
49
|
+
columns: columnOptions,
|
|
50
|
+
activeColumnKeys: activeKeys,
|
|
51
|
+
onChangeActiveColumnKeys: setActiveKeys,
|
|
52
|
+
defaultColumnKeys: defaultKeys,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const plugin = useTableColumnSettings<Member>(state.columnSettingsConfig);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<VStack gap={3}>
|
|
59
|
+
<HStack hAlign="start">
|
|
60
|
+
<Popover
|
|
61
|
+
placement="below"
|
|
62
|
+
label="Column settings"
|
|
63
|
+
width={200}
|
|
64
|
+
content={
|
|
65
|
+
<VStack gap={2}>
|
|
66
|
+
{columnOptions.map(col => (
|
|
67
|
+
<CheckboxInput
|
|
68
|
+
key={col.key}
|
|
69
|
+
label={col.label}
|
|
70
|
+
value={state.isColumnActive(col.key)}
|
|
71
|
+
isDisabled={!state.isColumnToggleable(col.key)}
|
|
72
|
+
onChange={() => state.toggleColumn(col.key)}
|
|
73
|
+
/>
|
|
74
|
+
))}
|
|
75
|
+
<Button
|
|
76
|
+
label="Reset"
|
|
77
|
+
size="sm"
|
|
78
|
+
variant="secondary"
|
|
79
|
+
onClick={state.resetToDefault}>
|
|
80
|
+
Reset
|
|
81
|
+
</Button>
|
|
82
|
+
</VStack>
|
|
83
|
+
}>
|
|
84
|
+
<Button label="Columns" size="sm">
|
|
85
|
+
Columns
|
|
86
|
+
</Button>
|
|
87
|
+
</Popover>
|
|
88
|
+
</HStack>
|
|
89
|
+
<Table
|
|
90
|
+
data={members}
|
|
91
|
+
columns={allColumns}
|
|
92
|
+
idKey="id"
|
|
93
|
+
plugins={{columnSettings: plugin}}
|
|
94
|
+
/>
|
|
95
|
+
</VStack>
|
|
96
|
+
);
|
|
97
|
+
}
|