@astryxdesign/cli 0.4.2 → 0.4.3-canary.00bed29
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 +23 -0
- package/README.md +45 -43
- package/api/discover/_adapter.d.mts +5 -3
- package/api/discover/_adapter.mjs +6 -4
- package/api/template/cdn/cdn.d.mts +23 -0
- package/api/template/cdn/cdn.mjs +86 -0
- package/api/template/cdn/cdn.test.mjs +108 -0
- package/api/template/template.d.mts +2 -0
- package/api/template/template.doc.mjs +21 -3
- package/api/template/template.mjs +16 -2
- package/api/template/template.type.d.mts +19 -0
- package/api/template/template.type.mjs +12 -0
- package/api/theme/build/build.font-warning.test.mjs +23 -14
- package/api/theme/build/build.icons-specifier.test.mjs +149 -0
- package/api/theme/build/build.mjs +88 -16
- package/api/theme/build/build.test.mjs +185 -14
- package/api/theme/theme.type.d.mts +22 -0
- package/api/theme/theme.type.mjs +15 -1
- package/assets/cdn.template.html +124 -0
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +11 -5
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.doc.mjs +21 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetHeights.tsx +46 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.doc.mjs +23 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetMobileKeyboard.tsx +100 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.doc.mjs +22 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetNoScrim.tsx +47 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.doc.mjs +22 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetShowcase.tsx +51 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.doc.mjs +26 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetSwitcherShowcase.tsx +221 -0
- package/assets/templates/blocks/components/ComplexSelector/ComplexSelectorDeadlinePicker.doc.mjs +20 -0
- package/assets/templates/blocks/components/ComplexSelector/ComplexSelectorDeadlinePicker.tsx +87 -0
- package/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/ComplexSelector/ComplexSelectorShowcase.tsx +199 -0
- package/assets/templates/blocks/components/ComplexSelector/ComplexSelectorTreeSearch.doc.mjs +14 -0
- package/assets/templates/blocks/components/ComplexSelector/ComplexSelectorTreeSearch.tsx +188 -0
- package/assets/templates/themes/neutral/neutralTheme.ts +6 -4
- package/assets/theme.template.ts +4 -3
- package/authoring/doctypes/base/type.ts +9 -0
- package/clients/cli/commands/build-theme.font-warning.test.mjs +9 -6
- package/clients/cli/commands/build-theme.icons-specifier.test.mjs +132 -7
- package/clients/cli/commands/build-theme.mjs +154 -65
- package/clients/cli/commands/build-theme.multi.test.mjs +148 -0
- package/clients/cli/commands/build-theme.watch.test.mjs +67 -0
- package/clients/cli/commands/discover.broken-integration.test.mjs +112 -0
- package/clients/cli/commands/discover.mjs +12 -0
- package/clients/cli/commands/search.mjs +9 -0
- package/clients/cli/commands/template-cdn.behavior.test.mjs +113 -0
- package/clients/cli/commands/template.doc.mjs +12 -2
- package/clients/cli/commands/template.mjs +24 -3
- package/clients/cli/commands/theme-build.doc.mjs +11 -5
- package/clients/cli/lib/json-shim.test.mjs +1 -1
- package/clients/cli/lib/manifest.mjs +3 -2
- package/clients/cli/lib/manifest.test.mjs +4 -2
- package/foundation/fs/path-safety.mjs +2 -2
- package/foundation/fs/path-safety.test.mjs +7 -0
- package/foundation/integrations/integration-warnings.test.mjs +17 -0
- package/foundation/integrations/validate-contributions.mjs +7 -0
- package/foundation/response/response-types.doc.mjs +11 -0
- package/foundation/text/copyright-header.mjs +11 -4
- package/package.json +12 -9
- package/assets/templates/pages/table-page-chart/page.tsx +0 -577
- package/assets/templates/pages/table-page-chart/template.doc.mjs +0 -13
- package/assets/templates/pages/table-page-heatmap-status/page.tsx +0 -467
- package/assets/templates/pages/table-page-heatmap-status/template.doc.mjs +0 -13
- package/assets/templates/pages/table-page-shoe-store-heatmap/page.tsx +0 -931
- package/assets/templates/pages/table-page-shoe-store-heatmap/template.doc.mjs +0 -13
package/api/theme/theme.type.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* ------------------------------------------------------------------
|
|
11
11
|
* xds --json theme build <file> -> theme.build
|
|
12
12
|
* xds --json theme build <file> --check -> theme.build.check
|
|
13
|
+
* xds --json theme build <a> <b> … -> theme.build.batch
|
|
13
14
|
* xds --json theme list -> theme.list
|
|
14
15
|
* xds --json theme add <slug> -> theme.add
|
|
15
16
|
* xds --json theme template -> theme.template
|
|
@@ -22,7 +23,10 @@
|
|
|
22
23
|
* xds --json theme build <file>
|
|
23
24
|
* @typedef {object} ThemeBuildResponse
|
|
24
25
|
* @property {'theme.build'} type
|
|
25
|
-
*
|
|
26
|
+
* `warnings` are defects the theme author should fix. `notices` are advisories
|
|
27
|
+
* about a correct theme — most of them cannot be fixed in a theme file at all,
|
|
28
|
+
* so folding them into `warnings` makes a clean build look dirty.
|
|
29
|
+
* @property {{name: string, tokenCount: number, componentCount: number, sizeKB: number, outputs: {css: string, js: string, dts: string, variantsDts?: string}, warnings: string[], notices: string[]}} data
|
|
26
30
|
*/
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -32,6 +36,16 @@
|
|
|
32
36
|
* @property {{name: string, upToDate: boolean, stale: Array<{path: string, reason: 'missing' | 'outdated'}>, checked: string[]}} data
|
|
33
37
|
*/
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* xds --json theme build <a> <b> … — several themes in one invocation. Each
|
|
41
|
+
* result carries the file as it was passed and the receipt a single-file build
|
|
42
|
+
* would have returned (null when that theme produced no CSS). One file still
|
|
43
|
+
* returns the bare theme.build / theme.build.check envelope.
|
|
44
|
+
* @typedef {object} ThemeBuildBatchResponse
|
|
45
|
+
* @property {'theme.build.batch'} type
|
|
46
|
+
* @property {{count: number, results: Array<{file: string, receipt: ThemeBuildResponse | ThemeBuildCheckResponse | null}>}} data
|
|
47
|
+
*/
|
|
48
|
+
|
|
35
49
|
/**
|
|
36
50
|
* A single theme entry as surfaced by `theme list`.
|
|
37
51
|
* @typedef {object} ThemeListEntry
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<!-- Copyright (c) Meta Platforms, Inc. and affiliates. -->
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<!--
|
|
4
|
+
Astryx from a CDN — no build step, no bundler, no install.
|
|
5
|
+
|
|
6
|
+
Open this file in a browser (double-click works) or serve it from anywhere.
|
|
7
|
+
Everything is pinned to the Astryx version you had installed when this was
|
|
8
|
+
written; nothing here resolves to "latest", because an unpinned CDN URL
|
|
9
|
+
silently changes under you.
|
|
10
|
+
|
|
11
|
+
React 19 removed its UMD builds, so there is no global-script path any more:
|
|
12
|
+
an import map plus `type="module"` is how you load Astryx without a bundler.
|
|
13
|
+
|
|
14
|
+
Two things worth doing next:
|
|
15
|
+
- `astryx theme template` writes an annotated theme you can point this page
|
|
16
|
+
at. Its `color` scale generates a whole ramp from one seed colour and
|
|
17
|
+
guarantees >= 4.5:1 for the text it generates against its surface.
|
|
18
|
+
- `astryx component <Name>` prints any component's real prop surface.
|
|
19
|
+
-->
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="utf-8" />
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
24
|
+
<!--
|
|
25
|
+
The theme names a font family; it never loads one. Without this link the
|
|
26
|
+
whole page silently falls back to the next family in the stack, which is
|
|
27
|
+
not the design you chose. `neutral` asks for Figtree — swap this for
|
|
28
|
+
whatever your theme names, or self-host with @font-face.
|
|
29
|
+
-->
|
|
30
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
31
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
32
|
+
<link
|
|
33
|
+
rel="stylesheet"
|
|
34
|
+
href="https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700&display=swap" />
|
|
35
|
+
<!-- reset.css and astryx.css are the component styles; theme.css is the
|
|
36
|
+
theme. Note /dist/theme.css — the package root has no theme.css. -->
|
|
37
|
+
<link
|
|
38
|
+
rel="stylesheet"
|
|
39
|
+
href="https://cdn.jsdelivr.net/npm/@astryxdesign/core@__ASTRYX_VERSION__/src/reset.css" />
|
|
40
|
+
<link
|
|
41
|
+
rel="stylesheet"
|
|
42
|
+
href="https://cdn.jsdelivr.net/npm/@astryxdesign/core@__ASTRYX_VERSION__/dist/astryx.css" />
|
|
43
|
+
<link
|
|
44
|
+
rel="stylesheet"
|
|
45
|
+
href="https://cdn.jsdelivr.net/npm/@astryxdesign/theme-neutral@__ASTRYX_VERSION__/dist/theme.css" />
|
|
46
|
+
<!--
|
|
47
|
+
react/jsx-runtime is not decoration: the published bundle imports it, and
|
|
48
|
+
leaving it out fails the page with "Failed to resolve module specifier".
|
|
49
|
+
|
|
50
|
+
?external=react,react-dom makes esm.sh reuse the React above instead of
|
|
51
|
+
bundling its own. Two React copies means every hook throws
|
|
52
|
+
"Cannot read properties of null (reading 'useState')".
|
|
53
|
+
|
|
54
|
+
htm is optional — delete it and the `h` binding below if you would rather
|
|
55
|
+
call createElement directly. It costs ~1KB and buys JSX-shaped markup,
|
|
56
|
+
which is worth it as soon as the tree is more than a few levels deep.
|
|
57
|
+
-->
|
|
58
|
+
<script type="importmap">
|
|
59
|
+
{
|
|
60
|
+
"imports": {
|
|
61
|
+
"react": "https://esm.sh/react@19.2.0",
|
|
62
|
+
"react/jsx-runtime": "https://esm.sh/react@19.2.0/jsx-runtime",
|
|
63
|
+
"react-dom": "https://esm.sh/react-dom@19.2.0",
|
|
64
|
+
"react-dom/client": "https://esm.sh/react-dom@19.2.0/client",
|
|
65
|
+
"htm": "https://esm.sh/htm@3.1.1",
|
|
66
|
+
"@astryxdesign/core": "https://esm.sh/@astryxdesign/core@__ASTRYX_VERSION__?external=react,react-dom",
|
|
67
|
+
"@astryxdesign/theme-neutral": "https://esm.sh/@astryxdesign/theme-neutral@__ASTRYX_VERSION__?external=react,react-dom"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
<!-- Nothing in the stylesheets sets a document font: the theme styles
|
|
72
|
+
prose elements and each component styles itself, and Button is
|
|
73
|
+
`font: inherit` by design. Without this line its label falls back to
|
|
74
|
+
the browser default (Times). -->
|
|
75
|
+
<style>
|
|
76
|
+
body {
|
|
77
|
+
font-family: var(--font-family-body);
|
|
78
|
+
}
|
|
79
|
+
/* Modules load over the network, so the page is empty for a moment.
|
|
80
|
+
Say so, rather than showing a blank screen that reads as broken. */
|
|
81
|
+
#root:empty::after {
|
|
82
|
+
content: 'Loading…';
|
|
83
|
+
display: block;
|
|
84
|
+
padding: 2rem;
|
|
85
|
+
color: var(--color-text-secondary);
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
88
|
+
</head>
|
|
89
|
+
<body>
|
|
90
|
+
<div id="root"></div>
|
|
91
|
+
<script type="module">
|
|
92
|
+
import * as React from 'react';
|
|
93
|
+
import {createRoot} from 'react-dom/client';
|
|
94
|
+
import htm from 'htm';
|
|
95
|
+
import {Theme, Card, Stack, Heading, Text, Button} from '@astryxdesign/core';
|
|
96
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral';
|
|
97
|
+
|
|
98
|
+
// Nothing is compiling this file, so there is no JSX. htm gives the same
|
|
99
|
+
// shape from a tagged template; `React.createElement` directly is the
|
|
100
|
+
// dependency-free alternative.
|
|
101
|
+
const h = htm.bind(React.createElement);
|
|
102
|
+
|
|
103
|
+
// Passing the theme OBJECT is what makes `mode` work: 'system' follows
|
|
104
|
+
// the OS, and light/dark switch at runtime. The `data-astryx-theme`
|
|
105
|
+
// attribute alone only scopes the stylesheet — it cannot switch modes.
|
|
106
|
+
createRoot(document.getElementById('root')).render(h`
|
|
107
|
+
<${Theme} theme=${neutralTheme} mode="system">
|
|
108
|
+
<${Stack} padding=${6} align="start">
|
|
109
|
+
<${Card} maxWidth=${480} elevation="low">
|
|
110
|
+
<${Stack} gap=${3} align="start">
|
|
111
|
+
<${Heading} level=${1}>Hello from a CDN</${Heading}>
|
|
112
|
+
<${Text}>No bundler, no install, no build step.</${Text}>
|
|
113
|
+
<!-- Button's text comes from \`label\`, which is required — it is
|
|
114
|
+
the accessible name. Children are optional and only change
|
|
115
|
+
the visible text; the accessible name stays \`label\`. -->
|
|
116
|
+
<${Button} variant="primary" label="Try me" />
|
|
117
|
+
</${Stack}>
|
|
118
|
+
</${Card}>
|
|
119
|
+
</${Stack}>
|
|
120
|
+
</${Theme}>
|
|
121
|
+
`);
|
|
122
|
+
</script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
@@ -9,7 +9,7 @@ export const docsDense = {
|
|
|
9
9
|
{ section: 'Available Themes', title: 'Themes', content: [null, null, { type: 'prose', text: 'published: neutral (start here), butter, chocolate, gothic (dark-only), matcha, stone, y2k. @astryxdesign/theme-{name} = source (runtime). @astryxdesign/theme-{name}/built = optimized (+ theme.css).' }] },
|
|
10
10
|
{ section: 'Theme Props', title: 'Props', content: [null] },
|
|
11
11
|
{ section: 'Creating a Custom Theme', title: 'Custom Theme', content: [{ type: 'prose', text: '`theme list` + `theme add <slug>` to start from a shipped theme, or defineTheme from scratch. only override tokens that differ.' }, null, { type: 'prose', text: '`astryx theme template` writes theme.template.ts: every defineTheme field + token families + override syntax, annotated, with the CLI command that prints each reference.' }] },
|
|
12
|
-
{ section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent hex
|
|
12
|
+
{ section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent via HCT; accent = hex or [light, dark] tuple (per-scheme palettes). tokens overrides win token-by-token; --color-on-accent stays baked from color.accent, so prefer a tuple accent over overriding --color-accent.' }, null, null] },
|
|
13
13
|
{ section: 'Component Style Overrides', title: 'Component Overrides', content: [{ type: 'prose', text: 'components field uses semantic component keys + style keys (base, variant:value, stateName), not raw selectors. for external CSS, prefer data-* selectors from `astryx docs styling`. write standard CSS (borderRadius, padding) — pipeline expands to internal vars. public vars (--button-focus-offset etc) set directly. private vars (--_*) cannot be set — use CSS properties. run `astryx component <Name>` for details.' }, null, null, null, null] },
|
|
14
14
|
{ section: 'Custom Variants', title: 'Custom Variants', content: [{ type: 'prose', text: 'any unknown prop:value in components becomes a new variant. astryx theme build generates TS augmentations. works on any extensible prop axis (variant, status, etc).' }, null, null, null, null] },
|
|
15
15
|
{ section: 'Building Themes for Production', title: 'Build for Production', content: [{ type: 'prose', text: 'astryx theme build compiles defineTheme to static CSS. outputs .css + .js (__built:true) + .d.ts.' }, null, null, null, null] },
|
|
@@ -162,7 +162,7 @@ function App() {
|
|
|
162
162
|
content: [
|
|
163
163
|
{
|
|
164
164
|
type: 'prose',
|
|
165
|
-
text: 'defineTheme creates a theme from token overrides and optional scale configs. Scale configs generate tokens from parameters. Explicit token overrides always take precedence over scale-generated values.',
|
|
165
|
+
text: 'defineTheme creates a theme from token overrides and optional scale configs. Scale configs generate tokens from parameters. Explicit token overrides always take precedence over scale-generated values, token by token. One caveat for the accent: overriding --color-accent in tokens re-points the reference tokens (--color-accent-muted, --color-text-accent, --color-icon-accent) but NOT --color-on-accent, which stays baked from the color.accent seed. To give each scheme its own accent with a consistent derived palette, pass a [light, dark] tuple to color.accent instead of overriding the token.',
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
type: 'code',
|
|
@@ -172,7 +172,8 @@ function App() {
|
|
|
172
172
|
|
|
173
173
|
const myTheme = defineTheme({
|
|
174
174
|
name: 'my-theme',
|
|
175
|
-
|
|
175
|
+
// accent: single hex, or [light, dark] tuple to seed each scheme separately
|
|
176
|
+
color: { accent: ['#7B61FF', '#9B85FF'], neutralStyle: 'cool' },
|
|
176
177
|
typography: {
|
|
177
178
|
scale: { base: 14, ratio: 1.2 },
|
|
178
179
|
body: { family: 'Inter', fallbacks: '-apple-system, sans-serif' },
|
|
@@ -181,7 +182,7 @@ const myTheme = defineTheme({
|
|
|
181
182
|
motion: { fast: 175, medium: 410, ratio: 0.75 },
|
|
182
183
|
tokens: {
|
|
183
184
|
// Explicit overrides take precedence over scale-generated values
|
|
184
|
-
'--color-
|
|
185
|
+
'--color-background-body': ['#FFFFFF', '#0A0A0A'],
|
|
185
186
|
},
|
|
186
187
|
components: {
|
|
187
188
|
button: { 'variant:primary': { color: 'white' } },
|
|
@@ -195,7 +196,7 @@ const myTheme = defineTheme({
|
|
|
195
196
|
[
|
|
196
197
|
'color',
|
|
197
198
|
'--color-accent, --color-background-*, --color-text-*, --color-border, etc.',
|
|
198
|
-
'accent? (hex; omit for neutral-only), neutralStyle? (warm|cool|neutral), contrast? (standard|high)',
|
|
199
|
+
'accent? (hex or [light, dark] tuple; omit for neutral-only), neutralStyle? (warm|cool|neutral), contrast? (standard|high)',
|
|
199
200
|
],
|
|
200
201
|
[
|
|
201
202
|
'typography.scale',
|
|
@@ -253,10 +254,15 @@ const brandTheme = defineTheme({
|
|
|
253
254
|
['tokens', 'Base tokens are copied first, then child tokens override on top.'],
|
|
254
255
|
['components', 'Deep-merged: child component rules override matching keys from the base.'],
|
|
255
256
|
['icons', 'Shallow-merged: child icons override matching names from the base.'],
|
|
256
|
-
['
|
|
257
|
+
['indicators', 'Shallow-merged: child indicators override matching names from the base.'],
|
|
258
|
+
['onDark, onLight', "Deep-merged per surface: the base's resolved surface first, then the child's overrides."],
|
|
257
259
|
['typography, motion, radius, color', 'Child config replaces base entirely (these are scale inputs, not additive).'],
|
|
258
260
|
],
|
|
259
261
|
},
|
|
262
|
+
{
|
|
263
|
+
type: 'prose',
|
|
264
|
+
text: 'Inheritance is resolved when the theme is defined, so an extended theme is flat: `astryx theme build` emits one self-contained stylesheet holding everything the child inherited, and the base theme\'s CSS does not need to be loaded next to it. A base that is not a theme — most often an import that missed — is a build error rather than a theme that silently inherits nothing.',
|
|
265
|
+
},
|
|
260
266
|
],
|
|
261
267
|
},
|
|
262
268
|
{
|
|
@@ -9,7 +9,7 @@ export const docsZh = {
|
|
|
9
9
|
{ section: 'Available Themes', title: '可用主题', content: [null, null, { type: 'prose', text: '已发布主题:neutral(推荐起点)、butter、chocolate、gothic(仅暗色)、matcha、stone、y2k。@astryxdesign/theme-{name} = 源码版(运行时注入)。@astryxdesign/theme-{name}/built = 优化版(配合 theme.css)。' }] },
|
|
10
10
|
{ section: 'Theme Props', title: 'Theme 属性', content: [null] },
|
|
11
11
|
{ section: 'Creating a Custom Theme', title: '创建自定义主题', content: [{ type: 'prose', text: '用 `theme list` + `theme add <slug>` 从内置主题开始,或用 defineTheme 从零编写。只覆盖与默认值不同的令牌。' }, null, { type: 'prose', text: '`astryx theme template` 会写入 theme.template.ts:带注释的完整参考,涵盖每个 defineTheme 字段、令牌族和覆盖语法,并标明打印各自参考的 CLI 命令。' }] },
|
|
12
|
-
{ section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: '支持比例配置(typography、radius、motion)+ 显式令牌覆盖 + 组件覆盖。' }, null, null] },
|
|
12
|
+
{ section: 'defineTheme', title: 'defineTheme', content: [{ type: 'prose', text: '支持比例配置(color、typography、radius、motion)+ 显式令牌覆盖 + 组件覆盖。color 通过 HCT 从 accent 派生完整调色板;accent 接受单个十六进制值或 [light, dark] 元组(每个模式使用各自的种子色)。tokens 覆盖按令牌逐个生效;--color-on-accent 始终由 color.accent 计算得出,因此优先使用元组 accent 而不是覆盖 --color-accent。' }, null, null] },
|
|
13
13
|
{ section: 'Building Themes for Production', title: '生产构建', content: [{ type: 'prose', text: 'astryx theme build 将 defineTheme 编译为静态 CSS。输出 .css + .js(__built:true)+ .d.ts。' }, null, null, null, null] },
|
|
14
14
|
{ section: 'Runtime vs Built Themes', title: '运行时 vs 构建', content: [{ type: 'prose', text: '运行时:useInsertionEffect 在客户端注入样式。构建:静态 CSS 在首次渲染时就存在。SSR 应用请使用 /built + theme.css。' }, null, null, null] },
|
|
15
15
|
{ section: 'Light/Dark Mode', title: '亮/暗模式', content: [{ type: 'prose', text: "令牌值使用 [light, dark] 元组实现自动模式切换。Theme 上 mode='system'(默认)跟随系统偏好。" }, null, null] },
|
|
@@ -0,0 +1,21 @@
|
|
|
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: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — Height variants',
|
|
8
|
+
displayName: 'Bottom Sheet — Height variants',
|
|
9
|
+
description:
|
|
10
|
+
'Compares hug, capped, and tall starting heights for different amounts of content.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Stack',
|
|
19
|
+
'Text',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {
|
|
7
|
+
BottomSheet,
|
|
8
|
+
type BottomSheetHeight,
|
|
9
|
+
} from '@astryxdesign/core/BottomSheet';
|
|
10
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
11
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
12
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
13
|
+
import {HStack, VStack} from '@astryxdesign/core/Stack';
|
|
14
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
15
|
+
|
|
16
|
+
const descriptions: Record<BottomSheetHeight, string> = {
|
|
17
|
+
hug: 'Hug fits short, bounded content.',
|
|
18
|
+
capped: 'Capped starts at a comfortable mid-height for lists and filters.',
|
|
19
|
+
tall: 'Tall reserves most of the viewport for long or changing content.',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function BottomSheetHeights() {
|
|
23
|
+
const [height, setHeight] = useState<BottomSheetHeight | null>(null);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<HStack gap={2} wrap="wrap">
|
|
28
|
+
<Button label="Open hug" onClick={() => setHeight('hug')} />
|
|
29
|
+
<Button label="Open capped" onClick={() => setHeight('capped')} />
|
|
30
|
+
<Button label="Open tall" onClick={() => setHeight('tall')} />
|
|
31
|
+
</HStack>
|
|
32
|
+
<BottomSheet
|
|
33
|
+
isOpen={height != null}
|
|
34
|
+
onOpenChange={isOpen => !isOpen && setHeight(null)}
|
|
35
|
+
label={`${height ?? 'Hug'} height`}
|
|
36
|
+
height={height ?? 'hug'}>
|
|
37
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
38
|
+
<Heading level={3}>{height ?? 'Hug'} height</Heading>
|
|
39
|
+
<Divider />
|
|
40
|
+
<Text type="body">{descriptions[height ?? 'hug']}</Text>
|
|
41
|
+
<Button label="Close" onClick={() => setHeight(null)} />
|
|
42
|
+
</VStack>
|
|
43
|
+
</BottomSheet>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — Mobile keyboard',
|
|
8
|
+
displayName: 'Bottom Sheet — Mobile keyboard',
|
|
9
|
+
description:
|
|
10
|
+
'Uses a tall, scrollable form that keeps focused controls visible above the mobile keyboard.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Stack',
|
|
19
|
+
'Text',
|
|
20
|
+
'TextArea',
|
|
21
|
+
'TextInput',
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
9
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
10
|
+
import {VStack} from '@astryxdesign/core/Stack';
|
|
11
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
12
|
+
import {TextArea} from '@astryxdesign/core/TextArea';
|
|
13
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
14
|
+
|
|
15
|
+
interface ProfileFormValues {
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
company: string;
|
|
19
|
+
role: string;
|
|
20
|
+
bio: string;
|
|
21
|
+
notes: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const initialValues: ProfileFormValues = {
|
|
25
|
+
name: '',
|
|
26
|
+
email: '',
|
|
27
|
+
company: '',
|
|
28
|
+
role: '',
|
|
29
|
+
bio: '',
|
|
30
|
+
notes: '',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default function BottomSheetMobileKeyboard() {
|
|
34
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
35
|
+
const [values, setValues] = useState(initialValues);
|
|
36
|
+
const update =
|
|
37
|
+
(field: keyof ProfileFormValues) =>
|
|
38
|
+
(value: string): void =>
|
|
39
|
+
setValues(current => ({...current, [field]: value}));
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
<Button label="Edit profile" onClick={() => setIsOpen(true)} />
|
|
44
|
+
<BottomSheet
|
|
45
|
+
isOpen={isOpen}
|
|
46
|
+
onOpenChange={setIsOpen}
|
|
47
|
+
label="Edit profile"
|
|
48
|
+
height="tall">
|
|
49
|
+
<form
|
|
50
|
+
onSubmit={event => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
setIsOpen(false);
|
|
53
|
+
}}>
|
|
54
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
55
|
+
<Heading level={3}>Edit profile</Heading>
|
|
56
|
+
<Divider />
|
|
57
|
+
<Text type="supporting" color="secondary">
|
|
58
|
+
Focus fields throughout the form to see them remain visible above
|
|
59
|
+
the mobile keyboard.
|
|
60
|
+
</Text>
|
|
61
|
+
<TextInput
|
|
62
|
+
label="Name"
|
|
63
|
+
value={values.name}
|
|
64
|
+
onChange={update('name')}
|
|
65
|
+
/>
|
|
66
|
+
<TextInput
|
|
67
|
+
label="Email"
|
|
68
|
+
type="email"
|
|
69
|
+
value={values.email}
|
|
70
|
+
onChange={update('email')}
|
|
71
|
+
/>
|
|
72
|
+
<TextInput
|
|
73
|
+
label="Company"
|
|
74
|
+
value={values.company}
|
|
75
|
+
onChange={update('company')}
|
|
76
|
+
/>
|
|
77
|
+
<TextInput
|
|
78
|
+
label="Role"
|
|
79
|
+
value={values.role}
|
|
80
|
+
onChange={update('role')}
|
|
81
|
+
/>
|
|
82
|
+
<TextArea
|
|
83
|
+
label="Bio"
|
|
84
|
+
rows={5}
|
|
85
|
+
value={values.bio}
|
|
86
|
+
onChange={update('bio')}
|
|
87
|
+
/>
|
|
88
|
+
<TextArea
|
|
89
|
+
label="Notes"
|
|
90
|
+
rows={5}
|
|
91
|
+
value={values.notes}
|
|
92
|
+
onChange={update('notes')}
|
|
93
|
+
/>
|
|
94
|
+
<Button label="Save profile" type="submit" />
|
|
95
|
+
</VStack>
|
|
96
|
+
</form>
|
|
97
|
+
</BottomSheet>
|
|
98
|
+
</>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — No scrim',
|
|
8
|
+
displayName: 'Bottom Sheet — No scrim',
|
|
9
|
+
description:
|
|
10
|
+
'Keeps the page visible and interactive behind a non-modal bottom sheet.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Section',
|
|
19
|
+
'Stack',
|
|
20
|
+
'Text',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
9
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
10
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
11
|
+
import {VStack} from '@astryxdesign/core/Stack';
|
|
12
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
13
|
+
|
|
14
|
+
export default function BottomSheetNoScrim() {
|
|
15
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [backgroundClicks, setBackgroundClicks] = useState(0);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Section padding={4}>
|
|
20
|
+
<VStack gap={3}>
|
|
21
|
+
<Heading level={3}>Nearby places</Heading>
|
|
22
|
+
<Text type="body">Background interactions: {backgroundClicks}</Text>
|
|
23
|
+
<Button
|
|
24
|
+
label="Interact with page"
|
|
25
|
+
variant="secondary"
|
|
26
|
+
onClick={() => setBackgroundClicks(count => count + 1)}
|
|
27
|
+
/>
|
|
28
|
+
<Button label="Show place details" onClick={() => setIsOpen(true)} />
|
|
29
|
+
</VStack>
|
|
30
|
+
<BottomSheet
|
|
31
|
+
isOpen={isOpen}
|
|
32
|
+
onOpenChange={setIsOpen}
|
|
33
|
+
label="Place details"
|
|
34
|
+
height="hug"
|
|
35
|
+
hasScrim={false}>
|
|
36
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
37
|
+
<Heading level={3}>Central Park</Heading>
|
|
38
|
+
<Divider />
|
|
39
|
+
<Text type="body">
|
|
40
|
+
The page remains visible and interactive behind this sheet.
|
|
41
|
+
</Text>
|
|
42
|
+
<Button label="Close details" onClick={() => setIsOpen(false)} />
|
|
43
|
+
</VStack>
|
|
44
|
+
</BottomSheet>
|
|
45
|
+
</Section>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet',
|
|
8
|
+
displayName: 'Bottom Sheet',
|
|
9
|
+
description: 'A mobile filter surface that rises from the bottom edge.',
|
|
10
|
+
isReady: true,
|
|
11
|
+
isShowcase: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'CheckboxInput',
|
|
17
|
+
'Divider',
|
|
18
|
+
'Heading',
|
|
19
|
+
'Section',
|
|
20
|
+
'Stack',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
9
|
+
import {Divider} from '@astryxdesign/core/Divider';
|
|
10
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
11
|
+
import {Section} from '@astryxdesign/core/Section';
|
|
12
|
+
import {VStack} from '@astryxdesign/core/Stack';
|
|
13
|
+
|
|
14
|
+
export default function BottomSheetShowcase() {
|
|
15
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [inStock, setInStock] = useState(false);
|
|
17
|
+
const [onSale, setOnSale] = useState(false);
|
|
18
|
+
const [freeShipping, setFreeShipping] = useState(false);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<Button label="Open sheet" onClick={() => setIsOpen(true)} />
|
|
23
|
+
<BottomSheet isOpen={isOpen} onOpenChange={setIsOpen} label="Filters">
|
|
24
|
+
<Section padding={4}>
|
|
25
|
+
<VStack gap={4}>
|
|
26
|
+
<Heading level={3}>Filters</Heading>
|
|
27
|
+
<Divider />
|
|
28
|
+
<VStack gap={2}>
|
|
29
|
+
<CheckboxInput
|
|
30
|
+
label="In stock"
|
|
31
|
+
value={inStock}
|
|
32
|
+
onChange={setInStock}
|
|
33
|
+
/>
|
|
34
|
+
<CheckboxInput
|
|
35
|
+
label="On sale"
|
|
36
|
+
value={onSale}
|
|
37
|
+
onChange={setOnSale}
|
|
38
|
+
/>
|
|
39
|
+
<CheckboxInput
|
|
40
|
+
label="Free shipping"
|
|
41
|
+
value={freeShipping}
|
|
42
|
+
onChange={setFreeShipping}
|
|
43
|
+
/>
|
|
44
|
+
</VStack>
|
|
45
|
+
<Button label="Apply" onClick={() => setIsOpen(false)} />
|
|
46
|
+
</VStack>
|
|
47
|
+
</Section>
|
|
48
|
+
</BottomSheet>
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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: 'BottomSheetSwitcher',
|
|
7
|
+
name: 'Bottom Sheet Switcher',
|
|
8
|
+
displayName: 'Bottom Sheet Switcher',
|
|
9
|
+
description:
|
|
10
|
+
'A three-step flow that transitions between content-hugging sheets of different heights inside one shared dialog.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: true,
|
|
13
|
+
aspectRatio: 3 / 4,
|
|
14
|
+
componentsUsed: [
|
|
15
|
+
'BottomSheet',
|
|
16
|
+
'BottomSheetSwitcher',
|
|
17
|
+
'Button',
|
|
18
|
+
'CheckboxInput',
|
|
19
|
+
'Divider',
|
|
20
|
+
'Heading',
|
|
21
|
+
'RadioList',
|
|
22
|
+
'Section',
|
|
23
|
+
'Stack',
|
|
24
|
+
'Text',
|
|
25
|
+
],
|
|
26
|
+
};
|