@astryxdesign/cli 0.0.15 → 0.1.0-canary.3047a7c
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 +88 -0
- package/README.md +1 -1
- package/docs/getting-started.doc.mjs +4 -4
- package/docs/migration.doc.mjs +4 -4
- package/docs/styling-libraries.doc.mjs +3 -3
- package/docs/styling.doc.mjs +1 -1
- package/docs/theme.doc.dense.mjs +1 -1
- package/docs/theme.doc.mjs +39 -19
- package/docs/theme.doc.zh.mjs +1 -1
- package/package.json +9 -13
- package/src/api/doctor.mjs +4 -4
- package/src/commands/agent-docs.mjs +27 -10
- package/src/commands/agent-docs.test.mjs +21 -21
- package/src/commands/doctor.test.mjs +3 -3
- package/src/commands/init.mjs +2 -2
- package/templates/pages/form-two-column/page.tsx +13 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @xds/cli
|
|
2
2
|
|
|
3
|
+
# 0.1.0
|
|
4
|
+
|
|
5
|
+
#### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- Read project config from `astryx.config.mjs` (was `xds.config.mjs`)
|
|
8
|
+
The CLI now resolves its optional project config from `astryx.config.mjs`
|
|
9
|
+
instead of `xds.config.mjs` — a hard cut, no fallback. Consumers with an
|
|
10
|
+
`xds.config.mjs` must rename it to `astryx.config.mjs` (the config shape and
|
|
11
|
+
all fields are unchanged). Part of removing `xds` naming from the public API.
|
|
12
|
+
- Rename the CLI command/bin from `xds` to `astryx`
|
|
13
|
+
The CLI binary is now `astryx` (was `xds`); `bin/xds.mjs` is renamed to
|
|
14
|
+
`bin/astryx.mjs`, the dual `xds`+`astryx` bin entries collapse to a single
|
|
15
|
+
`astryx`, and the program/manifest name is `astryx`. Invoke the CLI as
|
|
16
|
+
`npx astryx <command>` (e.g. `npx astryx component Button`). The swizzle
|
|
17
|
+
default output dir moves from `./components/xds` to `./components/astryx`.
|
|
18
|
+
Consumers using `npx xds`, an `xds` npm-script alias, or the `xds` MCP server
|
|
19
|
+
name should switch to `astryx`. Part of removing `xds` naming from the public API.
|
|
20
|
+
- Rename the exported `XDSError` class to `AstryxError`
|
|
21
|
+
The CLI's programmatic API error class is renamed `XDSError` -> `AstryxError`
|
|
22
|
+
(exported from `@xds/cli` + declared in its types). Consumers that catch or
|
|
23
|
+
reference `XDSError` from the CLI's API should switch to `AstryxError`. Part of
|
|
24
|
+
removing `xds` naming from the public API.
|
|
25
|
+
- Remove the XDS-prefix compatibility layer — astryx is now the only public surface
|
|
26
|
+
This release erases all `xds` naming from the public API; there is no compatibility
|
|
27
|
+
window. Consumers must migrate (we own all consumers pre-OSS):
|
|
28
|
+
- Remove the daily, brutalist, and default themes; neutral is the new baseline
|
|
29
|
+
Three theme packages are removed from the repo and will no longer be published:
|
|
30
|
+
|
|
31
|
+
#### Fixes
|
|
32
|
+
|
|
33
|
+
- `theme build` generates valid bare type imports (IconRegistry/DefinedTheme)
|
|
34
|
+
`astryx theme build` emitted `.d.ts` files importing `XDSIconRegistry` /
|
|
35
|
+
`XDSDefinedTheme` from `@xds/core`, but those aliases were removed — the
|
|
36
|
+
generated types failed to resolve. Generate `IconRegistry` / `DefinedTheme`
|
|
37
|
+
(the bare names `@xds/core` now exports) instead.
|
|
38
|
+
|
|
39
|
+
#### Documentation
|
|
40
|
+
|
|
41
|
+
- Update CLI theme docs to the current theme set
|
|
42
|
+
Refreshes the `astryx docs theme`, `getting-started`, `styling`,
|
|
43
|
+
`styling-libraries`, and `migration` reference docs to reflect the published
|
|
44
|
+
themes: `neutral`, `butter`, `chocolate`, `gothic`, `matcha`, `stone`, and
|
|
45
|
+
`y2k`. The removed `theme-default`, `theme-brutalist`, and `theme-daily`
|
|
46
|
+
packages are dropped from the docs, and install/import examples now use
|
|
47
|
+
`@astryxdesign/theme-neutral` as the recommended starting theme.
|
|
48
|
+
|
|
49
|
+
#### Other Changes
|
|
50
|
+
|
|
51
|
+
- **Component names:** the `XDS*` aliases are gone — use bare names (`Button` not
|
|
52
|
+
`XDSButton`, `useTheme` not `useXDSTheme`, `ButtonProps` not `XDSButtonProps`). The
|
|
53
|
+
`drop-xds-prefix-imports` codemod automates this.
|
|
54
|
+
- **CSS classes:** components emit only `.astryx-*` (the dual `.xds-*` class is gone).
|
|
55
|
+
Update custom CSS selectors `.xds-button` -> `.astryx-button` (prop/state value classes
|
|
56
|
+
like `.primary`/`.sm` are unchanged).
|
|
57
|
+
- **data attributes:** only `data-astryx-theme` / `data-astryx-media` are written; update
|
|
58
|
+
custom selectors and SSR root attributes off `data-xds-*`.
|
|
59
|
+
- **CSS layers:** `@layer xds-base` / `xds-theme` are renamed to `astryx-base` /
|
|
60
|
+
`astryx-theme`; update your `@layer` order line and any PostCSS `layersBefore` config.
|
|
61
|
+
`@astryxdesign/build`'s default library layer is now `astryx-base`.
|
|
62
|
+
- **Pre-compiled stylesheet:** the `@astryxdesign/core/xds.css` export is removed — import
|
|
63
|
+
`@astryxdesign/core/astryx.css`.
|
|
64
|
+
- **CSS custom properties:** the `--xds-*` padding fallback is gone; set `--astryx-*`.
|
|
65
|
+
- **CLI config key:** `@astryxdesign/cli` reads the package.json `"astryx"` field (was `"xds"`).
|
|
66
|
+
Rename the block; a stale `"xds"` key silently drops the package from discovery.
|
|
67
|
+
- `@astryxdesign/theme-daily`
|
|
68
|
+
- `@astryxdesign/theme-brutalist`
|
|
69
|
+
- `@astryxdesign/theme-default`
|
|
70
|
+
- import {defaultTheme} from '@astryxdesign/theme-default/built';
|
|
71
|
+
- import {neutralTheme} from '@astryxdesign/theme-neutral/built';
|
|
72
|
+
- <Theme theme={defaultTheme}>...</Theme>
|
|
73
|
+
- <Theme theme={neutralTheme}>...</Theme>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
- Remove the internal `drop-xds-meta-prefix` codemod from the OSS repo (#2970)
|
|
78
|
+
This codemod has been moved to its own package's tooling, where it belongs. It was registered as an optional, version-independent transform and is not part of any standard upgrade path, so removing it does not affect the public `0.0.13 → 0.0.15` migration.
|
|
79
|
+
- Rename the npm package scope from `@xds/*` to `@astryxdesign/*`
|
|
80
|
+
All published packages move to the new `@astryxdesign` scope (e.g. `@xds/core` → `@astryxdesign/core`), along with the workspace lockfile, build/runtime scope-directory scans, and docsite slug derivation. Consumers must update their imports and dependency names. The internal ESLint plugin namespace (`@xds/*` rules) is intentionally untouched and tracked separately. Existing `@xds/*` codemods continue to target the old scope so projects still on `@xds/*` can migrate.
|
|
81
|
+
|
|
82
|
+
#### Contributors
|
|
83
|
+
|
|
84
|
+
Thanks to everyone who contributed to this release:
|
|
85
|
+
|
|
86
|
+
- @cixzhang
|
|
87
|
+
- @ejhammond
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
3
91
|
# 0.0.15
|
|
4
92
|
|
|
5
93
|
#### Breaking Changes
|
package/README.md
CHANGED
|
@@ -341,7 +341,7 @@ astryx doctor — diagnosing your setup
|
|
|
341
341
|
@astryxdesign/core v0.0.14 is in step with @astryxdesign/cli v0.0.14.
|
|
342
342
|
⚠ Theme packages
|
|
343
343
|
No @astryxdesign/theme-* packages are installed.
|
|
344
|
-
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-
|
|
344
|
+
→ fix: Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set xds.theme.
|
|
345
345
|
ℹ astryx.config.mjs
|
|
346
346
|
No astryx.config.mjs found — using defaults.
|
|
347
347
|
ℹ AI agent docs
|
|
@@ -21,7 +21,7 @@ export const docs = {
|
|
|
21
21
|
type: 'code',
|
|
22
22
|
lang: 'text',
|
|
23
23
|
label: 'Paste this into your AI',
|
|
24
|
-
code: 'Install @astryxdesign/core, @astryxdesign/theme-
|
|
24
|
+
code: 'Install @astryxdesign/core, @astryxdesign/theme-neutral, and @astryxdesign/cli in this project. Run `npx astryx init` to set up agent docs. Read the generated files to learn the conventions.',
|
|
25
25
|
},
|
|
26
26
|
],
|
|
27
27
|
},
|
|
@@ -36,7 +36,7 @@ export const docs = {
|
|
|
36
36
|
type: 'code',
|
|
37
37
|
lang: 'bash',
|
|
38
38
|
label: 'Terminal',
|
|
39
|
-
code: `npm install @astryxdesign/core @astryxdesign/theme-
|
|
39
|
+
code: `npm install @astryxdesign/core @astryxdesign/theme-neutral @astryxdesign/cli`,
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
type: 'prose',
|
|
@@ -63,11 +63,11 @@ export const docs = {
|
|
|
63
63
|
label: 'globals.css',
|
|
64
64
|
code: `@import '@astryxdesign/core/reset.css';
|
|
65
65
|
@import '@astryxdesign/core/astryx.css';
|
|
66
|
-
@import '@astryxdesign/theme-
|
|
66
|
+
@import '@astryxdesign/theme-neutral/theme.css';`,
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
type: 'prose',
|
|
70
|
-
text: 'Available themes: @astryxdesign/theme-
|
|
70
|
+
text: 'Available themes: @astryxdesign/theme-neutral (muted minimal, a good starting point), @astryxdesign/theme-butter, @astryxdesign/theme-chocolate, @astryxdesign/theme-gothic (dark-only), @astryxdesign/theme-matcha, @astryxdesign/theme-stone, and @astryxdesign/theme-y2k. See `npx astryx docs theme` for the full theming guide.',
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
73
|
},
|
package/docs/migration.doc.mjs
CHANGED
|
@@ -91,15 +91,15 @@ npx astryx component Button --json`,
|
|
|
91
91
|
lang: 'tsx',
|
|
92
92
|
label: 'Root provider with explicit mode',
|
|
93
93
|
code: `import {Theme} from '@astryxdesign/core/theme';
|
|
94
|
-
import {
|
|
94
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral/built';
|
|
95
95
|
import {useState} from 'react';
|
|
96
|
-
import '@astryxdesign/theme-
|
|
96
|
+
import '@astryxdesign/theme-neutral/theme.css';
|
|
97
97
|
|
|
98
98
|
export function AppRoot({children}: {children: React.ReactNode}) {
|
|
99
99
|
const [mode, setMode] = useState<'system' | 'light' | 'dark'>('system');
|
|
100
100
|
|
|
101
101
|
return (
|
|
102
|
-
<Theme theme={
|
|
102
|
+
<Theme theme={neutralTheme} mode={mode}>
|
|
103
103
|
<SettingsContext.Provider value={{mode, setMode}}>
|
|
104
104
|
{children}
|
|
105
105
|
</SettingsContext.Provider>
|
|
@@ -121,7 +121,7 @@ export function AppRoot({children}: {children: React.ReactNode}) {
|
|
|
121
121
|
@import "tailwindcss/preflight.css" layer(base);
|
|
122
122
|
@import "@astryxdesign/core/reset.css";
|
|
123
123
|
@import "@astryxdesign/core/astryx.css";
|
|
124
|
-
@import "@astryxdesign/theme-
|
|
124
|
+
@import "@astryxdesign/theme-neutral/theme.css";
|
|
125
125
|
@import "@astryxdesign/core/tailwind-theme.css";
|
|
126
126
|
@import "tailwindcss/utilities.css" layer(utilities);`,
|
|
127
127
|
},
|
|
@@ -162,7 +162,7 @@ const styles = stylex.create({
|
|
|
162
162
|
@import "tailwindcss/preflight.css" layer(base);
|
|
163
163
|
@import "@astryxdesign/core/reset.css";
|
|
164
164
|
@import "@astryxdesign/core/astryx.css";
|
|
165
|
-
@import "@astryxdesign/theme-
|
|
165
|
+
@import "@astryxdesign/theme-neutral/theme.css";
|
|
166
166
|
@import "@astryxdesign/core/tailwind-theme.css";
|
|
167
167
|
@import "tailwindcss/utilities.css" layer(utilities);`,
|
|
168
168
|
},
|
|
@@ -374,9 +374,9 @@ tokens: {
|
|
|
374
374
|
lang: 'ts',
|
|
375
375
|
label: 'Resolve tokens without React context',
|
|
376
376
|
code: `import {resolveThemeTokens} from '@astryxdesign/core/theme/tokens';
|
|
377
|
-
import {
|
|
377
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral';
|
|
378
378
|
|
|
379
|
-
const tokens = resolveThemeTokens(
|
|
379
|
+
const tokens = resolveThemeTokens(neutralTheme, {mode: 'light'});
|
|
380
380
|
|
|
381
381
|
const chartOptions = {
|
|
382
382
|
textColor: tokens['--color-text-primary'],
|
package/docs/styling.doc.mjs
CHANGED
|
@@ -104,7 +104,7 @@ const overrides = stylex.create({
|
|
|
104
104
|
@import "tailwindcss/preflight.css" layer(base);
|
|
105
105
|
@import "@astryxdesign/core/reset.css";
|
|
106
106
|
@import "@astryxdesign/core/astryx.css";
|
|
107
|
-
@import "@astryxdesign/theme-
|
|
107
|
+
@import "@astryxdesign/theme-neutral/theme.css";
|
|
108
108
|
@import "@astryxdesign/core/tailwind-theme.css";
|
|
109
109
|
@import "tailwindcss/utilities.css" layer(utilities);`,
|
|
110
110
|
},
|
package/docs/theme.doc.dense.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const docsDense = {
|
|
|
6
6
|
description: 'Theme provider, custom themes, light/dark, component overrides',
|
|
7
7
|
sections: [
|
|
8
8
|
{ title: 'Quick Start', content: [null, null, { type: 'prose', text: 'default import = runtime injection. /built import = pre-compiled CSS (pair with theme.css).' }] },
|
|
9
|
-
{ title: 'Themes', content: [null, { type: 'prose', text: '@astryxdesign/theme-{name} = source (runtime). @astryxdesign/theme-{name}/built = optimized (+ theme.css).' }] },
|
|
9
|
+
{ title: 'Themes', content: [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
|
{ title: 'Props', content: [null] },
|
|
11
11
|
{ title: 'Custom Theme', content: [{ type: 'prose', text: 'CLI wizard or manual defineTheme. only override tokens that differ.' }, null] },
|
|
12
12
|
{ title: 'defineTheme', content: [{ type: 'prose', text: 'scale configs (color, typography, radius, motion) + explicit token overrides + component overrides. color derives full palette from accent hex via HCT.' }, null, null] },
|
package/docs/theme.doc.mjs
CHANGED
|
@@ -19,11 +19,11 @@ export const docs = {
|
|
|
19
19
|
lang: 'tsx',
|
|
20
20
|
label: 'Basic theme setup (runtime injection)',
|
|
21
21
|
code: `import {Theme} from '@astryxdesign/core';
|
|
22
|
-
import {
|
|
22
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral';
|
|
23
23
|
|
|
24
24
|
function App() {
|
|
25
25
|
return (
|
|
26
|
-
<Theme theme={
|
|
26
|
+
<Theme theme={neutralTheme}>
|
|
27
27
|
<YourApp />
|
|
28
28
|
</Theme>
|
|
29
29
|
);
|
|
@@ -34,12 +34,12 @@ function App() {
|
|
|
34
34
|
lang: 'tsx',
|
|
35
35
|
label: 'Optimized setup (pre-built CSS)',
|
|
36
36
|
code: `import {Theme} from '@astryxdesign/core';
|
|
37
|
-
import {
|
|
38
|
-
import '@astryxdesign/theme-
|
|
37
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral/built';
|
|
38
|
+
import '@astryxdesign/theme-neutral/theme.css';
|
|
39
39
|
|
|
40
40
|
function App() {
|
|
41
41
|
return (
|
|
42
|
-
<Theme theme={
|
|
42
|
+
<Theme theme={neutralTheme}>
|
|
43
43
|
<YourApp />
|
|
44
44
|
</Theme>
|
|
45
45
|
);
|
|
@@ -59,20 +59,40 @@ function App() {
|
|
|
59
59
|
type: 'table',
|
|
60
60
|
headers: ['Theme', 'Import', 'Description'],
|
|
61
61
|
rows: [
|
|
62
|
-
[
|
|
63
|
-
'Default',
|
|
64
|
-
"import {defaultTheme} from '@astryxdesign/theme-default'",
|
|
65
|
-
'Blue accent, system fonts, light/dark',
|
|
66
|
-
],
|
|
67
62
|
[
|
|
68
63
|
'Neutral',
|
|
69
64
|
"import {neutralTheme} from '@astryxdesign/theme-neutral'",
|
|
70
|
-
'
|
|
65
|
+
'Muted, minimal aesthetic with system fonts. A good starting point.',
|
|
66
|
+
],
|
|
67
|
+
[
|
|
68
|
+
'Butter',
|
|
69
|
+
"import {butterTheme} from '@astryxdesign/theme-butter'",
|
|
70
|
+
'Golden, buttery surfaces with blue accents; Sarina + Outfit type.',
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
'Chocolate',
|
|
74
|
+
"import {chocolateTheme} from '@astryxdesign/theme-chocolate'",
|
|
75
|
+
'Warm brown tones and cozy beige; Fraunces + Albert Sans type.',
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
'Gothic',
|
|
79
|
+
"import {gothicTheme} from '@astryxdesign/theme-gothic'",
|
|
80
|
+
'Dark-only atmospheric theme; deep blue-gray surfaces, distressed display type.',
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
'Matcha',
|
|
84
|
+
"import {matchaTheme} from '@astryxdesign/theme-matcha'",
|
|
85
|
+
'Earthy green theme with Figtree typography.',
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
'Stone',
|
|
89
|
+
"import {stoneTheme} from '@astryxdesign/theme-stone'",
|
|
90
|
+
'Warm stone and slate tones; Montserrat + Figtree type.',
|
|
71
91
|
],
|
|
72
92
|
[
|
|
73
|
-
'
|
|
74
|
-
"import {
|
|
75
|
-
'
|
|
93
|
+
'Y2K',
|
|
94
|
+
"import {y2kTheme} from '@astryxdesign/theme-y2k'",
|
|
95
|
+
'Playful Y2K pop; periwinkle body, holographic accents, Poppins + Crimson Text.',
|
|
76
96
|
],
|
|
77
97
|
],
|
|
78
98
|
},
|
|
@@ -194,14 +214,14 @@ const myTheme = defineTheme({
|
|
|
194
214
|
{
|
|
195
215
|
type: 'code',
|
|
196
216
|
lang: 'tsx',
|
|
197
|
-
label: 'Extending the
|
|
217
|
+
label: 'Extending the neutral theme',
|
|
198
218
|
code: `import {defineTheme} from '@astryxdesign/core/theme';
|
|
199
|
-
import {
|
|
219
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral';
|
|
200
220
|
import {myIcons} from './icons';
|
|
201
221
|
|
|
202
222
|
const brandTheme = defineTheme({
|
|
203
223
|
name: 'brand',
|
|
204
|
-
extends:
|
|
224
|
+
extends: neutralTheme,
|
|
205
225
|
icons: myIcons,
|
|
206
226
|
tokens: {
|
|
207
227
|
'--color-accent': ['#7B61FF', '#9B85FF'],
|
|
@@ -525,9 +545,9 @@ const pandaOrEmotionTheme = {
|
|
|
525
545
|
lang: 'ts',
|
|
526
546
|
label: 'Resolve token values without a hook',
|
|
527
547
|
code: `import {resolveThemeTokens} from '@astryxdesign/core/theme/tokens';
|
|
528
|
-
import {
|
|
548
|
+
import {neutralTheme} from '@astryxdesign/theme-neutral';
|
|
529
549
|
|
|
530
|
-
const lightTokens = resolveThemeTokens(
|
|
550
|
+
const lightTokens = resolveThemeTokens(neutralTheme, {mode: 'light'});
|
|
531
551
|
const chartTheme = {
|
|
532
552
|
textColor: lightTokens['--color-text-primary'],
|
|
533
553
|
seriesColor: lightTokens['--color-data-categorical-blue'],
|
package/docs/theme.doc.zh.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const docsZh = {
|
|
|
6
6
|
description: 'Theme 提供者、自定义主题、亮/暗模式和组件样式覆盖。',
|
|
7
7
|
sections: [
|
|
8
8
|
{ title: '快速开始', content: [null, null, { type: 'prose', text: '默认导入使用运行时样式注入。/built 导入使用预编译 CSS(需配合 theme.css)。' }] },
|
|
9
|
-
{ title: '可用主题', content: [null, { type: 'prose', text: '
|
|
9
|
+
{ title: '可用主题', content: [null, { type: 'prose', text: '已发布主题:neutral(推荐起点)、butter、chocolate、gothic(仅暗色)、matcha、stone、y2k。@astryxdesign/theme-{name} = 源码版(运行时注入)。@astryxdesign/theme-{name}/built = 优化版(配合 theme.css)。' }] },
|
|
10
10
|
{ title: 'Theme 属性', content: [null] },
|
|
11
11
|
{ title: '创建自定义主题', content: [{ type: 'prose', text: '使用 CLI 向导(推荐)或手动 defineTheme。只覆盖与默认值不同的令牌。' }, null] },
|
|
12
12
|
{ title: 'defineTheme', content: [{ type: 'prose', text: '支持比例配置(typography、radius、motion)+ 显式令牌覆盖 + 组件覆盖。' }, null, null] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.1.0-canary.3047a7c",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -50,13 +50,13 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@clack/prompts": "^1.5.1",
|
|
52
52
|
"commander": "^12.1.0",
|
|
53
|
-
"jiti": "^2.7.0"
|
|
53
|
+
"jiti": "^2.7.0",
|
|
54
|
+
"jscodeshift": "^17.3.0"
|
|
54
55
|
},
|
|
55
56
|
"peerDependencies": {
|
|
56
|
-
"@astryxdesign/core": "
|
|
57
|
-
"@astryxdesign/lab": "
|
|
58
|
-
"@astryxdesign/theme-
|
|
59
|
-
"@astryxdesign/theme-neutral": "*"
|
|
57
|
+
"@astryxdesign/core": "0.1.0-canary.3047a7c",
|
|
58
|
+
"@astryxdesign/lab": "0.1.0-canary.3047a7c",
|
|
59
|
+
"@astryxdesign/theme-neutral": "0.1.0-canary.3047a7c"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
62
|
"@astryxdesign/core": {
|
|
@@ -65,18 +65,14 @@
|
|
|
65
65
|
"@astryxdesign/lab": {
|
|
66
66
|
"optional": true
|
|
67
67
|
},
|
|
68
|
-
"@astryxdesign/theme-default": {
|
|
69
|
-
"optional": true
|
|
70
|
-
},
|
|
71
68
|
"@astryxdesign/theme-neutral": {
|
|
72
69
|
"optional": true
|
|
73
70
|
}
|
|
74
71
|
},
|
|
75
72
|
"devDependencies": {
|
|
76
|
-
"@astryxdesign/core": "
|
|
77
|
-
"@astryxdesign/lab": "
|
|
78
|
-
"@astryxdesign/theme-
|
|
79
|
-
"@astryxdesign/theme-neutral": "*"
|
|
73
|
+
"@astryxdesign/core": "0.1.0-canary.3047a7c",
|
|
74
|
+
"@astryxdesign/lab": "0.1.0-canary.3047a7c",
|
|
75
|
+
"@astryxdesign/theme-neutral": "0.1.0-canary.3047a7c"
|
|
80
76
|
},
|
|
81
77
|
"scripts": {
|
|
82
78
|
"astryx": "node bin/astryx.mjs",
|
package/src/api/doctor.mjs
CHANGED
|
@@ -240,7 +240,7 @@ export function checkThemes(ctx) {
|
|
|
240
240
|
label: 'Theme packages',
|
|
241
241
|
status: 'warn',
|
|
242
242
|
message: 'No @astryxdesign/theme-* packages are installed.',
|
|
243
|
-
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-
|
|
243
|
+
fix: 'Install a theme, e.g. `npm install @astryxdesign/theme-neutral`, then import its CSS or set xds.theme.',
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -355,8 +355,8 @@ export function checkAgentDocs(ctx) {
|
|
|
355
355
|
try {
|
|
356
356
|
const content = fs.readFileSync(path.join(ctx.cwd, rel), 'utf-8');
|
|
357
357
|
return (
|
|
358
|
-
content.includes('<!-- XDS:START -->') &&
|
|
359
|
-
content.includes('<!-- XDS:END -->')
|
|
358
|
+
(content.includes('<!-- ASTRYX:START -->') || content.includes('<!-- XDS:START -->')) &&
|
|
359
|
+
(content.includes('<!-- ASTRYX:END -->') || content.includes('<!-- XDS:END -->'))
|
|
360
360
|
);
|
|
361
361
|
} catch {
|
|
362
362
|
return false;
|
|
@@ -368,7 +368,7 @@ export function checkAgentDocs(ctx) {
|
|
|
368
368
|
id: 'agent-docs',
|
|
369
369
|
label: 'AI agent docs',
|
|
370
370
|
status: 'warn',
|
|
371
|
-
message: `Agent docs present (${present.join(', ')}) but no
|
|
371
|
+
message: `Agent docs present (${present.join(', ')}) but no Astryx section markers found.`,
|
|
372
372
|
fix: 'Add the XDS section to your agent docs with `astryx init --features agents`.',
|
|
373
373
|
};
|
|
374
374
|
}
|
|
@@ -31,8 +31,11 @@ const AGENTS_MD = 'AGENTS.md';
|
|
|
31
31
|
const CLAUDE_MD = 'CLAUDE.md';
|
|
32
32
|
const CLAUDE_DIR_MD = path.join('.claude', 'CLAUDE.md');
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
const
|
|
34
|
+
const MARKER_START = '<!-- ASTRYX:START -->';
|
|
35
|
+
const MARKER_END = '<!-- ASTRYX:END -->';
|
|
36
|
+
// Legacy markers — read during migration so the script finds existing XDS blocks
|
|
37
|
+
const LEGACY_MARKER_START = '<!-- XDS:START -->';
|
|
38
|
+
const LEGACY_MARKER_END = '<!-- XDS:END -->';
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Agent tool presets — maps tool names to their file search paths.
|
|
@@ -99,7 +102,7 @@ export function resolveAgentPaths(targetDir, agent) {
|
|
|
99
102
|
*/
|
|
100
103
|
export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPrefix()} = {}) {
|
|
101
104
|
const run = `${runPrefix} astryx`;
|
|
102
|
-
const lines = [
|
|
105
|
+
const lines = [MARKER_START];
|
|
103
106
|
|
|
104
107
|
// Component count from live discovery
|
|
105
108
|
let componentCount = '90+';
|
|
@@ -177,7 +180,7 @@ export function generateCompressedIndex(version, {coreDir, runPrefix = getRunPre
|
|
|
177
180
|
lines.push(`${run} swizzle <Name> eject source (--gap to report why)`);
|
|
178
181
|
lines.push(`${run} upgrade --apply codemods after version bump`);
|
|
179
182
|
lines.push(`after @astryxdesign/core bump, always run ${run} upgrade --apply`);
|
|
180
|
-
lines.push(
|
|
183
|
+
lines.push(MARKER_END);
|
|
181
184
|
|
|
182
185
|
return lines.join('\n');
|
|
183
186
|
}
|
|
@@ -218,14 +221,21 @@ export function injectXdsBlock(filePath, compressedIndex, {createIfMissing = fal
|
|
|
218
221
|
if (fs.existsSync(filePath)) {
|
|
219
222
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
220
223
|
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
// Find existing section — try new markers first, fall back to legacy XDS markers
|
|
225
|
+
let startIdx = content.indexOf(MARKER_START);
|
|
226
|
+
let endIdx = content.indexOf(MARKER_END);
|
|
227
|
+
let markerEndLength = MARKER_END.length;
|
|
228
|
+
if (startIdx === -1) {
|
|
229
|
+
startIdx = content.indexOf(LEGACY_MARKER_START);
|
|
230
|
+
endIdx = content.indexOf(LEGACY_MARKER_END);
|
|
231
|
+
markerEndLength = LEGACY_MARKER_END.length;
|
|
232
|
+
}
|
|
223
233
|
|
|
224
234
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
225
235
|
content =
|
|
226
236
|
content.slice(0, startIdx) +
|
|
227
237
|
compressedIndex +
|
|
228
|
-
content.slice(endIdx +
|
|
238
|
+
content.slice(endIdx + markerEndLength);
|
|
229
239
|
} else if (onlyReplace) {
|
|
230
240
|
// File exists but has no Astryx markers — skip it
|
|
231
241
|
return false;
|
|
@@ -277,13 +287,20 @@ export function removeXdsBlock(filePath, {deleteIfEmpty = false} = {}) {
|
|
|
277
287
|
if (!fs.existsSync(filePath)) return false;
|
|
278
288
|
|
|
279
289
|
let content = fs.readFileSync(filePath, 'utf-8');
|
|
280
|
-
|
|
281
|
-
|
|
290
|
+
// Find existing section — try new markers first, fall back to legacy
|
|
291
|
+
let startIdx = content.indexOf(MARKER_START);
|
|
292
|
+
let endIdx = content.indexOf(MARKER_END);
|
|
293
|
+
let markerEndLen = MARKER_END.length;
|
|
294
|
+
if (startIdx === -1) {
|
|
295
|
+
startIdx = content.indexOf(LEGACY_MARKER_START);
|
|
296
|
+
endIdx = content.indexOf(LEGACY_MARKER_END);
|
|
297
|
+
markerEndLen = LEGACY_MARKER_END.length;
|
|
298
|
+
}
|
|
282
299
|
|
|
283
300
|
if (startIdx === -1 || endIdx === -1) return false;
|
|
284
301
|
|
|
285
302
|
const before = content.slice(0, startIdx).trimEnd();
|
|
286
|
-
const after = content.slice(endIdx +
|
|
303
|
+
const after = content.slice(endIdx + markerEndLen).trimStart();
|
|
287
304
|
content = before + (after ? '\n\n' + after : '') + '\n';
|
|
288
305
|
|
|
289
306
|
if (deleteIfEmpty) {
|
|
@@ -32,8 +32,8 @@ describe('generateCompressedIndex', () => {
|
|
|
32
32
|
it('includes the version number', () => {
|
|
33
33
|
const result = generateCompressedIndex('1.2.3');
|
|
34
34
|
expect(result).toContain('Astryx v1.2.3');
|
|
35
|
-
expect(result).toContain('<!--
|
|
36
|
-
expect(result).toContain('<!--
|
|
35
|
+
expect(result).toContain('<!-- ASTRYX:START -->');
|
|
36
|
+
expect(result).toContain('<!-- ASTRYX:END -->');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('includes theme nudge rule', () => {
|
|
@@ -82,19 +82,19 @@ describe('injectXdsBlock', () => {
|
|
|
82
82
|
const filePath = path.join(tmpDir, 'test.md');
|
|
83
83
|
fs.writeFileSync(filePath, '# Existing content\n');
|
|
84
84
|
|
|
85
|
-
const result = injectXdsBlock(filePath, '<!--
|
|
85
|
+
const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->');
|
|
86
86
|
|
|
87
87
|
expect(result).toBe(true);
|
|
88
88
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
89
89
|
expect(content).toContain('# Existing content');
|
|
90
|
-
expect(content).toContain('<!--
|
|
90
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it('replaces existing markers', () => {
|
|
94
94
|
const filePath = path.join(tmpDir, 'test.md');
|
|
95
95
|
fs.writeFileSync(filePath, 'before\n<!-- XDS:START -->\nold\n<!-- XDS:END -->\nafter\n');
|
|
96
96
|
|
|
97
|
-
injectXdsBlock(filePath, '<!--
|
|
97
|
+
injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->');
|
|
98
98
|
|
|
99
99
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
100
100
|
expect(content).toContain('new');
|
|
@@ -106,7 +106,7 @@ describe('injectXdsBlock', () => {
|
|
|
106
106
|
it('returns false and does not create file when createIfMissing is false', () => {
|
|
107
107
|
const filePath = path.join(tmpDir, 'nonexistent.md');
|
|
108
108
|
|
|
109
|
-
const result = injectXdsBlock(filePath, '<!--
|
|
109
|
+
const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\ncontent\n<!-- ASTRYX:END -->');
|
|
110
110
|
|
|
111
111
|
expect(result).toBe(false);
|
|
112
112
|
expect(fs.existsSync(filePath)).toBe(false);
|
|
@@ -116,11 +116,11 @@ describe('injectXdsBlock', () => {
|
|
|
116
116
|
const filePath = path.join(tmpDir, 'test.md');
|
|
117
117
|
fs.writeFileSync(filePath, '# Existing content\n\nNo XDS markers here.\n');
|
|
118
118
|
|
|
119
|
-
const result = injectXdsBlock(filePath, '<!--
|
|
119
|
+
const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->', {onlyReplace: true});
|
|
120
120
|
|
|
121
121
|
expect(result).toBe(false);
|
|
122
122
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
123
|
-
expect(content).not.toContain('<!--
|
|
123
|
+
expect(content).not.toContain('<!-- ASTRYX:START -->');
|
|
124
124
|
expect(content).toBe('# Existing content\n\nNo XDS markers here.\n');
|
|
125
125
|
});
|
|
126
126
|
|
|
@@ -128,7 +128,7 @@ describe('injectXdsBlock', () => {
|
|
|
128
128
|
const filePath = path.join(tmpDir, 'test.md');
|
|
129
129
|
fs.writeFileSync(filePath, 'before\n<!-- XDS:START -->\nold\n<!-- XDS:END -->\nafter\n');
|
|
130
130
|
|
|
131
|
-
const result = injectXdsBlock(filePath, '<!--
|
|
131
|
+
const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\nnew\n<!-- ASTRYX:END -->', {onlyReplace: true});
|
|
132
132
|
|
|
133
133
|
expect(result).toBe(true);
|
|
134
134
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -139,7 +139,7 @@ describe('injectXdsBlock', () => {
|
|
|
139
139
|
it('creates file when createIfMissing is true', () => {
|
|
140
140
|
const filePath = path.join(tmpDir, 'new.md');
|
|
141
141
|
|
|
142
|
-
const result = injectXdsBlock(filePath, '<!--
|
|
142
|
+
const result = injectXdsBlock(filePath, '<!-- ASTRYX:START -->\ncontent\n<!-- ASTRYX:END -->', {
|
|
143
143
|
createIfMissing: true,
|
|
144
144
|
header: '# Header',
|
|
145
145
|
});
|
|
@@ -147,7 +147,7 @@ describe('injectXdsBlock', () => {
|
|
|
147
147
|
expect(result).toBe(true);
|
|
148
148
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
149
149
|
expect(content).toContain('# Header');
|
|
150
|
-
expect(content).toContain('<!--
|
|
150
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
151
151
|
});
|
|
152
152
|
});
|
|
153
153
|
|
|
@@ -157,9 +157,9 @@ describe('injectAgentsMd', () => {
|
|
|
157
157
|
|
|
158
158
|
const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
159
159
|
expect(content).toContain('# AGENTS.md');
|
|
160
|
-
expect(content).toContain('<!--
|
|
160
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
161
161
|
expect(content).toContain('Astryx v1.0.0');
|
|
162
|
-
expect(content).toContain('<!--
|
|
162
|
+
expect(content).toContain('<!-- ASTRYX:END -->');
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
it('updates existing AGENTS.md by replacing XDS markers', () => {
|
|
@@ -195,7 +195,7 @@ Existing agent docs.
|
|
|
195
195
|
|
|
196
196
|
const content = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
197
197
|
expect(content).toContain('Existing agent docs.');
|
|
198
|
-
expect(content).toContain('<!--
|
|
198
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
199
199
|
expect(content).toContain('Astryx v1.0.0');
|
|
200
200
|
});
|
|
201
201
|
});
|
|
@@ -210,7 +210,7 @@ describe('injectClaudeMd', () => {
|
|
|
210
210
|
const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
|
|
211
211
|
expect(content).toContain('# Claude Config');
|
|
212
212
|
expect(content).toContain('Existing rules.');
|
|
213
|
-
expect(content).toContain('<!--
|
|
213
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
214
214
|
expect(content).toContain('Astryx v1.0.0');
|
|
215
215
|
});
|
|
216
216
|
|
|
@@ -322,7 +322,7 @@ describe('installAgentDocs', () => {
|
|
|
322
322
|
expect(fs.existsSync(path.join(tmpDir, '.claude', 'CLAUDE.md'))).toBe(true);
|
|
323
323
|
expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
|
|
324
324
|
const content = fs.readFileSync(path.join(tmpDir, '.claude', 'CLAUDE.md'), 'utf-8');
|
|
325
|
-
expect(content).toContain('<!--
|
|
325
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
326
326
|
});
|
|
327
327
|
|
|
328
328
|
it('injects into CLAUDE.md at root when it exists', () => {
|
|
@@ -334,7 +334,7 @@ describe('installAgentDocs', () => {
|
|
|
334
334
|
expect(written).toEqual(['CLAUDE.md']);
|
|
335
335
|
expect(fs.existsSync(path.join(tmpDir, 'AGENTS.md'))).toBe(false);
|
|
336
336
|
const claudeContent = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
|
|
337
|
-
expect(claudeContent).toContain('<!--
|
|
337
|
+
expect(claudeContent).toContain('<!-- ASTRYX:START -->');
|
|
338
338
|
expect(claudeContent).toContain('Project rules.');
|
|
339
339
|
});
|
|
340
340
|
|
|
@@ -349,8 +349,8 @@ describe('installAgentDocs', () => {
|
|
|
349
349
|
expect(written).toContain('CLAUDE.md');
|
|
350
350
|
const agentsContent = fs.readFileSync(path.join(tmpDir, 'AGENTS.md'), 'utf-8');
|
|
351
351
|
const claudeContent = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
|
|
352
|
-
expect(agentsContent).toContain('<!--
|
|
353
|
-
expect(claudeContent).toContain('<!--
|
|
352
|
+
expect(agentsContent).toContain('<!-- ASTRYX:START -->');
|
|
353
|
+
expect(claudeContent).toContain('<!-- ASTRYX:START -->');
|
|
354
354
|
});
|
|
355
355
|
|
|
356
356
|
it('updates existing .claude/CLAUDE.md', () => {
|
|
@@ -363,7 +363,7 @@ describe('installAgentDocs', () => {
|
|
|
363
363
|
expect(written).toEqual(['.claude/CLAUDE.md']);
|
|
364
364
|
const content = fs.readFileSync(path.join(tmpDir, '.claude', 'CLAUDE.md'), 'utf-8');
|
|
365
365
|
expect(content).toContain('Existing content.');
|
|
366
|
-
expect(content).toContain('<!--
|
|
366
|
+
expect(content).toContain('<!-- ASTRYX:START -->');
|
|
367
367
|
});
|
|
368
368
|
|
|
369
369
|
it('respects --agent claude preset: finds existing CLAUDE.md', () => {
|
|
@@ -410,7 +410,7 @@ describe('installAgentDocs', () => {
|
|
|
410
410
|
|
|
411
411
|
expect(written).toEqual([]);
|
|
412
412
|
const content = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8');
|
|
413
|
-
expect(content).not.toContain('<!--
|
|
413
|
+
expect(content).not.toContain('<!-- ASTRYX:START -->');
|
|
414
414
|
expect(content).toBe('# Claude\n\nProject rules only.\n');
|
|
415
415
|
});
|
|
416
416
|
|
|
@@ -106,14 +106,14 @@ describe('doctor — individual checks', () => {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
it('themes: WARN when theme installed but not wired', () => {
|
|
109
|
-
installPkg('@astryxdesign/theme-
|
|
109
|
+
installPkg('@astryxdesign/theme-neutral', '0.0.14');
|
|
110
110
|
const res = checkThemes({cwd: tmpDir, configTheme: null});
|
|
111
111
|
expect(res.status).toBe('warn');
|
|
112
|
-
expect(res.message).toContain('@astryxdesign/theme-
|
|
112
|
+
expect(res.message).toContain('@astryxdesign/theme-neutral');
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
it('themes: PASS when theme installed and wired via config', () => {
|
|
116
|
-
installPkg('@astryxdesign/theme-
|
|
116
|
+
installPkg('@astryxdesign/theme-neutral', '0.0.14');
|
|
117
117
|
const res = checkThemes({cwd: tmpDir, configTheme: 'default'});
|
|
118
118
|
expect(res.status).toBe('pass');
|
|
119
119
|
});
|
package/src/commands/init.mjs
CHANGED
|
@@ -246,8 +246,8 @@ export function registerInit(program) {
|
|
|
246
246
|
humanLog(' Next steps:');
|
|
247
247
|
humanLog(" 1. Import components: import { Button } from '@astryxdesign/core'");
|
|
248
248
|
humanLog(' 2. Optionally add a theme:');
|
|
249
|
-
humanLog(" import {
|
|
250
|
-
humanLog(' <Theme theme={
|
|
249
|
+
humanLog(" import { neutralTheme } from '@astryxdesign/theme-neutral'");
|
|
250
|
+
humanLog(' <Theme theme={neutralTheme}>...</Theme>');
|
|
251
251
|
humanLog(` 3. ${run} xds --help for all commands`);
|
|
252
252
|
humanLog('');
|
|
253
253
|
});
|
|
@@ -18,13 +18,10 @@ import {Link} from '@astryxdesign/core/Link';
|
|
|
18
18
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
19
19
|
import {Card} from '@astryxdesign/core/Card';
|
|
20
20
|
import {Selector} from '@astryxdesign/core/Selector';
|
|
21
|
+
import {radiusVars} from '@astryxdesign/core/theme/tokens.stylex';
|
|
21
22
|
|
|
22
23
|
const ILLUSTRATION_URL =
|
|
23
|
-
'https://lookaside.facebook.com/assets/astryx/
|
|
24
|
-
|
|
25
|
-
// ─────────────────────────────────────────────────────────────
|
|
26
|
-
// Constants
|
|
27
|
-
// ─────────────────────────────────────────────────────────────
|
|
24
|
+
'https://lookaside.facebook.com/assets/astryx/light-working-vertical-2.png';
|
|
28
25
|
|
|
29
26
|
const INQUIRY_REASONS = [
|
|
30
27
|
'New business',
|
|
@@ -51,13 +48,9 @@ const CONTACT_COLUMNS = [
|
|
|
51
48
|
{label: 'Press & partnerships', email: 'press@company.com'},
|
|
52
49
|
];
|
|
53
50
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
// The only custom styling is fitting the illustration inside its AspectRatio
|
|
59
|
-
// box without distortion (contain, not cover — it's line art, don't crop it).
|
|
60
|
-
// No objectFit prop on AspectRatio, and there's no Image primitive (#2582).
|
|
51
|
+
// AspectRatio has no objectFit/radius prop and there's no Image primitive
|
|
52
|
+
// (#2582), so the cover photo is styled directly. overflow:hidden masks the
|
|
53
|
+
// cover crop to the rounded corners.
|
|
61
54
|
const styles = stylex.create({
|
|
62
55
|
page: {
|
|
63
56
|
minHeight: '100%',
|
|
@@ -65,14 +58,12 @@ const styles = stylex.create({
|
|
|
65
58
|
illustrationImg: {
|
|
66
59
|
width: '100%',
|
|
67
60
|
height: '100%',
|
|
68
|
-
objectFit: '
|
|
61
|
+
objectFit: 'cover',
|
|
62
|
+
borderRadius: radiusVars['--radius-container'],
|
|
63
|
+
overflow: 'hidden',
|
|
69
64
|
},
|
|
70
65
|
});
|
|
71
66
|
|
|
72
|
-
// ─────────────────────────────────────────────────────────────
|
|
73
|
-
// Page
|
|
74
|
-
// ─────────────────────────────────────────────────────────────
|
|
75
|
-
|
|
76
67
|
/**
|
|
77
68
|
* Form (Two-column) — marketing contact form template.
|
|
78
69
|
*
|
|
@@ -105,15 +96,10 @@ export default function FormTwoColumnPage() {
|
|
|
105
96
|
|
|
106
97
|
return (
|
|
107
98
|
<Center xstyle={styles.page}>
|
|
108
|
-
<Section
|
|
109
|
-
maxWidth={1100}
|
|
110
|
-
width="100%"
|
|
111
|
-
padding={10}
|
|
112
|
-
variant="transparent">
|
|
99
|
+
<Section maxWidth={1100} width="100%" padding={10} variant="transparent">
|
|
113
100
|
<VStack gap={10}>
|
|
114
|
-
{/*
|
|
101
|
+
{/* Two-column; stacks to one column below ~520px. */}
|
|
115
102
|
<Grid columns={{minWidth: 320}} align="center" gap={10}>
|
|
116
|
-
{/* Left: headline + description + illustration */}
|
|
117
103
|
<VStack gap={6}>
|
|
118
104
|
<VStack gap={3}>
|
|
119
105
|
<Text type="display-1" as="h1">
|
|
@@ -127,13 +113,12 @@ export default function FormTwoColumnPage() {
|
|
|
127
113
|
<AspectRatio ratio={4 / 3}>
|
|
128
114
|
<img
|
|
129
115
|
src={ILLUSTRATION_URL}
|
|
130
|
-
alt="
|
|
116
|
+
alt="Two people working at a desk"
|
|
131
117
|
{...stylex.props(styles.illustrationImg)}
|
|
132
118
|
/>
|
|
133
119
|
</AspectRatio>
|
|
134
120
|
</VStack>
|
|
135
121
|
|
|
136
|
-
{/* Right: form on a card */}
|
|
137
122
|
<Card padding={8}>
|
|
138
123
|
<VStack gap={4}>
|
|
139
124
|
<Text type="label">Your details</Text>
|
|
@@ -188,9 +173,7 @@ export default function FormTwoColumnPage() {
|
|
|
188
173
|
</Grid>
|
|
189
174
|
|
|
190
175
|
<VStack gap={2}>
|
|
191
|
-
<Text type="label">
|
|
192
|
-
What are you reaching out about?
|
|
193
|
-
</Text>
|
|
176
|
+
<Text type="label">What are you reaching out about?</Text>
|
|
194
177
|
<HStack gap={2} wrap="wrap">
|
|
195
178
|
{INQUIRY_REASONS.map(reason => (
|
|
196
179
|
<Token
|
|
@@ -238,7 +221,7 @@ export default function FormTwoColumnPage() {
|
|
|
238
221
|
</Card>
|
|
239
222
|
</Grid>
|
|
240
223
|
|
|
241
|
-
{/*
|
|
224
|
+
{/* Contact strip; stacks below ~440px. */}
|
|
242
225
|
<VStack gap={6}>
|
|
243
226
|
<Divider />
|
|
244
227
|
<Grid columns={{minWidth: 200}} gap={6}>
|