@astryxdesign/cli 0.4.7 → 0.5.0-canary.009bcb3
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 +64 -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/search/search.mjs +50 -4
- package/api/search/search.test.mjs +71 -0
- package/api/template/data-token-fallbacks.test.mjs +85 -0
- package/api/template/template.doc.mjs +2 -2
- package/api/theme/build/build.mjs +43 -44
- package/api/theme/targets/targets.d.mts +18 -0
- package/api/theme/targets/targets.mjs +87 -0
- package/api/theme/targets/targets.test.mjs +66 -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/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/transforms/v0.5.0/__tests__/next-codemods.test.mjs +127 -0
- package/assets/codemods/transforms/v0.5.0/banner-collapsible-content.mjs +171 -0
- package/assets/codemods/transforms/v0.5.0/index.mjs +20 -0
- 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/Banner/BannerCollapsibleContent.doc.mjs +1 -1
- package/assets/templates/blocks/components/Banner/BannerCollapsibleContent.tsx +1 -1
- package/assets/templates/blocks/components/DateInput/DateInputClearable.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +6 -1
- package/assets/templates/blocks/components/DateInput/DateInputFormats.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputShowcase.tsx +4 -1
- package/assets/templates/blocks/components/DateInput/DateInputWithDescription.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputWithValidation.tsx +5 -1
- 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/HoverCard/HoverCardHookUsage.tsx +4 -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/dashboard-cohort-funnel/page.tsx +1 -1
- package/assets/templates/pages/dashboard-data/page.tsx +1 -1
- package/assets/templates/pages/dashboard-portfolio/page.tsx +5 -5
- package/assets/templates/pages/dashboard-service-monitoring/page.tsx +1 -1
- package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
- package/assets/templates/pages/settings-dialog/page.tsx +2543 -757
- package/assets/templates/pages/settings-dialog/template.doc.mjs +1 -2
- package/assets/templates/pages/table-filter/page.tsx +4093 -0
- package/assets/templates/pages/table-filter/template.doc.mjs +12 -0
- package/assets/templates/pages/table-grouped/page.tsx +151 -144
- package/assets/templates/pages/theme-showcase/page.tsx +36 -17
- package/assets/templates/themes/chocolate/chocolateTheme.ts +3 -1
- package/assets/templates/themes/matcha/matchaTheme.ts +3 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +16 -9
- package/assets/templates/themes/stone/stoneTheme.ts +3 -1
- package/clients/cli/commands/build-theme.color-scheme.test.mjs +7 -6
- package/clients/cli/commands/build-theme.data-tokens.test.mjs +144 -0
- package/clients/cli/commands/build-theme.mjs +85 -0
- package/clients/cli/commands/build-theme.variants.test.mjs +3 -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/component-format.mjs +2 -2
- package/clients/cli/lib/manifest.mjs +2 -0
- package/foundation/discovery/component-discovery.mjs +10 -9
- package/foundation/discovery/hook-discovery.mjs +2 -1
- package/foundation/discovery/hook-discovery.test.mjs +156 -37
- package/foundation/discovery/theming-targets.d.mts +86 -0
- package/foundation/discovery/theming-targets.mjs +202 -0
- package/foundation/discovery/theming-targets.test.mjs +245 -0
- package/foundation/fs/paths.d.mts +16 -0
- package/foundation/fs/paths.mjs +36 -0
- package/foundation/fs/paths.test.mjs +29 -1
- 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,171 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Codemod: migrate Banner's `defaultIsExpanded` onto `collapsible`
|
|
5
|
+
*
|
|
6
|
+
* Banner's collapse axis used to be a single knob, `defaultIsExpanded`, with
|
|
7
|
+
* the disclosure itself inferred from the presence of `children`. There was no
|
|
8
|
+
* way to have content without a toggle, and no controlled mode. The axis now
|
|
9
|
+
* lives on one `boolean | CollapsibleConfig` prop, per the boolean-or-config
|
|
10
|
+
* convention:
|
|
11
|
+
*
|
|
12
|
+
* <Banner>{children}</Banner> → collapsible, starts closed
|
|
13
|
+
* <Banner collapsible={{defaultIsOpen: true}}> → collapsible, starts open
|
|
14
|
+
* <Banner collapsible={{isOpen, onOpenChange}}> → controlled
|
|
15
|
+
* <Banner collapsible={false}> → always visible, no toggle
|
|
16
|
+
*
|
|
17
|
+
* The default is unchanged, so a Banner that never mentioned the old prop needs
|
|
18
|
+
* no rewrite at all — this transform is a prop rename and nothing more:
|
|
19
|
+
*
|
|
20
|
+
* defaultIsExpanded → collapsible={{defaultIsOpen: true}}
|
|
21
|
+
* defaultIsExpanded={true} → collapsible={{defaultIsOpen: true}}
|
|
22
|
+
* defaultIsExpanded={false} → (removed — it is the default)
|
|
23
|
+
* defaultIsExpanded={expr} → collapsible={{defaultIsOpen: expr}}
|
|
24
|
+
*
|
|
25
|
+
* Only elements named `Banner` are touched, and only when the file imports that
|
|
26
|
+
* name from `@astryxdesign/core`: `defaultIsExpanded` is also a ChatToolCalls
|
|
27
|
+
* prop, which this migration must leave alone.
|
|
28
|
+
*
|
|
29
|
+
* Scope: JSX attributes only. `defaultIsExpanded` inside a props object (a
|
|
30
|
+
* Storybook `args`, a spread built up in a variable) is left alone rather than
|
|
31
|
+
* rewritten on a guess about which component the object is for — removing the
|
|
32
|
+
* prop from the type makes those sites a type error, which is loud enough to
|
|
33
|
+
* find them.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export const meta = {
|
|
37
|
+
title: "Rename Banner's `defaultIsExpanded` to the `collapsible` config",
|
|
38
|
+
description:
|
|
39
|
+
"Banner's collapse axis is now a single `collapsible?: boolean | " +
|
|
40
|
+
'CollapsibleConfig` prop. Rewrites `defaultIsExpanded` to the equivalent ' +
|
|
41
|
+
'config (`{defaultIsOpen: true}`), and drops ' +
|
|
42
|
+
'`defaultIsExpanded={false}`, which is the default. Banners that never ' +
|
|
43
|
+
'set the prop are untouched — the default is unchanged. Pass ' +
|
|
44
|
+
'`collapsible={false}` for content that is always visible.',
|
|
45
|
+
pr: '#5255',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const OLD_PROP = 'defaultIsExpanded';
|
|
49
|
+
const NEW_PROP = 'collapsible';
|
|
50
|
+
const COMPONENT = 'Banner';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Does this file use the core `Banner`?
|
|
54
|
+
*
|
|
55
|
+
* `defaultIsExpanded` is a ChatToolCalls prop too, and a local component may
|
|
56
|
+
* well be called Banner, so an unqualified element-name match is not enough.
|
|
57
|
+
*
|
|
58
|
+
* @param {any} j
|
|
59
|
+
* @param {any} root
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
function importsCoreBanner(j, root) {
|
|
63
|
+
let found = false;
|
|
64
|
+
root.find(j.ImportDeclaration).forEach((/** @type {any} */ path) => {
|
|
65
|
+
const source = path.node.source?.value;
|
|
66
|
+
if (typeof source !== 'string' || !source.startsWith('@astryxdesign/core')) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const spec of path.node.specifiers ?? []) {
|
|
70
|
+
if (
|
|
71
|
+
(spec.type === 'ImportSpecifier' && spec.imported?.name === COMPONENT) ||
|
|
72
|
+
(spec.local?.name === COMPONENT &&
|
|
73
|
+
(spec.type === 'ImportDefaultSpecifier' ||
|
|
74
|
+
spec.type === 'ImportSpecifier'))
|
|
75
|
+
) {
|
|
76
|
+
found = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return found;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {import('../../../../authoring/codemod/type').AstryxCodemodFile} file
|
|
85
|
+
* @param {import('../../../../authoring/codemod/type').CodemodTransformApi} api
|
|
86
|
+
* @returns {string | null | undefined}
|
|
87
|
+
*/
|
|
88
|
+
export default function transformer(file, api) {
|
|
89
|
+
// Cheap bail-out: nothing to rename without the old prop.
|
|
90
|
+
if (!file.source.includes(OLD_PROP)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const j = api.jscodeshift;
|
|
95
|
+
const root = j(file.source);
|
|
96
|
+
|
|
97
|
+
if (!importsCoreBanner(j, root)) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let hasChanges = false;
|
|
102
|
+
|
|
103
|
+
/** `collapsible={{defaultIsOpen: <expr>}}` */
|
|
104
|
+
const collapsibleWithDefault = (/** @type {any} */ expression) =>
|
|
105
|
+
j.jsxAttribute(
|
|
106
|
+
j.jsxIdentifier(NEW_PROP),
|
|
107
|
+
j.jsxExpressionContainer(
|
|
108
|
+
j.objectExpression([
|
|
109
|
+
j.objectProperty(j.identifier('defaultIsOpen'), expression),
|
|
110
|
+
]),
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
root.find(j.JSXOpeningElement).forEach((/** @type {any} */ path) => {
|
|
115
|
+
const name = path.node.name;
|
|
116
|
+
if (name?.type !== 'JSXIdentifier' || name.name !== COMPONENT) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const attrs = path.node.attributes ?? [];
|
|
121
|
+
const oldIndex = attrs.findIndex(
|
|
122
|
+
(/** @type {any} */ a) =>
|
|
123
|
+
a.type === 'JSXAttribute' && a.name?.name === OLD_PROP,
|
|
124
|
+
);
|
|
125
|
+
if (oldIndex === -1) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Already migrated by hand, or a spread that may carry either prop:
|
|
130
|
+
// in both cases a rewrite would be guesswork.
|
|
131
|
+
const hasNewProp = attrs.some(
|
|
132
|
+
(/** @type {any} */ a) =>
|
|
133
|
+
a.type === 'JSXAttribute' && a.name?.name === NEW_PROP,
|
|
134
|
+
);
|
|
135
|
+
const hasSpread = attrs.some(
|
|
136
|
+
(/** @type {any} */ a) => a.type === 'JSXSpreadAttribute',
|
|
137
|
+
);
|
|
138
|
+
if (hasNewProp || hasSpread) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const value = attrs[oldIndex].value;
|
|
143
|
+
|
|
144
|
+
if (value == null) {
|
|
145
|
+
// Bare `defaultIsExpanded` — starts open.
|
|
146
|
+
attrs[oldIndex] = collapsibleWithDefault(j.booleanLiteral(true));
|
|
147
|
+
} else if (value.type === 'JSXExpressionContainer') {
|
|
148
|
+
const expression = value.expression;
|
|
149
|
+
const isBooleanLiteral =
|
|
150
|
+
expression.type === 'BooleanLiteral' ||
|
|
151
|
+
(expression.type === 'Literal' && typeof expression.value === 'boolean');
|
|
152
|
+
if (isBooleanLiteral && expression.value === false) {
|
|
153
|
+
// Starting closed is the default now, so the prop simply goes.
|
|
154
|
+
attrs.splice(oldIndex, 1);
|
|
155
|
+
} else if (isBooleanLiteral) {
|
|
156
|
+
attrs[oldIndex] = collapsibleWithDefault(j.booleanLiteral(true));
|
|
157
|
+
} else {
|
|
158
|
+
// A dynamic default stays dynamic.
|
|
159
|
+
attrs[oldIndex] = collapsibleWithDefault(expression);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
// `defaultIsExpanded="something"` is not valid for a boolean prop;
|
|
163
|
+
// leave it for a human rather than inventing a meaning.
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
hasChanges = true;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return hasChanges ? root.toSource({quote: 'single'}) : undefined;
|
|
171
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file next transform manifest
|
|
5
|
+
*
|
|
6
|
+
* Staged codemods for the next release. The Version Packages PR promotes
|
|
7
|
+
* this file into the resolved version folder.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import bannerCollapsibleContent, {
|
|
11
|
+
meta as bannerCollapsibleContentMeta,
|
|
12
|
+
} from './banner-collapsible-content.mjs';
|
|
13
|
+
|
|
14
|
+
export default [
|
|
15
|
+
{
|
|
16
|
+
name: 'banner-collapsible-content',
|
|
17
|
+
transform: bannerCollapsibleContent,
|
|
18
|
+
meta: bannerCollapsibleContentMeta,
|
|
19
|
+
},
|
|
20
|
+
];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# /packages/cli/assets/docs
|
|
2
|
+
|
|
3
|
+
Reference topics for people **building with** Astryx. Not docs about building Astryx itself.
|
|
4
|
+
|
|
5
|
+
One `{topic}.doc.mjs` per topic, plus optional `{topic}.doc.dense.mjs` / `{topic}.doc.zh.mjs` prose overlays. `foundation/discovery/docs-discovery.mjs` picks up any `{topic}.doc.mjs` here with no registration; `api/docs/_adapter.mjs` merges the overlays.
|
|
6
|
+
|
|
7
|
+
What you add reaches `astryx docs <topic>`, `astryx search`, the `--json` API, the agent-docs block and the doc site — and ships on npm.
|
|
8
|
+
|
|
9
|
+
## Who you are writing for
|
|
10
|
+
|
|
11
|
+
Someone building a product with Astryx. Their questions:
|
|
12
|
+
|
|
13
|
+
- what a component is for, and when to reach for something else
|
|
14
|
+
- the props, their defaults, and what each does to what they see
|
|
15
|
+
- how to compose it, and the pattern to copy
|
|
16
|
+
- what it costs — bundle size, the a11y obligations they inherit
|
|
17
|
+
- how to theme it, and which targets are stable
|
|
18
|
+
|
|
19
|
+
**The test for anything you add: does a caller act on it?** They are not reviewing a PR, promoting a component out of lab, or attaching evidence to a checklist.
|
|
20
|
+
|
|
21
|
+
## Tells that you are writing for us instead
|
|
22
|
+
|
|
23
|
+
- second person aimed at the wrong reader — "reviewers should…", "before promoting a component…", "attach evidence for…"
|
|
24
|
+
- **rubric, readiness, gate, audit, checklist, sign-off, promotion, evidence** as things the reader must produce
|
|
25
|
+
- a table of things to verify rather than things to use
|
|
26
|
+
- anything about lab → core, which is our lifecycle, not theirs
|
|
27
|
+
- Storybook, Playwright, CI or the Simulator named as tools the reader runs
|
|
28
|
+
|
|
29
|
+
One subtlety: a statement about the **system's behavior** is caller-facing even when it sounds like process. "A component's theme targets are stable once published" tells a caller what they can rely on; "reviewers must check that theme targets are stable" is ours. Same fact, different reader — **rewrite it rather than move it**.
|
|
30
|
+
|
|
31
|
+
## Where the rest goes
|
|
32
|
+
|
|
33
|
+
The material is usually good; the finding is placement, not quality. It goes in the [wiki](https://github.com/facebook/astryx/wiki) — **as a section on the page that already covers it, not a new page.** The wiki is at nearly 60 pages, several of them overlapping, because every stray section got its own.
|
|
34
|
+
|
|
35
|
+
| what you wrote | where it goes |
|
|
36
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
+
| how a component is graded — checks, scoring | [Component-Audit-Rubric](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric) |
|
|
38
|
+
| lab → core promotion, what a component must satisfy | [Component-Lifecycle](https://github.com/facebook/astryx/wiki/Component-Lifecycle) |
|
|
39
|
+
| how to build a new component | [Component-Authoring-Guide](https://github.com/facebook/astryx/wiki/Component-Authoring-Guide), [Creating-New-Components](https://github.com/facebook/astryx/wiki/Creating-New-Components) |
|
|
40
|
+
| what a component must be hardened against | [Component-Hardening-Protocol](https://github.com/facebook/astryx/wiki/Component-Hardening-Protocol), [Hardening-Audit-Guide](https://github.com/facebook/astryx/wiki/Hardening-Audit-Guide) |
|
|
41
|
+
| a11y requirements as checks we run | [Accessibility-Checklist](https://github.com/facebook/astryx/wiki/Accessibility-Checklist) |
|
|
42
|
+
| how the system is put together | [System-Architecture](https://github.com/facebook/astryx/wiki/System-Architecture), [Theming-Infrastructure](https://github.com/facebook/astryx/wiki/Theming-Infrastructure) |
|
|
43
|
+
| API naming and shape decisions | [API-Conventions](https://github.com/facebook/astryx/wiki/API-Conventions), [API-Arbitration](https://github.com/facebook/astryx/wiki/API-Arbitration) |
|
|
44
|
+
| contributor workflow, PR process | [Contributing](https://github.com/facebook/astryx/wiki/Contributing), [Contributing-with-AI-Assistants](https://github.com/facebook/astryx/wiki/Contributing-with-AI-Assistants) |
|
|
45
|
+
| release mechanics | [Release-Process](https://github.com/facebook/astryx/wiki/Release-Process) |
|
|
46
|
+
| what a nightly agent role does | the Night-Watch pages, from [Night-Watch-Overview](https://github.com/facebook/astryx/wiki/Night-Watch-Overview) |
|
|
47
|
+
|
|
48
|
+
**Fits no row?** It is still not caller-facing. Default it to [Contributing](https://github.com/facebook/astryx/wiki/Contributing), or `CONTRIBUTING.md` when it is a step someone follows with the repo cloned. Never default it back to this directory.
|
|
49
|
+
|
|
50
|
+
Worked example: a responsive-and-interaction readiness rubric is grading criteria → **Component-Audit-Rubric**, or **Component-Lifecycle** if it is a promotion gate.
|
|
@@ -115,7 +115,7 @@ export const docs = {
|
|
|
115
115
|
content: [
|
|
116
116
|
{
|
|
117
117
|
type: 'prose',
|
|
118
|
-
text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves
|
|
118
|
+
text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves: `astryx docs` lists it, `astryx docs <topic>` prints it, `astryx search` indexes it, and `astryx init` names it in the agent block. A topic is a plain object stamped `type: 'generic'`, the same shape core's own topics use.",
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
type: 'code',
|
|
@@ -124,7 +124,7 @@ export const docs = {
|
|
|
124
124
|
},
|
|
125
125
|
{
|
|
126
126
|
type: 'prose',
|
|
127
|
-
text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x
|
|
127
|
+
text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x (core's, or another integration's) so a package whose consumers install it differently can serve its own Getting Started instead of the built-in one. Give the replacement a different `name` and the old name keeps resolving to it, so a link or an agent that learned the old topic still lands in the right place.",
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
type: 'code',
|
|
@@ -133,14 +133,14 @@ export const docs = {
|
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
type: 'prose',
|
|
136
|
-
text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork
|
|
136
|
+
text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork: a fork of someone else's guide stops receiving their fixes the day you write it.",
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'list',
|
|
140
140
|
style: 'unordered',
|
|
141
141
|
items: [
|
|
142
142
|
'A topic name is a CLI argument and a docsite path, so it may hold only letters, digits, `_` and `-`.',
|
|
143
|
-
"A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override
|
|
143
|
+
"A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override; the CLI will not guess which one you meant.",
|
|
144
144
|
'`replaces` and `extends` are exclusive: a topic either takes another\'s place or merges onto it.',
|
|
145
145
|
'Two integrations replacing one topic is a warning, and the one configured later in `astryx.config` wins.',
|
|
146
146
|
],
|
|
@@ -10,7 +10,7 @@ export const docsDense = {
|
|
|
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
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
|
-
{ 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
|
|
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 theme targets [Name]` to enumerate every themeable key (--json for lint), `astryx component <Name>` for one component.' }, 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] },
|
|
16
16
|
{ section: 'Runtime vs Built Themes', title: 'Runtime vs Built', content: [{ type: 'prose', text: 'runtime: useInsertionEffect injects styles client-side. built: static CSS on first paint. USE /built + theme.css FOR SSR.' }, null, null, null] },
|
|
@@ -300,7 +300,7 @@ const brandTheme = defineTheme({
|
|
|
300
300
|
},
|
|
301
301
|
{
|
|
302
302
|
type: 'prose',
|
|
303
|
-
text: 'Run `astryx
|
|
303
|
+
text: 'Run `astryx theme targets` for every themeable key in the system (`astryx theme targets <Name>` to scope it, `--json` to lint a theme against it), and `astryx component <Name>` for one component\'s theming targets, public CSS variables, and which standard CSS properties are supported.',
|
|
304
304
|
},
|
|
305
305
|
{
|
|
306
306
|
type: 'list',
|
|
@@ -51,7 +51,7 @@ export const docs = {
|
|
|
51
51
|
content: [
|
|
52
52
|
{
|
|
53
53
|
type: 'prose',
|
|
54
|
-
text: 'Astryx never loads font files. defineTheme and the built CSS only set font-family
|
|
54
|
+
text: 'Astryx never loads font files. defineTheme and the built CSS only set font-family: naming a webfont (Fraunces, JetBrains Mono, and so on) makes every browser look for it, and quietly fall back when the app has not loaded it. `astryx theme build` warns when a theme names families that are neither CSS generics nor common system fonts and prints the snippet to add; loading the font is always the app\'s job.',
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
type: 'code',
|
|
@@ -77,7 +77,7 @@ export const docs = {
|
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
type: 'prose',
|
|
80
|
-
text: "Always pair a webfont with a real fallback stack
|
|
80
|
+
text: "Always pair a webfont with a real fallback stack (metric-similar system fonts plus a generic) so text stays readable before the font loads and wherever it never does: defineTheme({typography: {heading: {family: 'Fraunces', fallbacks: 'Georgia, serif'}}}).",
|
|
81
81
|
},
|
|
82
82
|
],
|
|
83
83
|
},
|
|
@@ -6,7 +6,7 @@ export const doc = {
|
|
|
6
6
|
exampleFor: 'Banner',
|
|
7
7
|
name: 'Banner — Collapsible',
|
|
8
8
|
displayName: 'Banner — Collapsible',
|
|
9
|
-
description: 'Combine an action button, dismiss control, and
|
|
9
|
+
description: 'Combine an action button, dismiss control, and a collapsible detail area in one banner. Children sit behind the toggle by default; `collapsible={{defaultIsOpen: true}}` starts it open, and `collapsible={false}` drops the toggle entirely. Use for complex notifications like config changes or deployment summaries.',
|
|
10
10
|
isReady: true,
|
|
11
11
|
aspectRatio: 16 / 9,
|
|
12
12
|
componentsUsed: ['Banner', 'Button', 'List', 'Layout', 'Text'],
|
|
@@ -16,7 +16,7 @@ export default function BannerCollapsibleContent() {
|
|
|
16
16
|
description="Review the changes before they take effect."
|
|
17
17
|
endContent={<Button label="Review" variant="secondary" size="sm" />}
|
|
18
18
|
isDismissable
|
|
19
|
-
|
|
19
|
+
collapsible={{defaultIsOpen: true}}>
|
|
20
20
|
<Stack direction="vertical" gap={2}>
|
|
21
21
|
<Text type="supporting" color="secondary">
|
|
22
22
|
Changed settings:
|
|
@@ -16,7 +16,11 @@ export default function DateInputClearable() {
|
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<Stack
|
|
19
|
+
<Stack
|
|
20
|
+
direction="vertical"
|
|
21
|
+
gap={4}
|
|
22
|
+
width="100%"
|
|
23
|
+
style={{minWidth: 240, maxWidth: 400}}>
|
|
20
24
|
<Text type="supporting" color="secondary">
|
|
21
25
|
{value ? `Selected: ${value}` : 'No date selected'}
|
|
22
26
|
</Text>
|
|
@@ -27,7 +27,11 @@ export default function DateInputDateRange() {
|
|
|
27
27
|
const [value, setValue] = useState<DateString | undefined>(undefined);
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<Stack
|
|
30
|
+
<Stack
|
|
31
|
+
direction="vertical"
|
|
32
|
+
gap={4}
|
|
33
|
+
width="100%"
|
|
34
|
+
style={{minWidth: 240, maxWidth: 400}}>
|
|
31
35
|
<Text type="supporting" color="secondary">
|
|
32
36
|
{value ? `Booked: ${value}` : 'Pick a date in the available range'}
|
|
33
37
|
</Text>
|
|
@@ -37,6 +41,7 @@ export default function DateInputDateRange() {
|
|
|
37
41
|
max={max}
|
|
38
42
|
description={description}
|
|
39
43
|
placeholder="Select a booking date"
|
|
44
|
+
nativePicker="never"
|
|
40
45
|
value={value}
|
|
41
46
|
onChange={setValue}
|
|
42
47
|
/>
|
|
@@ -16,7 +16,11 @@ export default function DateInputFormats() {
|
|
|
16
16
|
);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<Stack
|
|
19
|
+
<Stack
|
|
20
|
+
direction="vertical"
|
|
21
|
+
gap={4}
|
|
22
|
+
width="100%"
|
|
23
|
+
style={{minWidth: 240, maxWidth: 400}}>
|
|
20
24
|
<Text type="supporting" color="secondary">
|
|
21
25
|
The same committed date, displayed with different formats.
|
|
22
26
|
</Text>
|
|
@@ -13,7 +13,10 @@ export default function DateInputShowcase() {
|
|
|
13
13
|
const [date, setDate] = useState<DateString | undefined>(undefined);
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
|
-
<Stack
|
|
16
|
+
<Stack
|
|
17
|
+
direction="vertical"
|
|
18
|
+
width="100%"
|
|
19
|
+
style={{minWidth: 240, maxWidth: 400}}>
|
|
17
20
|
<DateInput
|
|
18
21
|
label="Start date"
|
|
19
22
|
placeholder="Select a date"
|
|
@@ -14,7 +14,11 @@ export default function DateInputWithDescription() {
|
|
|
14
14
|
const [value, setValue] = useState<DateString | undefined>(undefined);
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<Stack
|
|
17
|
+
<Stack
|
|
18
|
+
direction="vertical"
|
|
19
|
+
gap={4}
|
|
20
|
+
width="100%"
|
|
21
|
+
style={{minWidth: 240, maxWidth: 400}}>
|
|
18
22
|
<Text type="supporting" color="secondary">
|
|
19
23
|
Helper text explains what the field expects
|
|
20
24
|
</Text>
|
|
@@ -21,7 +21,11 @@ export default function DateInputWithValidation() {
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<Stack
|
|
24
|
+
<Stack
|
|
25
|
+
direction="vertical"
|
|
26
|
+
gap={4}
|
|
27
|
+
width="100%"
|
|
28
|
+
style={{minWidth: 240, maxWidth: 400}}>
|
|
25
29
|
<DateInput
|
|
26
30
|
label="Event date"
|
|
27
31
|
value={errorVal}
|
|
@@ -0,0 +1,25 @@
|
|
|
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: 'Dialog',
|
|
7
|
+
alsoExampleFor: ['BottomSheet', 'useMediaQuery'],
|
|
8
|
+
name: 'Dialog — Adaptive presentation',
|
|
9
|
+
displayName: 'Dialog — Adaptive presentation',
|
|
10
|
+
description:
|
|
11
|
+
'Opt-in recipe for an AdaptiveDialog wrapper: Dialog remains the default everywhere, while touchPresentation="bottom-sheet" switches only at lg and below when pointer is coarse and hover is unavailable. Includes a deterministic presentation override for tests/unusual environments and notes that BottomSheet purpose controls swipe and scrim dismissal. Usage examples: touchPresentation="dialog" keeps Dialog, "fullscreen" chooses fullscreen Dialog, and "bottom-sheet" chooses BottomSheet only for the touch-oriented range. Keep presentation as the deterministic override. Do not use this by default for AlertDialog or destructive confirmations.',
|
|
12
|
+
isReady: true,
|
|
13
|
+
aspectRatio: 3 / 4,
|
|
14
|
+
componentsUsed: [
|
|
15
|
+
'Dialog',
|
|
16
|
+
'DialogHeader',
|
|
17
|
+
'BottomSheet',
|
|
18
|
+
'Layout',
|
|
19
|
+
'Button',
|
|
20
|
+
'Text',
|
|
21
|
+
'TextInput',
|
|
22
|
+
'TextArea',
|
|
23
|
+
'useMediaQuery',
|
|
24
|
+
],
|
|
25
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
import {useState, type ReactNode} from 'react';
|
|
6
|
+
import {BottomSheet} from '@astryxdesign/core/BottomSheet';
|
|
7
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
type DialogPurpose,
|
|
12
|
+
} from '@astryxdesign/core/Dialog';
|
|
13
|
+
import {Heading} from '@astryxdesign/core/Heading';
|
|
14
|
+
import {
|
|
15
|
+
HStack,
|
|
16
|
+
Layout,
|
|
17
|
+
LayoutContent,
|
|
18
|
+
LayoutFooter,
|
|
19
|
+
VStack,
|
|
20
|
+
} from '@astryxdesign/core/Layout';
|
|
21
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
22
|
+
import {TextArea} from '@astryxdesign/core/TextArea';
|
|
23
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
24
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
25
|
+
|
|
26
|
+
const TOUCH_ORIENTED_LG_QUERY =
|
|
27
|
+
'(max-width: 1024px) and (pointer: coarse) and (hover: none)';
|
|
28
|
+
|
|
29
|
+
type AdaptivePresentation = 'dialog' | 'fullscreen' | 'bottom-sheet';
|
|
30
|
+
|
|
31
|
+
type AdaptiveDialogProps = {
|
|
32
|
+
isOpen: boolean;
|
|
33
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
34
|
+
title: string;
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
footer?: ReactNode;
|
|
37
|
+
purpose?: DialogPurpose;
|
|
38
|
+
width?: number | string;
|
|
39
|
+
maxHeight?: number | string;
|
|
40
|
+
touchPresentation?: AdaptivePresentation;
|
|
41
|
+
presentation?: AdaptivePresentation;
|
|
42
|
+
bottomSheetHeight?: 'hug' | 'capped' | 'tall' | number | string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function AdaptiveDialog({
|
|
46
|
+
isOpen,
|
|
47
|
+
onOpenChange,
|
|
48
|
+
title,
|
|
49
|
+
children,
|
|
50
|
+
footer,
|
|
51
|
+
purpose = 'info',
|
|
52
|
+
width = 480,
|
|
53
|
+
maxHeight = '75dvh',
|
|
54
|
+
touchPresentation = 'dialog',
|
|
55
|
+
presentation,
|
|
56
|
+
bottomSheetHeight = 'capped',
|
|
57
|
+
}: AdaptiveDialogProps) {
|
|
58
|
+
const isTouchOrientedLargeOrBelow = useMediaQuery(TOUCH_ORIENTED_LG_QUERY);
|
|
59
|
+
const resolvedPresentation =
|
|
60
|
+
presentation ??
|
|
61
|
+
(isTouchOrientedLargeOrBelow ? touchPresentation : 'dialog');
|
|
62
|
+
|
|
63
|
+
if (resolvedPresentation === 'bottom-sheet') {
|
|
64
|
+
return (
|
|
65
|
+
<BottomSheet
|
|
66
|
+
isOpen={isOpen}
|
|
67
|
+
onOpenChange={onOpenChange}
|
|
68
|
+
label={title}
|
|
69
|
+
purpose={purpose}
|
|
70
|
+
height={bottomSheetHeight}>
|
|
71
|
+
<VStack gap={4} style={{padding: 'var(--spacing-4)'}}>
|
|
72
|
+
<Heading level={3}>{title}</Heading>
|
|
73
|
+
{children}
|
|
74
|
+
{footer}
|
|
75
|
+
</VStack>
|
|
76
|
+
</BottomSheet>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dialogContent = (
|
|
81
|
+
<Layout
|
|
82
|
+
header={<DialogHeader title={title} onOpenChange={onOpenChange} />}
|
|
83
|
+
content={<LayoutContent>{children}</LayoutContent>}
|
|
84
|
+
footer={footer ? <LayoutFooter>{footer}</LayoutFooter> : undefined}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Dialog
|
|
90
|
+
isOpen={isOpen}
|
|
91
|
+
onOpenChange={onOpenChange}
|
|
92
|
+
purpose={purpose}
|
|
93
|
+
width={width}
|
|
94
|
+
maxHeight={maxHeight}
|
|
95
|
+
variant={
|
|
96
|
+
resolvedPresentation === 'fullscreen' ? 'fullscreen' : 'standard'
|
|
97
|
+
}>
|
|
98
|
+
{dialogContent}
|
|
99
|
+
</Dialog>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default function DialogAdaptivePresentation() {
|
|
104
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
105
|
+
const [name, setName] = useState('Ruby Cheung');
|
|
106
|
+
const [email, setEmail] = useState('ruby@example.com');
|
|
107
|
+
const [notes, setNotes] = useState('');
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<Button label="Edit profile" onClick={() => setIsOpen(true)} />
|
|
112
|
+
{/*
|
|
113
|
+
touchPresentation examples:
|
|
114
|
+
- "dialog" keeps Dialog even in touch-oriented <=lg contexts.
|
|
115
|
+
- "fullscreen" uses fullscreen Dialog there.
|
|
116
|
+
- "bottom-sheet" uses BottomSheet there.
|
|
117
|
+
presentation="dialog" | "fullscreen" | "bottom-sheet" overrides
|
|
118
|
+
the media query for tests and unusual environments.
|
|
119
|
+
*/}
|
|
120
|
+
<AdaptiveDialog
|
|
121
|
+
isOpen={isOpen}
|
|
122
|
+
onOpenChange={setIsOpen}
|
|
123
|
+
title="Edit profile"
|
|
124
|
+
purpose="form"
|
|
125
|
+
touchPresentation="bottom-sheet"
|
|
126
|
+
bottomSheetHeight="tall"
|
|
127
|
+
footer={
|
|
128
|
+
<HStack gap={2} hAlign="end" wrap="wrap">
|
|
129
|
+
<Button
|
|
130
|
+
label="Cancel"
|
|
131
|
+
variant="secondary"
|
|
132
|
+
onClick={() => setIsOpen(false)}
|
|
133
|
+
/>
|
|
134
|
+
<Button
|
|
135
|
+
label="Save profile"
|
|
136
|
+
variant="primary"
|
|
137
|
+
onClick={() => setIsOpen(false)}
|
|
138
|
+
/>
|
|
139
|
+
</HStack>
|
|
140
|
+
}>
|
|
141
|
+
<VStack gap={4}>
|
|
142
|
+
<Text type="supporting" color="secondary">
|
|
143
|
+
Dialog remains the default presentation. This example explicitly
|
|
144
|
+
opts into a Bottom Sheet only at lg and below when the device has a
|
|
145
|
+
coarse pointer and no hover. Pass the presentation prop to make
|
|
146
|
+
tests or unusual environments deterministic.
|
|
147
|
+
</Text>
|
|
148
|
+
<Text type="supporting" color="secondary">
|
|
149
|
+
In Bottom Sheet presentation, purpose="form" blocks scrim clicks and
|
|
150
|
+
swipe dismissal while preserving Escape. Use this opt-in only when
|
|
151
|
+
that contract is acceptable; keep AlertDialog/destructive
|
|
152
|
+
confirmations on Dialog unless a product deliberately chooses
|
|
153
|
+
otherwise.
|
|
154
|
+
</Text>
|
|
155
|
+
<TextInput label="Name" value={name} onChange={setName} />
|
|
156
|
+
<TextInput
|
|
157
|
+
label="Email"
|
|
158
|
+
type="email"
|
|
159
|
+
value={email}
|
|
160
|
+
onChange={setEmail}
|
|
161
|
+
/>
|
|
162
|
+
<TextArea label="Notes" rows={6} value={notes} onChange={setNotes} />
|
|
163
|
+
</VStack>
|
|
164
|
+
</AdaptiveDialog>
|
|
165
|
+
</>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -69,7 +69,7 @@ function Content({onClose}: {onClose: () => void}) {
|
|
|
69
69
|
|
|
70
70
|
// Remove isInline for production — dialogs should be modal.
|
|
71
71
|
export default function DialogScrollingContent() {
|
|
72
|
-
const dialog = useImperativeDialog({maxHeight: '
|
|
72
|
+
const dialog = useImperativeDialog({maxHeight: '50dvh'});
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
75
|
<>
|