@astryxdesign/cli 0.4.6 → 0.4.7-canary.20bae6c
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 +4 -0
- package/README.md +53 -51
- package/api/docs/docs.doc.mjs +2 -2
- package/api/index.d.mts +1 -1
- package/api/index.mjs +1 -1
- package/api/template/template.doc.mjs +2 -2
- package/api/theme/build/build.mjs +8 -38
- package/api/theme/targets/targets.d.mts +18 -0
- package/api/theme/targets/targets.mjs +87 -0
- package/api/theme/targets/targets.test.mjs +65 -0
- package/api/theme/theme.d.mts +1 -0
- package/api/theme/theme.mjs +3 -1
- package/api/theme/theme.type.d.mts +23 -0
- package/api/theme/theme.type.mjs +21 -1
- package/api/theme/themeTargets.doc.d.mts +11 -0
- package/api/theme/themeTargets.doc.mjs +58 -0
- package/api/theme/themeTemplate.doc.mjs +3 -3
- package/assets/docs/README.md +50 -0
- package/assets/docs/cli-integrations.doc.mjs +4 -4
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/typography.doc.mjs +2 -2
- package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.doc.mjs +25 -0
- package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.tsx +167 -0
- package/assets/templates/blocks/components/Dialog/DialogScrollingContent.tsx +1 -1
- package/assets/templates/blocks/components/Step/StepContent.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepContent.tsx +32 -0
- package/assets/templates/blocks/components/Step/StepIndicator.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepIndicator.tsx +60 -0
- package/assets/templates/blocks/components/Step/StepShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/Step/StepShowcase.tsx +26 -0
- package/assets/templates/blocks/components/Step/StepStates.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepStates.tsx +46 -0
- package/assets/templates/blocks/components/Stepper/StepperCustomContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/Stepper/StepperCustomContent.tsx +126 -0
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +1 -1
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +17 -5
- package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.tsx +25 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +2 -2
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +1 -1
- package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +1 -1
- package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +6 -7
- package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +1 -1
- package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
- package/assets/templates/themes/neutral/neutralTheme.ts +13 -8
- package/clients/cli/commands/build-theme.mjs +85 -0
- package/clients/cli/commands/dialog-adaptive-template.test.mjs +24 -0
- package/clients/cli/commands/theme-targets.behavior.test.mjs +64 -0
- package/clients/cli/commands/theme-targets.doc.mjs +38 -0
- package/clients/cli/commands/theme-template.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +4 -2
- package/clients/cli/index.mjs +1 -0
- package/clients/cli/lib/manifest.mjs +2 -0
- package/foundation/discovery/theming-targets.d.mts +48 -0
- package/foundation/discovery/theming-targets.mjs +135 -0
- package/foundation/discovery/theming-targets.test.mjs +127 -0
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +0 -24
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +0 -92
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +0 -40
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Stepper, Step} from '@astryxdesign/core/Stepper';
|
|
7
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
8
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
9
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
10
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
11
|
+
import {Badge} from '@astryxdesign/core/Badge';
|
|
12
|
+
import {Banner} from '@astryxdesign/core/Banner';
|
|
13
|
+
|
|
14
|
+
export default function StepperCustomContent() {
|
|
15
|
+
const [active, setActive] = useState(0);
|
|
16
|
+
// The content slot takes any node, not just form fields — a panel, a summary,
|
|
17
|
+
// a notice. Gating it on `active` is what makes the flow expand a step at a
|
|
18
|
+
// time.
|
|
19
|
+
return (
|
|
20
|
+
<div style={{width: 680}}>
|
|
21
|
+
<Stepper
|
|
22
|
+
activeStep={active}
|
|
23
|
+
orientation="vertical"
|
|
24
|
+
onStepClick={setActive}>
|
|
25
|
+
<Step
|
|
26
|
+
step={0}
|
|
27
|
+
label="Project details"
|
|
28
|
+
description="Name it and point us at the source"
|
|
29
|
+
indicator="number">
|
|
30
|
+
{active === 0 && (
|
|
31
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
|
|
32
|
+
<div style={{display: 'flex', gap: 12}}>
|
|
33
|
+
<TextInput
|
|
34
|
+
label="Project name"
|
|
35
|
+
placeholder="My awesome project"
|
|
36
|
+
value=""
|
|
37
|
+
width="100%"
|
|
38
|
+
/>
|
|
39
|
+
<TextInput
|
|
40
|
+
label="Repository URL"
|
|
41
|
+
placeholder="https://github.com/..."
|
|
42
|
+
value=""
|
|
43
|
+
width="100%"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
<Button
|
|
48
|
+
label="Continue"
|
|
49
|
+
variant="primary"
|
|
50
|
+
onClick={() => setActive(1)}
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</Step>
|
|
56
|
+
<Step
|
|
57
|
+
step={1}
|
|
58
|
+
label="Review the build"
|
|
59
|
+
description="What we detected from your default branch"
|
|
60
|
+
indicator="number">
|
|
61
|
+
{active === 1 && (
|
|
62
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
|
|
63
|
+
<Card variant="muted" padding={3}>
|
|
64
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 8}}>
|
|
65
|
+
<div
|
|
66
|
+
style={{
|
|
67
|
+
display: 'flex',
|
|
68
|
+
alignItems: 'center',
|
|
69
|
+
gap: 8,
|
|
70
|
+
}}>
|
|
71
|
+
<Text type="label">Next.js 15</Text>
|
|
72
|
+
<Badge variant="success" label="Detected" />
|
|
73
|
+
</div>
|
|
74
|
+
<Text type="supporting">
|
|
75
|
+
Build command <code>next build</code> · Output{' '}
|
|
76
|
+
<code>.next</code> · Node 20
|
|
77
|
+
</Text>
|
|
78
|
+
</div>
|
|
79
|
+
</Card>
|
|
80
|
+
<div style={{display: 'flex', gap: 8}}>
|
|
81
|
+
<Button
|
|
82
|
+
label="Back"
|
|
83
|
+
variant="secondary"
|
|
84
|
+
onClick={() => setActive(0)}
|
|
85
|
+
/>
|
|
86
|
+
<Button
|
|
87
|
+
label="Looks right"
|
|
88
|
+
variant="primary"
|
|
89
|
+
onClick={() => setActive(2)}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</Step>
|
|
95
|
+
<Step
|
|
96
|
+
step={2}
|
|
97
|
+
label="Deploy"
|
|
98
|
+
description="Ships to production"
|
|
99
|
+
indicator="number">
|
|
100
|
+
{active === 2 && (
|
|
101
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
|
|
102
|
+
<Banner
|
|
103
|
+
status="info"
|
|
104
|
+
title="First deploy takes a few minutes"
|
|
105
|
+
description="Later deploys reuse the build cache and finish in under a minute."
|
|
106
|
+
/>
|
|
107
|
+
<div style={{display: 'flex', gap: 8}}>
|
|
108
|
+
<Button
|
|
109
|
+
label="Back"
|
|
110
|
+
variant="secondary"
|
|
111
|
+
onClick={() => setActive(1)}
|
|
112
|
+
/>
|
|
113
|
+
<Button
|
|
114
|
+
label="Deploy now"
|
|
115
|
+
variant="primary"
|
|
116
|
+
onClick={() => setActive(3)}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</Step>
|
|
122
|
+
<Step step={3} label="Live" indicator="number" />
|
|
123
|
+
</Stepper>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -7,7 +7,7 @@ export const doc = {
|
|
|
7
7
|
name: 'Stepper — Indicator Modes',
|
|
8
8
|
displayName: 'Stepper — Indicator Modes',
|
|
9
9
|
description:
|
|
10
|
-
'The indicator prop side by side: auto (check when done, ring when current, number ahead), always-number, and a custom icon per step.',
|
|
10
|
+
'The indicator prop side by side: none (the bar and label carry the progress on their own), auto (check when done, ring when current, number ahead), always-number, and a custom icon per step.',
|
|
11
11
|
isReady: true,
|
|
12
12
|
aspectRatio: 4 / 3,
|
|
13
13
|
componentsUsed: ['Stepper', 'Text', 'Icon'],
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
5
|
import {useState} from 'react';
|
|
6
|
-
import {Stepper, Step} from '@astryxdesign/
|
|
6
|
+
import {Stepper, Step} from '@astryxdesign/core/Stepper';
|
|
7
7
|
import {Text} from '@astryxdesign/core/Text';
|
|
8
8
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
9
9
|
|
|
@@ -11,6 +11,18 @@ export default function StepperIndicatorModes() {
|
|
|
11
11
|
const [active, setActive] = useState(2);
|
|
12
12
|
return (
|
|
13
13
|
<div style={{display: 'flex', gap: 48, flexWrap: 'wrap'}}>
|
|
14
|
+
<div style={{maxWidth: 220}}>
|
|
15
|
+
<Text type="label">None</Text>
|
|
16
|
+
<Stepper
|
|
17
|
+
activeStep={active}
|
|
18
|
+
orientation="vertical"
|
|
19
|
+
onStepClick={setActive}>
|
|
20
|
+
<Step step={0} label="Account" indicator="none" />
|
|
21
|
+
<Step step={1} label="Profile" indicator="none" />
|
|
22
|
+
<Step step={2} label="Settings" indicator="none" />
|
|
23
|
+
<Step step={3} label="Review" indicator="none" />
|
|
24
|
+
</Stepper>
|
|
25
|
+
</div>
|
|
14
26
|
<div style={{maxWidth: 220}}>
|
|
15
27
|
<Text type="label">Auto</Text>
|
|
16
28
|
<Stepper
|
|
@@ -44,22 +56,22 @@ export default function StepperIndicatorModes() {
|
|
|
44
56
|
<Step
|
|
45
57
|
step={0}
|
|
46
58
|
label="Account"
|
|
47
|
-
|
|
59
|
+
indicator={<Icon icon="info" size="sm" />}
|
|
48
60
|
/>
|
|
49
61
|
<Step
|
|
50
62
|
step={1}
|
|
51
63
|
label="Profile"
|
|
52
|
-
|
|
64
|
+
indicator={<Icon icon="search" size="sm" />}
|
|
53
65
|
/>
|
|
54
66
|
<Step
|
|
55
67
|
step={2}
|
|
56
68
|
label="Settings"
|
|
57
|
-
|
|
69
|
+
indicator={<Icon icon="wrench" size="sm" />}
|
|
58
70
|
/>
|
|
59
71
|
<Step
|
|
60
72
|
step={3}
|
|
61
73
|
label="Review"
|
|
62
|
-
|
|
74
|
+
indicator={<Icon icon="check" size="sm" />}
|
|
63
75
|
/>
|
|
64
76
|
</Stepper>
|
|
65
77
|
</div>
|
|
@@ -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: 'Stepper',
|
|
7
|
+
name: 'Stepper — On-Track Horizontal',
|
|
8
|
+
displayName: 'Stepper — On-Track Horizontal',
|
|
9
|
+
description:
|
|
10
|
+
'The on-track layout in horizontal orientation: instead of sitting beside the label, each indicator is slotted into the connector itself, so the numbered nodes read as beads on one continuous line. Labels center under their node.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 16 / 9,
|
|
13
|
+
componentsUsed: ['Stepper'],
|
|
14
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState} from 'react';
|
|
6
|
+
import {Stepper, Step} from '@astryxdesign/core/Stepper';
|
|
7
|
+
|
|
8
|
+
export default function StepperOnTrackHorizontal() {
|
|
9
|
+
const [active, setActive] = useState(2);
|
|
10
|
+
return (
|
|
11
|
+
<div style={{width: 560}}>
|
|
12
|
+
<Stepper
|
|
13
|
+
activeStep={active}
|
|
14
|
+
orientation="horizontal"
|
|
15
|
+
indicatorPosition="on-track"
|
|
16
|
+
onStepClick={setActive}>
|
|
17
|
+
<Step step={0} label="Workspace" indicator="number" />
|
|
18
|
+
<Step step={1} label="Team" indicator="number" />
|
|
19
|
+
<Step step={2} label="Integrations" indicator="number" />
|
|
20
|
+
<Step step={3} label="Import" indicator="number" />
|
|
21
|
+
<Step step={4} label="Launch" indicator="number" />
|
|
22
|
+
</Stepper>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -7,8 +7,8 @@ export const doc = {
|
|
|
7
7
|
name: 'Stepper — On-Track Vertical',
|
|
8
8
|
displayName: 'Stepper — On-Track Vertical',
|
|
9
9
|
description:
|
|
10
|
-
'The on-track layout in vertical orientation: indicators sit inline on a continuous connector rail, with each label and description beside its node.',
|
|
10
|
+
'The on-track layout in vertical orientation: indicators sit inline on a continuous connector rail, with each label and description beside its node. Compare with the separated layout, where the indicator sits alongside its own bar segment instead of on a shared rail.',
|
|
11
11
|
isReady: true,
|
|
12
12
|
aspectRatio: 4 / 3,
|
|
13
|
-
componentsUsed: ['Stepper'],
|
|
13
|
+
componentsUsed: ['Stepper', 'Step'],
|
|
14
14
|
};
|
|
@@ -7,7 +7,7 @@ export const doc = {
|
|
|
7
7
|
name: 'Stepper — Checkout Progress',
|
|
8
8
|
displayName: 'Stepper — Checkout Progress',
|
|
9
9
|
description:
|
|
10
|
-
'
|
|
10
|
+
'The default stepper: a horizontal track where every step owns an equal segment of the progress bar above its label. The default auto indicator resolves itself per step — a check once the step is done, a ring on the current step, a number for the ones still ahead. Click any step to jump.',
|
|
11
11
|
isReady: true,
|
|
12
12
|
isShowcase: true,
|
|
13
13
|
aspectRatio: 16 / 9,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
5
|
import {useState} from 'react';
|
|
6
|
-
import {Stepper, Step} from '@astryxdesign/
|
|
6
|
+
import {Stepper, Step} from '@astryxdesign/core/Stepper';
|
|
7
7
|
|
|
8
8
|
export default function StepperShowcase() {
|
|
9
9
|
const [active, setActive] = useState(2);
|
|
@@ -12,13 +12,12 @@ export default function StepperShowcase() {
|
|
|
12
12
|
<Stepper
|
|
13
13
|
activeStep={active}
|
|
14
14
|
orientation="horizontal"
|
|
15
|
-
indicatorPosition="on-track"
|
|
16
15
|
onStepClick={setActive}>
|
|
17
|
-
<Step step={0} label="Cart"
|
|
18
|
-
<Step step={1} label="Shipping"
|
|
19
|
-
<Step step={2} label="Payment"
|
|
20
|
-
<Step step={3} label="Review"
|
|
21
|
-
<Step step={4} label="Confirm"
|
|
16
|
+
<Step step={0} label="Cart" />
|
|
17
|
+
<Step step={1} label="Shipping" />
|
|
18
|
+
<Step step={2} label="Payment" />
|
|
19
|
+
<Step step={3} label="Review" />
|
|
20
|
+
<Step step={4} label="Confirm" />
|
|
22
21
|
</Stepper>
|
|
23
22
|
</div>
|
|
24
23
|
);
|
|
@@ -36,7 +36,11 @@ const clipStyle: CSSProperties = {
|
|
|
36
36
|
// 3 columns on desktop, dropping straight to 1 column below 720px (no 2-col
|
|
37
37
|
// middle state). minmax(0, 1fr) (not 1fr) so tracks split evenly and ignore the
|
|
38
38
|
// images' intrinsic min-width. The hero spans 2 columns on desktop, then fills
|
|
39
|
-
// the row once it's single-column
|
|
39
|
+
// the row once it's single-column — and switches from 3:1 to 3:2 with a plain
|
|
40
|
+
// aspect-ratio override. AspectRatio's ratio is a class-level declaration in
|
|
41
|
+
// @layer astryx-base, and this <style> tag is unlayered, so the override wins
|
|
42
|
+
// regardless of specificity — every stacked tile is uniform without rendering
|
|
43
|
+
// the hero twice.
|
|
40
44
|
const GALLERY_CSS = `
|
|
41
45
|
.mixed-gallery-grid {
|
|
42
46
|
display: grid;
|
|
@@ -52,6 +56,7 @@ const GALLERY_CSS = `
|
|
|
52
56
|
}
|
|
53
57
|
.mixed-gallery-hero {
|
|
54
58
|
grid-column: 1 / -1;
|
|
59
|
+
aspect-ratio: 3 / 2;
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
`;
|
|
@@ -134,9 +139,13 @@ export default function MixedGalleryTemplate() {
|
|
|
134
139
|
of three. Every tile is 3:2 except the hero, which is 3:1 so that
|
|
135
140
|
(being 2 columns wide) it matches the row height exactly. All
|
|
136
141
|
rows are therefore the same height. Responsive via @container:
|
|
137
|
-
3 columns → 1 column at ≤720px
|
|
142
|
+
3 columns → 1 column at ≤720px, where the hero drops to 3:2 so
|
|
143
|
+
the stacked tiles are uniform. */}
|
|
138
144
|
<div className="mixed-gallery-grid">
|
|
139
|
-
{/* Hero — spans 2 columns; 3:1 keeps it level with the sidebar
|
|
145
|
+
{/* Hero — spans 2 columns; 3:1 keeps it level with the sidebar.
|
|
146
|
+
Stacked, the unlayered aspect-ratio override switches it to
|
|
147
|
+
3:2 (see GALLERY_CSS) — one element, no duplicate hero
|
|
148
|
+
markup. */}
|
|
140
149
|
<GalleryCard
|
|
141
150
|
image={IMAGES[0]}
|
|
142
151
|
ratio={3 / 1}
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* icon = T30 / T80
|
|
25
25
|
* text = T30 / T80
|
|
26
26
|
*
|
|
27
|
-
* All 9 saturated badge values pass WCAG AA (5
|
|
27
|
+
* All 9 saturated badge values pass WCAG AA against their label (>= 4.5:1);
|
|
28
|
+
* `scripts/check-badge-contrast.test.mjs` holds every theme to that.
|
|
28
29
|
*
|
|
29
30
|
* Only overrides tokens that differ from the defaults.
|
|
30
31
|
*/
|
|
@@ -432,10 +433,14 @@ export const neutralTheme = defineTheme({
|
|
|
432
433
|
color: '#171717',
|
|
433
434
|
},
|
|
434
435
|
'variant:error': {
|
|
435
|
-
// Light: T55 #e33f4a
|
|
436
|
+
// Light: T58 #c9303a. The T55 stop #e33f4a pairs with white at only
|
|
437
|
+
// 4.14:1 — the label is 12px/500, so AA wants 4.5, not the 3:1
|
|
438
|
+
// large-text allowance. One tonal step down holds the hue
|
|
439
|
+
// (OKLCH H 21.9 -> 22.8, C 0.200 -> 0.189) and reaches 5.29:1.
|
|
436
440
|
// Dark : T60 stop from dark-mode tonal palette of Tailwind red-600
|
|
437
|
-
// source #dc2626 (kept on H=27 alarm-red rather than coral)
|
|
438
|
-
|
|
441
|
+
// source #dc2626 (kept on H=27 alarm-red rather than coral).
|
|
442
|
+
// Dark text on it is 6.60:1 and unchanged.
|
|
443
|
+
backgroundColor: 'light-dark(#c9303a, #ff705d)',
|
|
439
444
|
color: 'light-dark(#ffffff, #171717)',
|
|
440
445
|
},
|
|
441
446
|
|
|
@@ -500,7 +505,7 @@ export const neutralTheme = defineTheme({
|
|
|
500
505
|
//
|
|
501
506
|
// success → badge success bg (green T45 / dark-ramp T60)
|
|
502
507
|
// warning → badge warning bg (yellow T85, same hex both modes)
|
|
503
|
-
// error → badge error bg (red
|
|
508
|
+
// error → badge error bg (red T58 / dark-ramp T60)
|
|
504
509
|
// accent → badge info bg (blue T50 / dark-ramp T60) — the
|
|
505
510
|
// StatusDot "accent" is the info/attention color, so it
|
|
506
511
|
// pairs with the info badge rather than --color-accent
|
|
@@ -515,7 +520,7 @@ export const neutralTheme = defineTheme({
|
|
|
515
520
|
statusdot: {
|
|
516
521
|
'variant:success': {backgroundColor: 'light-dark(#198100, #64af4c)'},
|
|
517
522
|
'variant:warning': {backgroundColor: '#ffce2f'},
|
|
518
|
-
'variant:error': {backgroundColor: 'light-dark(#
|
|
523
|
+
'variant:error': {backgroundColor: 'light-dark(#c9303a, #ff705d)'},
|
|
519
524
|
'variant:accent': {backgroundColor: 'light-dark(#0074e2, #6d9cfe)'},
|
|
520
525
|
},
|
|
521
526
|
|
|
@@ -611,8 +616,8 @@ export const neutralTheme = defineTheme({
|
|
|
611
616
|
'--color-warning': '#ffce2f',
|
|
612
617
|
},
|
|
613
618
|
'variant:error': {
|
|
614
|
-
// Red
|
|
615
|
-
'--color-error': '#
|
|
619
|
+
// Red T58 saturated stop (= variant:error badge bg)
|
|
620
|
+
'--color-error': '#c9303a',
|
|
616
621
|
},
|
|
617
622
|
},
|
|
618
623
|
|
|
@@ -38,6 +38,7 @@ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
|
38
38
|
import {themeAdd} from '../../../api/theme/add/add.mjs';
|
|
39
39
|
import {themeTemplate} from '../../../api/theme/template/template.mjs';
|
|
40
40
|
import {themeList} from '../../../api/theme/list/list.mjs';
|
|
41
|
+
import {themeTargets} from '../../../api/theme/targets/targets.mjs';
|
|
41
42
|
import {themeBuild, importSpecifier} from '../../../api/theme/build/build.mjs';
|
|
42
43
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
43
44
|
import {doc as themeGroup} from './theme.doc.mjs';
|
|
@@ -45,10 +46,12 @@ import {doc as themeBuildCommand} from './theme-build.doc.mjs';
|
|
|
45
46
|
import {doc as themeListCommand} from './theme-list.doc.mjs';
|
|
46
47
|
import {doc as themeAddCommand} from './theme-add.doc.mjs';
|
|
47
48
|
import {doc as themeTemplateCommand} from './theme-template.doc.mjs';
|
|
49
|
+
import {doc as themeTargetsCommand} from './theme-targets.doc.mjs';
|
|
48
50
|
import {doc as themeBuildFn} from '../../../api/theme/themeBuild.doc.mjs';
|
|
49
51
|
import {doc as themeListFn} from '../../../api/theme/themeList.doc.mjs';
|
|
50
52
|
import {doc as themeAddFn} from '../../../api/theme/themeAdd.doc.mjs';
|
|
51
53
|
import {doc as themeTemplateFn} from '../../../api/theme/themeTemplate.doc.mjs';
|
|
54
|
+
import {doc as themeTargetsFn} from '../../../api/theme/themeTargets.doc.mjs';
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Path to this CLI's real entry (clients/cli/bin/astryx.mjs), resolved from
|
|
@@ -194,6 +197,36 @@ function printThemeList(themes) {
|
|
|
194
197
|
);
|
|
195
198
|
}
|
|
196
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Render the targets as one greppable line each, under an aligned header. A
|
|
202
|
+
* `records()` block would be five lines per target — over a thousand for the
|
|
203
|
+
* full surface, which is the view this command exists to make readable.
|
|
204
|
+
* @param {import('../../../api/theme/theme.type.mjs').ThemeTargetEntry[]} targets
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
function formatTargetsTable(targets) {
|
|
208
|
+
const rows = targets.map(t => ({
|
|
209
|
+
key: t.key,
|
|
210
|
+
component: t.component,
|
|
211
|
+
props: t.props.join(', ') || '-',
|
|
212
|
+
states: t.states.join(', ') || '-',
|
|
213
|
+
}));
|
|
214
|
+
const head = {key: 'key', component: 'component', props: 'props', states: 'states'};
|
|
215
|
+
const width = (/** @type {'key'|'component'|'props'} */ field) =>
|
|
216
|
+
[head, ...rows].reduce((max, r) => Math.max(max, r[field].length), 0);
|
|
217
|
+
const w = {key: width('key'), component: width('component'), props: width('props')};
|
|
218
|
+
const line = (/** @type {typeof head} */ r) =>
|
|
219
|
+
[
|
|
220
|
+
r.key.padEnd(w.key),
|
|
221
|
+
r.component.padEnd(w.component),
|
|
222
|
+
r.props.padEnd(w.props),
|
|
223
|
+
r.states,
|
|
224
|
+
]
|
|
225
|
+
.join(' ')
|
|
226
|
+
.trimEnd();
|
|
227
|
+
return [line(head), ...rows.map(line)].join('\n');
|
|
228
|
+
}
|
|
229
|
+
|
|
197
230
|
/**
|
|
198
231
|
* @param {import('commander').Command} program
|
|
199
232
|
*/
|
|
@@ -225,6 +258,18 @@ export function registerTheme(program) {
|
|
|
225
258
|
},
|
|
226
259
|
});
|
|
227
260
|
|
|
261
|
+
// Theming questions are asked at `theme`, but per-component overrides live
|
|
262
|
+
// under `component`. Without this pointer the group reads as a build-tool
|
|
263
|
+
// menu, and the component targets are unreachable from the noun the user
|
|
264
|
+
// started at.
|
|
265
|
+
theme.addHelpText(
|
|
266
|
+
'after',
|
|
267
|
+
`\nComponent style overrides:\n` +
|
|
268
|
+
` ${getCliInvocation()} theme targets Every themeable class, with its props and states\n` +
|
|
269
|
+
` ${getCliInvocation()} component <Name> One component's theming table\n` +
|
|
270
|
+
` ${getCliInvocation()} docs theme How component overrides work\n`,
|
|
271
|
+
);
|
|
272
|
+
|
|
228
273
|
defineCommand(theme, themeBuildCommand, {
|
|
229
274
|
fn: themeBuildFn,
|
|
230
275
|
action: async (
|
|
@@ -502,4 +547,44 @@ export function registerTheme(program) {
|
|
|
502
547
|
);
|
|
503
548
|
},
|
|
504
549
|
});
|
|
550
|
+
|
|
551
|
+
defineCommand(theme, themeTargetsCommand, {
|
|
552
|
+
fn: themeTargetsFn,
|
|
553
|
+
action: async (/** @type {string | undefined} */ filter) => {
|
|
554
|
+
const json = program.opts().json || false;
|
|
555
|
+
|
|
556
|
+
/** @type {import('../../../api/theme/theme.type.mjs').ThemeTargetsResponse} */
|
|
557
|
+
let result;
|
|
558
|
+
try {
|
|
559
|
+
result = await themeTargets(filter, {cwd: process.cwd()});
|
|
560
|
+
} catch (e) {
|
|
561
|
+
const err =
|
|
562
|
+
/** @type {import('../../../api/error.mjs').AstryxError} */ (e);
|
|
563
|
+
cliError(err.message, {
|
|
564
|
+
suggestions: err.suggestions || [],
|
|
565
|
+
code: err.code,
|
|
566
|
+
});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (json) return jsonOut(result);
|
|
571
|
+
|
|
572
|
+
const run = getCliInvocation();
|
|
573
|
+
const {targets, componentCount} = result.data;
|
|
574
|
+
emit(
|
|
575
|
+
section(
|
|
576
|
+
'Theming targets',
|
|
577
|
+
`${targets.length} across ${componentCount} component${componentCount === 1 ? '' : 's'}`,
|
|
578
|
+
),
|
|
579
|
+
text(formatTargetsTable(targets)),
|
|
580
|
+
text(
|
|
581
|
+
[
|
|
582
|
+
`Each key goes under \`components\` in defineTheme; it paints \`.astryx-<key>\`.`,
|
|
583
|
+
`Props take a value (\`variant:secondary\`); states are written bare (\`checked\`).`,
|
|
584
|
+
`One component in full: ${run} component <Name>`,
|
|
585
|
+
].join('\n'),
|
|
586
|
+
),
|
|
587
|
+
);
|
|
588
|
+
},
|
|
589
|
+
});
|
|
505
590
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest';
|
|
4
|
+
import {component} from '../../../api/component/component.mjs';
|
|
5
|
+
|
|
6
|
+
const CWD = {cwd: '.'};
|
|
7
|
+
|
|
8
|
+
// Resolves built-in block templates by walking the source tree.
|
|
9
|
+
const SCAN_TIMEOUT = 30_000;
|
|
10
|
+
|
|
11
|
+
describe(
|
|
12
|
+
'Dialog adaptive presentation template',
|
|
13
|
+
() => {
|
|
14
|
+
it('is listed as a Dialog example block', async () => {
|
|
15
|
+
const result = await component('Dialog', {
|
|
16
|
+
...CWD,
|
|
17
|
+
blocks: true,
|
|
18
|
+
});
|
|
19
|
+
const exampleNames = result.data.examples.map(b => b.name);
|
|
20
|
+
expect(exampleNames).toContain('DialogAdaptivePresentation');
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
SCAN_TIMEOUT,
|
|
24
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CLI behavior for `astryx theme targets`.
|
|
5
|
+
*
|
|
6
|
+
* The API leaf is covered by api/theme/targets/targets.test.mjs; what is only
|
|
7
|
+
* reachable here is the terminal binding — the table a human reads, the JSON
|
|
8
|
+
* envelope a lint script reads, and the route into component overrides from
|
|
9
|
+
* `theme --help`, which is where the question gets asked.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
14
|
+
|
|
15
|
+
describe('astryx theme targets', () => {
|
|
16
|
+
it('prints one greppable line per target, with props and states', async () => {
|
|
17
|
+
const {status, stdout} = await runCli(['theme', 'targets', 'Switch']);
|
|
18
|
+
|
|
19
|
+
expect(status).toBe(0);
|
|
20
|
+
expect(stdout).toMatch(/^switch\s+Switch\s+size\s+checked, disabled$/m);
|
|
21
|
+
expect(stdout).toMatch(/^switch-thumb\s+Switch\s+size\s+checked$/m);
|
|
22
|
+
expect(stdout).toMatch(/3 across 1 component/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('lists the whole surface when unfiltered', async () => {
|
|
26
|
+
const {status, stdout} = await runCli(['theme', 'targets']);
|
|
27
|
+
|
|
28
|
+
expect(status).toBe(0);
|
|
29
|
+
const rows = stdout.split('\n').filter(l => /^[a-z][a-z0-9-]*\s{2,}/.test(l));
|
|
30
|
+
expect(rows.length).toBeGreaterThan(100);
|
|
31
|
+
expect(stdout).toMatch(/^button\s/m);
|
|
32
|
+
expect(stdout).toMatch(/^switch-thumb\s/m);
|
|
33
|
+
}, 30_000);
|
|
34
|
+
|
|
35
|
+
it('returns a theme.targets envelope under --json', async () => {
|
|
36
|
+
const {status, stdout} = await runCli(['--json', 'theme', 'targets', 'Switch']);
|
|
37
|
+
|
|
38
|
+
expect(status).toBe(0);
|
|
39
|
+
const payload = JSON.parse(stdout);
|
|
40
|
+
expect(payload.type).toBe('theme.targets');
|
|
41
|
+
expect(payload.data.targets).toContainEqual({
|
|
42
|
+
key: 'switch-thumb',
|
|
43
|
+
className: 'astryx-switch-thumb',
|
|
44
|
+
component: 'Switch',
|
|
45
|
+
props: ['size'],
|
|
46
|
+
states: ['checked'],
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('fails a filter that matches nothing', async () => {
|
|
51
|
+
const {status, stderr} = await runCli(['theme', 'targets', 'nosuchthing']);
|
|
52
|
+
|
|
53
|
+
expect(status).toBe(1);
|
|
54
|
+
expect(stderr).toMatch(/No theming target matches "nosuchthing"/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('routes a theming question from `theme --help` to component overrides', async () => {
|
|
58
|
+
const {stdout} = await runCli(['theme', '--help']);
|
|
59
|
+
|
|
60
|
+
expect(stdout).toMatch(/Component style overrides:/);
|
|
61
|
+
expect(stdout).toMatch(/theme targets/);
|
|
62
|
+
expect(stdout).toMatch(/component <Name>/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CommandDoc for `astryx theme targets`. The terminal binding of the
|
|
5
|
+
* `themeTargets()` function (referenced via `fn`); its args map to that
|
|
6
|
+
* function's params so a converter can build Commander config + --help from one
|
|
7
|
+
* source of truth.
|
|
8
|
+
* @position packages/cli/clients/cli/commands — command documentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @type {import('@astryxdesign/cli/authoring').CommandDoc} */
|
|
12
|
+
export const doc = {
|
|
13
|
+
type: 'command',
|
|
14
|
+
name: 'theme targets',
|
|
15
|
+
displayName: 'astryx theme targets',
|
|
16
|
+
namespace: 'cli',
|
|
17
|
+
summary: 'List the component theming targets a theme can override',
|
|
18
|
+
description:
|
|
19
|
+
'Prints every `defineTheme` components key across the system: the stable class it paints, ' +
|
|
20
|
+
'the component that declares it, and the props and states that are legal override keys ' +
|
|
21
|
+
'under it. This is the whole themeable surface in one command — what auditing a theme, or ' +
|
|
22
|
+
'answering "which key paints this pixel?", used to need one `astryx component <Name>` per ' +
|
|
23
|
+
'component to assemble. Pass a component name to scope it; pass any substring to search ' +
|
|
24
|
+
'keys. `--json` for a list a repo can lint its own theme against.',
|
|
25
|
+
fn: 'themeTargets',
|
|
26
|
+
args: [{name: 'filter', param: 'filter', required: false}],
|
|
27
|
+
examples: [
|
|
28
|
+
{label: 'The whole themeable surface', cli: 'astryx theme targets'},
|
|
29
|
+
{label: "One component's targets", cli: 'astryx theme targets Switch'},
|
|
30
|
+
{label: 'Search keys', cli: 'astryx theme targets thumb'},
|
|
31
|
+
{label: 'For a lint or an audit script', cli: 'astryx --json theme targets'},
|
|
32
|
+
],
|
|
33
|
+
exitCodes: [
|
|
34
|
+
{code: 0, when: 'success'},
|
|
35
|
+
{code: 1, when: 'a filter matches no target, or core cannot be resolved'},
|
|
36
|
+
],
|
|
37
|
+
related: ['component', 'theme build', 'theme template'],
|
|
38
|
+
};
|
|
@@ -16,9 +16,9 @@ export const doc = {
|
|
|
16
16
|
namespace: 'cli',
|
|
17
17
|
summary: 'Write the annotated theme template into your project',
|
|
18
18
|
description:
|
|
19
|
-
'Writes theme.template.ts: the annotated reference for the whole theme surface
|
|
19
|
+
'Writes theme.template.ts: the annotated reference for the whole theme surface, covering every ' +
|
|
20
20
|
'defineTheme field, the token families, the component override syntax, and how a theme is ' +
|
|
21
|
-
'consumed
|
|
21
|
+
'consumed, naming the CLI command that prints the authoritative reference for each. Read ' +
|
|
22
22
|
'it, copy what you need into your own theme file, delete it. Use `theme add <slug>` instead ' +
|
|
23
23
|
'to start from a theme we ship. Leaves an existing file untouched unless --overwrite.',
|
|
24
24
|
fn: 'themeTemplate',
|