@astryxdesign/cli 0.1.8-canary.3bb5896 → 0.1.8-canary.4a7ce02

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.
Files changed (37) hide show
  1. package/docs/elevation.doc.mjs +1 -79
  2. package/package.json +10 -11
  3. package/src/api/error.mjs +2 -2
  4. package/src/commands/swizzle.mjs +1 -1
  5. package/src/lib/cli-error.mjs +2 -2
  6. package/src/lib/json-shim.mjs +1 -1
  7. package/src/lib/json.mjs +3 -3
  8. package/src/types/api.d.ts +9 -4
  9. package/src/types/base.d.ts +3 -24
  10. package/src/types/index.d.ts +0 -1
  11. package/src/types/swizzle.d.ts +0 -4
  12. package/src/utils/package-manager.mjs +4 -30
  13. package/src/utils/update-check.mjs +3 -3
  14. package/src/utils/update-check.test.mjs +3 -3
  15. package/src/types/build.d.ts +0 -23
  16. package/templates/blocks/components/Banner/BannerFloating.doc.mjs +0 -14
  17. package/templates/blocks/components/Banner/BannerFloating.tsx +0 -16
  18. package/templates/blocks/components/Button/ButtonFloating.doc.mjs +0 -14
  19. package/templates/blocks/components/Button/ButtonFloating.tsx +0 -37
  20. package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.doc.mjs +0 -14
  21. package/templates/blocks/components/ButtonGroup/ButtonGroupFloating.tsx +0 -23
  22. package/templates/blocks/components/Card/CardElevations.doc.mjs +0 -14
  23. package/templates/blocks/components/Card/CardElevations.tsx +0 -32
  24. package/templates/blocks/components/Card/ClickableCardElevated.doc.mjs +0 -14
  25. package/templates/blocks/components/Card/ClickableCardElevated.tsx +0 -21
  26. package/templates/blocks/components/Card/SelectableCardElevated.doc.mjs +0 -14
  27. package/templates/blocks/components/Card/SelectableCardElevated.tsx +0 -37
  28. package/templates/blocks/components/ChatComposer/ChatComposerFlat.doc.mjs +0 -14
  29. package/templates/blocks/components/ChatComposer/ChatComposerFlat.tsx +0 -75
  30. package/templates/blocks/components/IconButton/IconButtonFloating.doc.mjs +0 -14
  31. package/templates/blocks/components/IconButton/IconButtonFloating.tsx +0 -37
  32. package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.doc.mjs +0 -14
  33. package/templates/blocks/components/OverflowList/OverflowListCappedToolbar.tsx +0 -39
  34. package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.doc.mjs +0 -14
  35. package/templates/blocks/components/OverflowList/OverflowListMultiRowTags.tsx +0 -44
  36. package/templates/blocks/components/Thumbnail/ThumbnailElevated.doc.mjs +0 -14
  37. package/templates/blocks/components/Thumbnail/ThumbnailElevated.tsx +0 -31
@@ -36,85 +36,10 @@ export const docs = {
36
36
  },
37
37
  ],
38
38
  },
39
- {
40
- title: 'Choosing a level',
41
- category: 'foundations',
42
- content: [
43
- {
44
- type: 'prose',
45
- text: 'Pick the level by how far the surface sits from the page, not by how much shadow you want. Elevation encodes stacking order: a higher level means the surface is layered over more of the UI. Use exactly one level per surface, and only raise a surface above `none` when it actually sits above other content.',
46
- },
47
- {
48
- type: 'table',
49
- headers: ['Level', 'When to use', 'Examples'],
50
- rows: [
51
- [
52
- 'none',
53
- 'The component is flat and embedded in the surface — it is part of the page, not layered above it. This is the default for every surface except ChatComposer.',
54
- 'A Card in a grid, an inline Banner, a standard Button',
55
- ],
56
- [
57
- 'low',
58
- 'The component is in the normal page flow but should read as distinct from the background. Use for emphasis or to separate the component from the surface behind it — the component still sits on the page, it is not floating over other content.',
59
- 'A raised Card that needs emphasis, a ChatComposer, a resting Thumbnail',
60
- ],
61
- [
62
- 'med',
63
- 'The component sits over other content on the same page — it floats above nearby elements but not the whole screen.',
64
- 'A Popover, a floating Banner, a floating action Button',
65
- ],
66
- [
67
- 'high',
68
- 'The component is placed over the entire UI — it is the topmost layer and typically has a backdrop or takes focus from everything else.',
69
- 'A modal Dialog, a full-screen overlay surface',
70
- ],
71
- ],
72
- },
73
- {
74
- type: 'prose',
75
- text: 'If two surfaces overlap, the one on top takes the higher level. If a surface does not overlap anything, it is `none` or `low` — never `med` or `high`.',
76
- },
77
- ],
78
- },
79
- {
80
- title: 'The elevation prop',
81
- category: 'foundations',
82
- content: [
83
- {
84
- type: 'prose',
85
- text: 'Configurable surfaces expose a single `elevation` prop instead of asking consumers to hand-write a box-shadow. It takes the graded enum `none | low | med | high`, narrowed per component to the steps that surface needs: Card, ClickableCard, SelectableCard, Button, IconButton, ButtonGroup, Thumbnail, and Banner expose the full scale, while ChatComposer exposes only `none | low`. `none` is a flat literal (`box-shadow: none`); the other levels map to the `--shadow-*` tokens above, so a surface stays theme-agnostic.',
86
- },
87
- {
88
- type: 'prose',
89
- text: 'Prop defaults preserve current appearance: every surface defaults to `none` except ChatComposer, which defaults to `low` to keep its raised look. Set `elevation="none"` to flatten it — the flat composer draws a border with the same rest / hover / focus treatment as a text input. Thumbnail keeps its existing hover-only shadow at the `none` default.',
90
- },
91
- {
92
- type: 'code',
93
- lang: 'tsx',
94
- label: 'Raising a surface with the elevation prop',
95
- code: `// Flat by default — raise only when the surface needs to float.
96
- <Card elevation="low">Raised card</Card>
97
-
98
- // A floating action button.
99
- <IconButton icon={<Icon icon="add" />} label="New" variant="primary" elevation="med" />
100
-
101
- // Flatten the composer (defaults to 'low').
102
- <ChatComposer elevation="none" onSubmit={handleSubmit} />`,
103
- },
104
- {
105
- type: 'prose',
106
- text: 'Always float — no prop: intrinsic overlays (Dialog, Popover, Tooltip, Toast, HoverCard, DropdownMenu, and the components that compose them) bake their elevation in and never expose the prop. Flow content (inputs, Text, layout) is never elevated; input fields use inset rings, which are a separate concept from elevation.',
107
- },
108
- ],
109
- },
110
39
  {
111
40
  title: 'Usage',
112
41
  category: 'foundations',
113
42
  content: [
114
- {
115
- type: 'prose',
116
- text: 'When building a custom surface, read elevation from the token scale rather than a hand-rolled shadow — the same tokens the elevation prop resolves to.',
117
- },
118
43
  {
119
44
  type: 'code',
120
45
  lang: 'tsx',
@@ -143,8 +68,7 @@ const styles = stylex.create({
143
68
  type: 'list',
144
69
  style: 'do',
145
70
  items: [
146
- 'Reach for the `elevation` prop on a configurable surface before writing any custom shadow.',
147
- 'Choose the level by how far the surface sits from the page: `none` when flat/embedded, `low` when in-flow but distinct, `med` when over page content, `high` when over the whole UI. See "Choosing a level".',
71
+ 'Match elevation to interaction context: low for tooltips, med for dropdowns, high for dialogs.',
148
72
  'Use inset shadows for input focus/selection states; they compose better than outlines.',
149
73
  ],
150
74
  },
@@ -152,8 +76,6 @@ const styles = stylex.create({
152
76
  type: 'list',
153
77
  style: 'dont',
154
78
  items: [
155
- 'Hand-write a `box-shadow` in app code — set the `elevation` prop or read `shadowVars` instead.',
156
- 'Raise a surface that does not sit above other content — a non-overlapping surface is `none` or `low`, never `med` or `high`.',
157
79
  'Stack multiple elevation levels on the same element.',
158
80
  'Use elevation shadows for decorative borders. Use --color-border tokens instead.',
159
81
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/cli",
3
- "version": "0.1.8-canary.3bb5896",
3
+ "version": "0.1.8-canary.4a7ce02",
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",
@@ -79,10 +79,10 @@
79
79
  "zod": "^4.4.3"
80
80
  },
81
81
  "peerDependencies": {
82
- "@astryxdesign/charts": "0.1.8-canary.3bb5896",
83
- "@astryxdesign/core": "0.1.8-canary.3bb5896",
84
- "@astryxdesign/lab": "0.1.8-canary.3bb5896",
85
- "@astryxdesign/theme-neutral": "0.1.8-canary.3bb5896",
82
+ "@astryxdesign/charts": "0.1.8-canary.4a7ce02",
83
+ "@astryxdesign/core": "0.1.8-canary.4a7ce02",
84
+ "@astryxdesign/lab": "0.1.8-canary.4a7ce02",
85
+ "@astryxdesign/theme-neutral": "0.1.8-canary.4a7ce02",
86
86
  "gpt-tokenizer": "^3.4.0"
87
87
  },
88
88
  "peerDependenciesMeta": {
@@ -100,17 +100,16 @@
100
100
  }
101
101
  },
102
102
  "devDependencies": {
103
- "@astryxdesign/charts": "0.1.8-canary.3bb5896",
104
- "@astryxdesign/core": "0.1.8-canary.3bb5896",
105
- "@astryxdesign/lab": "0.1.8-canary.3bb5896",
106
- "@astryxdesign/theme-neutral": "0.1.8-canary.3bb5896",
103
+ "@astryxdesign/charts": "0.1.8-canary.4a7ce02",
104
+ "@astryxdesign/core": "0.1.8-canary.4a7ce02",
105
+ "@astryxdesign/lab": "0.1.8-canary.4a7ce02",
106
+ "@astryxdesign/theme-neutral": "0.1.8-canary.4a7ce02",
107
107
  "gpt-tokenizer": "^3.4.0"
108
108
  },
109
109
  "scripts": {
110
110
  "astryx": "node bin/astryx.mjs",
111
111
  "postinstall": "node scripts/postinstall.mjs",
112
112
  "typecheck:template-docs": "tsc --project tsconfig.template-docs.json",
113
- "typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json",
114
- "typecheck:strict": "tsc --project tsconfig.strict.json"
113
+ "typecheck:json-api": "tsc --project tsconfig.json-api.json && tsc --project tsconfig.api-contract.json"
115
114
  }
116
115
  }
package/src/api/error.mjs CHANGED
@@ -14,7 +14,7 @@
14
14
  import {ERROR_CODES} from '../lib/error-codes.mjs';
15
15
 
16
16
  export class AstryxError extends Error {
17
- /** @type {import('../types/base').Suggestion[] | undefined} */
17
+ /** @type {Array<{name: string, reason: string}> | undefined} */
18
18
  suggestions;
19
19
 
20
20
  /**
@@ -26,7 +26,7 @@ export class AstryxError extends Error {
26
26
 
27
27
  /**
28
28
  * @param {string} message
29
- * @param {import('../types/base').Suggestion[]} [suggestions]
29
+ * @param {Array<{name: string, reason: string}>} [suggestions]
30
30
  * @param {string} [code] - Stable error code. Defaults to ERR_UNKNOWN.
31
31
  */
32
32
  constructor(message, suggestions, code) {
@@ -358,7 +358,7 @@ export function registerSwizzle(program) {
358
358
  const feedback = buildFeedback(dirName, owner.issuesUrl);
359
359
 
360
360
  if (json) {
361
- /** @type {import('../types/swizzle').SwizzleCopyResponse['data']} */
361
+ /** @type {Record<string, unknown>} */
362
362
  const payload = {
363
363
  component: dirName,
364
364
  package: owner.package,
@@ -55,8 +55,8 @@ import {ERROR_CODES} from './error-codes.mjs';
55
55
 
56
56
  /**
57
57
  * Suggestion object — matches the shape used by API errors and the JSON
58
- * envelope's `suggestions` field. Canonical definition lives in types/base.
59
- * @typedef {import('../types/base').Suggestion} Suggestion
58
+ * envelope's `suggestions` field.
59
+ * @typedef {{name: string, reason?: string}} Suggestion
60
60
  */
61
61
 
62
62
  /**
@@ -102,7 +102,7 @@ export function buildHelpEnvelope(cmd) {
102
102
  * for both success and error).
103
103
  *
104
104
  * @param {string} message
105
- * @param {import('../types/base').Suggestion[]} [suggestions]
105
+ * @param {Array<{name: string, reason: string}>} [suggestions]
106
106
  * @param {string} [code] - Stable machine-readable error code (error-codes.mjs).
107
107
  */
108
108
  function emitJsonError(message, suggestions, code) {
package/src/lib/json.mjs CHANGED
@@ -112,10 +112,10 @@ export function jsonOut(type, data, meta) {
112
112
  * consumers can branch on it unconditionally.
113
113
  *
114
114
  * @param {unknown} err
115
- * @param {import('../types/base').Suggestion[]} [suggestions]
115
+ * @param {Array<{name: string, reason: string}>} [suggestions]
116
116
  * @param {string} [code] - Explicit stable error code. Overrides any code
117
117
  * carried on a thrown Error.
118
- * @returns {{apiVersion: number, error: string, code: string, suggestions?: import('../types/base').Suggestion[]}}
118
+ * @returns {{apiVersion: number, error: string, code: string, suggestions?: Array<{name: string, reason: string}>}}
119
119
  */
120
120
  export function toErrorEnvelope(err, suggestions, code) {
121
121
  const message =
@@ -136,7 +136,7 @@ export function toErrorEnvelope(err, suggestions, code) {
136
136
  /**
137
137
  * Output a structured JSON error and exit.
138
138
  * @param {string} message
139
- * @param {import('../types/base').Suggestion[]} [suggestions]
139
+ * @param {Array<{name: string, reason: string}>} [suggestions]
140
140
  * @param {string} [code] - Stable machine-readable error code (error-codes.mjs).
141
141
  */
142
142
  export function jsonError(message, suggestions, code) {
@@ -44,14 +44,17 @@ import type {
44
44
  import type {SearchResponse, SearchDomain} from './search';
45
45
  import type {ErrorCode} from './error-codes';
46
46
  import type {DoctorResponse} from './doctor';
47
- import type {Suggestion} from './base';
48
47
 
49
48
  /** Structured API error with a stable machine-readable code. */
50
49
  export declare class AstryxError extends Error {
51
50
  /** Stable error code; consumers branch on this, never the message. */
52
51
  code: ErrorCode;
53
- suggestions?: Suggestion[];
54
- constructor(message: string, suggestions?: Suggestion[], code?: ErrorCode);
52
+ suggestions?: Array<{name: string; reason: string}>;
53
+ constructor(
54
+ message: string,
55
+ suggestions?: Array<{name: string; reason: string}>,
56
+ code?: ErrorCode,
57
+ );
55
58
  }
56
59
 
57
60
  // ── Component ────────────────────────────────────────────────────────
@@ -97,7 +100,9 @@ export interface DocsOptions {
97
100
  }
98
101
 
99
102
  type DocsResult =
100
- DocsListResponse | DocsDetailResponse | DocsDetailSectionResponse;
103
+ | DocsListResponse
104
+ | DocsDetailResponse
105
+ | DocsDetailSectionResponse;
101
106
 
102
107
  export declare function docs(
103
108
  topic?: string,
@@ -11,12 +11,10 @@
11
11
  import type {
12
12
  ComponentListResponse,
13
13
  ComponentBriefResponse,
14
- ComponentFullResponse,
15
14
  ComponentDetailResponse,
16
15
  ComponentDetailPropsResponse,
17
16
  ComponentDetailSourceResponse,
18
17
  ComponentDetailShowcaseResponse,
19
- ComponentDetailBlocksResponse,
20
18
  } from './component';
21
19
  import type {
22
20
  DiscoverListResponse,
@@ -44,28 +42,13 @@ import type {
44
42
  } from './hook';
45
43
  import type {SwizzleListResponse, SwizzleCopyResponse} from './swizzle';
46
44
  import type {ThemeBuildResponse} from './theme';
47
- import type {
48
- UpgradeListResponse,
49
- UpgradeRunResponse,
50
- UpgradeStatusResponse,
51
- } from './upgrade';
45
+ import type {UpgradeListResponse, UpgradeRunResponse} from './upgrade';
52
46
  import type {SearchResponse} from './search';
53
- import type {BuildHelpResponse} from './build';
54
47
  import type {ErrorCode} from './error-codes';
55
48
  import type {ManifestResponse} from './manifest';
56
49
  import type {DoctorResponse} from './doctor';
57
50
  import type {ValidateIntegrationResponse} from './validate-integration';
58
51
 
59
- /**
60
- * A "did you mean…" suggestion attached to an error. `reason` is optional:
61
- * some call sites emit bare `{name}` (e.g. a list of candidate component names)
62
- * with no per-item explanation.
63
- */
64
- export interface Suggestion {
65
- name: string;
66
- reason?: string;
67
- }
68
-
69
52
  /**
70
53
  * Structured error. Check `'error' in result` to discriminate.
71
54
  *
@@ -75,7 +58,7 @@ export interface Suggestion {
75
58
  export interface CLIError {
76
59
  error: string;
77
60
  code: ErrorCode;
78
- suggestions?: Suggestion[];
61
+ suggestions?: Array<{name: string; reason: string}>;
79
62
  }
80
63
 
81
64
  /** Returned by the fallback hook for commands without --json support. */
@@ -91,12 +74,10 @@ export type CLIResult<T> = T | CLIError | CLIUnsupportedError;
91
74
  export type CLIAnyResponse =
92
75
  | ComponentListResponse
93
76
  | ComponentBriefResponse
94
- | ComponentFullResponse
95
77
  | ComponentDetailResponse
96
78
  | ComponentDetailPropsResponse
97
79
  | ComponentDetailSourceResponse
98
80
  | ComponentDetailShowcaseResponse
99
- | ComponentDetailBlocksResponse
100
81
  | DiscoverListResponse
101
82
  | DiscoverDetailResponse
102
83
  | DiscoverDetailDocResponse
@@ -118,9 +99,7 @@ export type CLIAnyResponse =
118
99
  | ThemeBuildResponse
119
100
  | UpgradeListResponse
120
101
  | UpgradeRunResponse
121
- | UpgradeStatusResponse
122
102
  | SearchResponse
123
- | BuildHelpResponse
124
103
  | ManifestResponse
125
104
  | DoctorResponse
126
105
  | ValidateIntegrationResponse;
@@ -150,7 +129,7 @@ export function jsonOut<T extends CLIResponseType>(
150
129
  */
151
130
  export function jsonError(
152
131
  message: string,
153
- suggestions?: Suggestion[],
132
+ suggestions?: Array<{name: string; reason: string}>,
154
133
  code?: ErrorCode,
155
134
  ): never;
156
135
 
@@ -10,7 +10,6 @@ export * from './swizzle';
10
10
  export * from './theme';
11
11
  export * from './upgrade';
12
12
  export * from './search';
13
- export * from './build';
14
13
  export * from './error-codes';
15
14
  export * from './manifest';
16
15
  export * from './doctor';
@@ -29,13 +29,9 @@ export interface SwizzleCopyResponse {
29
29
  type: 'swizzle.copy';
30
30
  data: {
31
31
  component: string;
32
- /** Owner package the component source was copied from. */
33
- package: string;
34
32
  outputDir: string;
35
33
  filesCopied: number;
36
34
  files: string[];
37
- /** Whether any copied file uses StyleX (requires build-time setup). */
38
- usesStyleX: boolean;
39
35
  feedback?: SwizzleFeedback;
40
36
  };
41
37
  }
@@ -10,41 +10,15 @@
10
10
  import * as fs from 'node:fs';
11
11
  import * as path from 'node:path';
12
12
 
13
- /**
14
- * A package manager we can install with and run binaries through.
15
- * @typedef {'yarn' | 'pnpm' | 'bun' | 'npm'} PackageManager
16
- */
17
-
18
- /**
19
- * Result of package-manager detection. `'npx'` is the sentinel for "nothing
20
- * detected" — no lockfile, no `packageManager` field, no runner user-agent —
21
- * so callers fall back to npm/npx. It is a distinct value from {@link PackageManager}
22
- * because it means "undetected", not "npm was chosen".
23
- * @typedef {PackageManager | 'npx'} DetectedPackageManager
24
- */
25
-
26
- /** @type {readonly PackageManager[]} */
27
- const KNOWN_PMS = ['yarn', 'pnpm', 'bun', 'npm'];
28
-
29
- /**
30
- * Narrow an arbitrary string to a known {@link PackageManager}.
31
- * @param {string} name
32
- * @returns {name is PackageManager}
33
- */
34
- function isKnownPackageManager(name) {
35
- return /** @type {readonly string[]} */ (KNOWN_PMS).includes(name);
36
- }
37
-
38
13
  /**
39
14
  * Detect the package manager used in a project directory.
40
15
  * Walks up from targetDir looking for lockfiles.
41
16
  *
42
- * Returns `'npx'` when nothing can be detected — see {@link DetectedPackageManager}.
43
- *
44
17
  * @param {string} [targetDir=process.cwd()]
45
- * @returns {DetectedPackageManager}
18
+ * @returns {'yarn'|'pnpm'|'bun'|'npm'}
46
19
  */
47
20
  export function detectPackageManager(targetDir = process.cwd()) {
21
+ const KNOWN_PMS = new Set(['yarn', 'pnpm', 'bun', 'npm']);
48
22
  let dir = path.resolve(targetDir);
49
23
  const root = path.parse(dir).root;
50
24
 
@@ -62,7 +36,7 @@ export function detectPackageManager(targetDir = process.cwd()) {
62
36
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
63
37
  if (pkg.packageManager) {
64
38
  const name = pkg.packageManager.split('@')[0];
65
- if (isKnownPackageManager(name)) return name;
39
+ if (KNOWN_PMS.has(name)) return name;
66
40
  }
67
41
  } catch {
68
42
  // Best-effort: unreadable/invalid package.json — keep walking up.
@@ -76,7 +50,7 @@ export function detectPackageManager(targetDir = process.cwd()) {
76
50
  const ua = process.env.npm_config_user_agent;
77
51
  if (ua) {
78
52
  const name = ua.split('/')[0];
79
- if (isKnownPackageManager(name)) return name;
53
+ if (KNOWN_PMS.has(name)) return name;
80
54
  }
81
55
 
82
56
  return 'npx';
@@ -18,9 +18,9 @@ import {getCliInvocation} from './package-manager.mjs';
18
18
 
19
19
  /**
20
20
  * Read the latest available version from local signals.
21
- * No network calls — purely an env-var check ($ASTRYX_LATEST_VERSION),
22
- * so it takes no arguments.
21
+ * No network calls — purely filesystem and env var checks.
23
22
  *
23
+ * @param {string} [cwd] - Project directory (default: process.cwd())
24
24
  * @returns {string|null} Latest version string, or null if unknown
25
25
  */
26
26
  export function getLatestVersion() {
@@ -64,7 +64,7 @@ export function getInstalledVersion(cwd = process.cwd()) {
64
64
  * @returns {string|null} FYI hint string, or null if up to date / unknown
65
65
  */
66
66
  export function checkForUpdate(cwd = process.cwd()) {
67
- const latest = getLatestVersion();
67
+ const latest = getLatestVersion(cwd);
68
68
  if (!latest) return null;
69
69
 
70
70
  // Persist for subsequent commands in this shell session
@@ -33,12 +33,12 @@ afterEach(() => {
33
33
  describe('getLatestVersion', () => {
34
34
  it('reads from ASTRYX_LATEST_VERSION env var', () => {
35
35
  process.env.ASTRYX_LATEST_VERSION = '0.0.8';
36
- expect(getLatestVersion()).toBe('0.0.8');
36
+ expect(getLatestVersion(tmpDir)).toBe('0.0.8');
37
37
  });
38
38
 
39
39
  it('ignores invalid env var values', () => {
40
40
  process.env.ASTRYX_LATEST_VERSION = 'not-a-version';
41
- expect(getLatestVersion()).toBeNull();
41
+ expect(getLatestVersion(tmpDir)).toBeNull();
42
42
  });
43
43
 
44
44
  it('returns null when no signals exist', () => {
@@ -46,7 +46,7 @@ describe('getLatestVersion', () => {
46
46
  path.join(tmpDir, 'package.json'),
47
47
  JSON.stringify({name: 'test'}),
48
48
  );
49
- expect(getLatestVersion()).toBeNull();
49
+ expect(getLatestVersion(tmpDir)).toBeNull();
50
50
  });
51
51
  });
52
52
 
@@ -1,23 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * Build command JSON responses.
5
- *
6
- * `astryx build` is the "assemble a page" verb. With no query it emits the
7
- * workflow playbook; with a query it delegates to the search pipeline and
8
- * emits a `search` response (see search.d.ts).
9
- *
10
- * Invocation -> type discriminator
11
- * ---------------------------------------------------
12
- * xds --json build -> build.help
13
- * xds --json build "<idea>" -> search
14
- */
15
-
16
- /** xds --json build (no query) — the "how to build a page" playbook signal. */
17
- export interface BuildHelpResponse {
18
- type: 'build.help';
19
- data: {
20
- /** Always true; marks this envelope as the playbook rather than a result set. */
21
- playbook: true;
22
- };
23
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'Banner',
7
- name: 'Banner — Floating',
8
- displayName: 'Banner — Floating',
9
- description:
10
- 'A floating banner raised with `elevation="med"`. Banners are inline by default; raise one when it should read as an overlay above content.',
11
- isReady: true,
12
- aspectRatio: 16 / 9,
13
- componentsUsed: ['Banner'],
14
- };
@@ -1,16 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {Banner} from '@astryxdesign/core/Banner';
6
-
7
- export default function BannerFloating() {
8
- return (
9
- <Banner
10
- status="info"
11
- title="You have unsaved changes"
12
- description="A raised banner reads as an overlay floating above the page."
13
- elevation="med"
14
- />
15
- );
16
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'Button',
7
- name: 'Button — Floating',
8
- displayName: 'Button — Floating',
9
- description:
10
- 'A floating action button raised with `elevation="med"`. Use elevation for buttons that hover above content, like a FAB.',
11
- isReady: true,
12
- aspectRatio: 16 / 9,
13
- componentsUsed: ['Button', 'Icon', 'Layout', 'Text'],
14
- };
@@ -1,37 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {Button} from '@astryxdesign/core/Button';
6
- import {Icon} from '@astryxdesign/core/Icon';
7
- import {Stack} from '@astryxdesign/core/Layout';
8
- import {Text} from '@astryxdesign/core/Text';
9
- import {PlusIcon} from '@heroicons/react/24/outline';
10
-
11
- const VARIANTS = [
12
- {variant: 'primary' as const, label: 'Primary'},
13
- {variant: 'secondary' as const, label: 'Secondary'},
14
- {variant: 'ghost' as const, label: 'Ghost'},
15
- {variant: 'destructive' as const, label: 'Destructive'},
16
- ];
17
-
18
- export default function ButtonFloating() {
19
- return (
20
- <Stack direction="vertical" gap={4}>
21
- <Text type="supporting" color="secondary">
22
- Floating action buttons — raised above content with `elevation="med"`
23
- </Text>
24
- <Stack direction="horizontal" gap={3} vAlign="center">
25
- {VARIANTS.map(({variant, label}) => (
26
- <Button
27
- key={variant}
28
- label={label}
29
- variant={variant}
30
- icon={<Icon icon={PlusIcon} />}
31
- elevation="med"
32
- />
33
- ))}
34
- </Stack>
35
- </Stack>
36
- );
37
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'ButtonGroup',
7
- name: 'ButtonGroup — Floating',
8
- displayName: 'ButtonGroup — Floating',
9
- description:
10
- 'A grouped action bar raised with `elevation="med"`. The connected buttons share one surface, so the shadow lifts them as a unit.',
11
- isReady: true,
12
- aspectRatio: 16 / 9,
13
- componentsUsed: ['ButtonGroup', 'Button', 'Layout', 'Text'],
14
- };
@@ -1,23 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {ButtonGroup} from '@astryxdesign/core/ButtonGroup';
6
- import {Button} from '@astryxdesign/core/Button';
7
- import {Stack} from '@astryxdesign/core/Layout';
8
- import {Text} from '@astryxdesign/core/Text';
9
-
10
- export default function ButtonGroupFloating() {
11
- return (
12
- <Stack direction="vertical" gap={3} hAlign="start">
13
- <Text type="supporting" color="secondary">
14
- The whole group shares one raised surface — a floating action bar
15
- </Text>
16
- <ButtonGroup label="Zoom controls" elevation="med">
17
- <Button label="Zoom out" />
18
- <Button label="Reset" />
19
- <Button label="Zoom in" />
20
- </ButtonGroup>
21
- </Stack>
22
- );
23
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'Card',
7
- name: 'Card — Elevations',
8
- displayName: 'Card — Elevations',
9
- description:
10
- 'The four elevation levels side by side. Cards are flat by default; raise a card with `elevation` only when it needs to float above surrounding content.',
11
- isReady: true,
12
- aspectRatio: 16 / 9,
13
- componentsUsed: ['Card', 'Layout', 'Text'],
14
- };
@@ -1,32 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {Card} from '@astryxdesign/core/Card';
6
- import {Stack} from '@astryxdesign/core/Layout';
7
- import {Grid} from '@astryxdesign/core/Grid';
8
- import {Text, Heading} from '@astryxdesign/core/Text';
9
-
10
- const LEVELS = [
11
- {elevation: 'none' as const, caption: 'Flat — sits on the page (default)'},
12
- {elevation: 'low' as const, caption: 'Low — lifts off slightly'},
13
- {elevation: 'med' as const, caption: 'Med — clearly raised'},
14
- {elevation: 'high' as const, caption: 'High — floats well above'},
15
- ];
16
-
17
- export default function CardElevations() {
18
- return (
19
- <Grid columns={2} gap={4} width={520}>
20
- {LEVELS.map(({elevation, caption}) => (
21
- <Card key={elevation} elevation={elevation}>
22
- <Stack direction="vertical" gap={1}>
23
- <Heading level={4}>elevation=&quot;{elevation}&quot;</Heading>
24
- <Text type="supporting" color="secondary">
25
- {caption}
26
- </Text>
27
- </Stack>
28
- </Card>
29
- ))}
30
- </Grid>
31
- );
32
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'ClickableCard',
7
- name: 'ClickableCard — Elevated',
8
- displayName: 'Clickable Card — Elevated',
9
- description:
10
- 'A clickable card raised with `elevation="med"` so the shadow signals the whole surface is interactive.',
11
- isReady: true,
12
- aspectRatio: 1,
13
- componentsUsed: ['ClickableCard', 'Layout', 'Text'],
14
- };
@@ -1,21 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {ClickableCard} from '@astryxdesign/core/ClickableCard';
6
- import {Stack} from '@astryxdesign/core/Layout';
7
- import {Text, Heading} from '@astryxdesign/core/Text';
8
-
9
- export default function ClickableCardElevated() {
10
- return (
11
- <ClickableCard label="Open report" href="#" elevation="med" width={320}>
12
- <Stack direction="vertical" gap={2}>
13
- <Heading level={4}>Quarterly report</Heading>
14
- <Text type="body" color="secondary">
15
- A raised shadow signals the whole card is clickable, lifting it above
16
- the surrounding content.
17
- </Text>
18
- </Stack>
19
- </ClickableCard>
20
- );
21
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'SelectableCard',
7
- name: 'SelectableCard — Elevated',
8
- displayName: 'Selectable Card — Elevated',
9
- description:
10
- 'Raised selectable cards with `elevation="low"`. The inset selection ring composes on top of the shadow, so a selected card keeps its elevation.',
11
- isReady: true,
12
- aspectRatio: 2,
13
- componentsUsed: ['SelectableCard', 'Layout', 'Text'],
14
- };
@@ -1,37 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {useState} from 'react';
6
- import {SelectableCard} from '@astryxdesign/core/SelectableCard';
7
- import {Grid} from '@astryxdesign/core/Grid';
8
- import {Text} from '@astryxdesign/core/Text';
9
-
10
- const PLANS = [
11
- {id: 'starter', name: 'Starter'},
12
- {id: 'pro', name: 'Pro'},
13
- ];
14
-
15
- export default function SelectableCardElevated() {
16
- const [selected, setSelected] = useState('pro');
17
-
18
- return (
19
- <Grid columns={2} gap={3} width={420}>
20
- {PLANS.map(plan => (
21
- <SelectableCard
22
- key={plan.id}
23
- label={plan.name}
24
- isSelected={selected === plan.id}
25
- onChange={() => setSelected(plan.id)}
26
- elevation="low">
27
- <Text type="body" weight="bold">
28
- {plan.name}
29
- </Text>
30
- <Text type="supporting" color="secondary">
31
- The resting shadow stays put — the selection ring layers on top.
32
- </Text>
33
- </SelectableCard>
34
- ))}
35
- </Grid>
36
- );
37
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'ChatComposer',
7
- name: 'ChatComposer — Flat',
8
- displayName: 'ChatComposer — Flat',
9
- description:
10
- 'The composer is raised by default (`elevation="low"`). Set `elevation="none"` to flatten it — it then draws a border with the same rest / hover / focus treatment as a text input.',
11
- isReady: true,
12
- aspectRatio: 16 / 9,
13
- componentsUsed: ['ChatComposer', 'Layout', 'Text'],
14
- };
@@ -1,75 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {ChatComposer} from '@astryxdesign/core/Chat';
6
- import {Button} from '@astryxdesign/core/Button';
7
- import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
8
- import {Icon} from '@astryxdesign/core/Icon';
9
- import {Stack} from '@astryxdesign/core/Layout';
10
- import {Text} from '@astryxdesign/core/Text';
11
- import {
12
- Cog6ToothIcon,
13
- MicrophoneIcon,
14
- SparklesIcon,
15
- } from '@heroicons/react/24/outline';
16
-
17
- export default function ChatComposerFlat() {
18
- return (
19
- <Stack direction="vertical" gap={2} style={{width: '100%', maxWidth: 450}}>
20
- <Text type="supporting" color="secondary">
21
- elevation=&quot;none&quot; — flat, with a text-input-style border and
22
- focus ring
23
- </Text>
24
- <ChatComposer
25
- elevation="none"
26
- onSubmit={value => {
27
- console.log('Sent:', value);
28
- }}
29
- footerActions={
30
- <>
31
- <DropdownMenu
32
- button={{
33
- label: 'Auto',
34
- variant: 'ghost',
35
- size: 'md',
36
- icon: <Icon icon={SparklesIcon} size="sm" />,
37
- children: 'Auto',
38
- }}
39
- menuWidth={200}
40
- items={[
41
- {label: 'Auto', onClick: () => {}},
42
- {label: 'Model A', onClick: () => {}},
43
- {label: 'Model B', onClick: () => {}},
44
- ]}
45
- />
46
- <DropdownMenu
47
- button={{
48
- label: 'Settings',
49
- variant: 'ghost',
50
- size: 'md',
51
- icon: <Icon icon={Cog6ToothIcon} size="sm" />,
52
- children: 'Settings',
53
- }}
54
- menuWidth={200}
55
- items={[
56
- {label: 'Preferences', onClick: () => {}},
57
- {label: 'Keyboard shortcuts', onClick: () => {}},
58
- {label: 'About', onClick: () => {}},
59
- ]}
60
- />
61
- </>
62
- }
63
- sendActions={
64
- <Button
65
- label="Microphone"
66
- variant="ghost"
67
- size="md"
68
- icon={<Icon icon={MicrophoneIcon} />}
69
- isIconOnly
70
- />
71
- }
72
- />
73
- </Stack>
74
- );
75
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'IconButton',
7
- name: 'IconButton — Floating',
8
- displayName: 'IconButton — Floating',
9
- description:
10
- 'A floating action button (FAB) — the most common floating-button shape is icon-only. Raised here with `elevation="high"`.',
11
- isReady: true,
12
- aspectRatio: 4 / 3,
13
- componentsUsed: ['IconButton', 'Icon', 'Layout', 'Text'],
14
- };
@@ -1,37 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {IconButton} from '@astryxdesign/core/IconButton';
6
- import {Icon} from '@astryxdesign/core/Icon';
7
- import {Stack} from '@astryxdesign/core/Layout';
8
- import {Text} from '@astryxdesign/core/Text';
9
- import {PlusIcon} from '@heroicons/react/24/outline';
10
-
11
- const VARIANTS = [
12
- {variant: 'primary' as const, label: 'Primary'},
13
- {variant: 'secondary' as const, label: 'Secondary'},
14
- {variant: 'ghost' as const, label: 'Ghost'},
15
- {variant: 'destructive' as const, label: 'Destructive'},
16
- ];
17
-
18
- export default function IconButtonFloating() {
19
- return (
20
- <Stack direction="vertical" gap={4}>
21
- <Text type="supporting" color="secondary">
22
- FABs are usually icon-only — raise one with `elevation="high"`
23
- </Text>
24
- <Stack direction="horizontal" gap={3} vAlign="center">
25
- {VARIANTS.map(({variant, label}) => (
26
- <IconButton
27
- key={variant}
28
- label={label}
29
- variant={variant}
30
- icon={<Icon icon={PlusIcon} />}
31
- elevation="high"
32
- />
33
- ))}
34
- </Stack>
35
- </Stack>
36
- );
37
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'OverflowList',
7
- name: 'OverflowList — Capped Toolbar',
8
- displayName: 'OverflowList — Capped Toolbar',
9
- description:
10
- 'maxVisibleItems caps the row at three actions even when more would fit; the rest move to a dropdown',
11
- isReady: true,
12
- aspectRatio: 4 / 3,
13
- componentsUsed: ['OverflowList', 'Button', 'DropdownMenu', 'Card', 'Center'],
14
- };
@@ -1,39 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {OverflowList} from '@astryxdesign/core/OverflowList';
6
- import {Button} from '@astryxdesign/core/Button';
7
- import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
8
- import {Card} from '@astryxdesign/core/Card';
9
- import {Center} from '@astryxdesign/core/Center';
10
-
11
- const actions = ['Save', 'Edit', 'Duplicate', 'Share', 'Archive', 'Delete'];
12
-
13
- export default function OverflowListCappedToolbar() {
14
- return (
15
- <Center width={420}>
16
- <Card padding={2}>
17
- <OverflowList
18
- gap={2}
19
- maxVisibleItems={3}
20
- overflowRenderer={overflowItems => (
21
- <DropdownMenu
22
- button={{
23
- label: `+${overflowItems.length}`,
24
- variant: 'ghost',
25
- size: 'sm',
26
- }}
27
- items={overflowItems.map(({index}) => ({
28
- label: actions[index],
29
- }))}
30
- />
31
- )}>
32
- {actions.map(action => (
33
- <Button key={action} label={action} size="sm" />
34
- ))}
35
- </OverflowList>
36
- </Card>
37
- </Center>
38
- );
39
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'OverflowList',
7
- name: 'OverflowList — Multi-row Tags',
8
- displayName: 'OverflowList — Multi-row Tags',
9
- description:
10
- 'Tags wrap onto up to two rows with maxRows, then collapse the rest into a count badge',
11
- isReady: true,
12
- aspectRatio: 4 / 3,
13
- componentsUsed: ['OverflowList', 'Badge', 'Card'],
14
- };
@@ -1,44 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {OverflowList} from '@astryxdesign/core/OverflowList';
6
- import {Badge} from '@astryxdesign/core/Badge';
7
- import {Card} from '@astryxdesign/core/Card';
8
-
9
- const tags = [
10
- 'React',
11
- 'TypeScript',
12
- 'StyleX',
13
- 'Storybook',
14
- 'Vitest',
15
- 'Playwright',
16
- 'ESLint',
17
- 'Prettier',
18
- 'Vite',
19
- 'pnpm',
20
- ];
21
-
22
- export default function OverflowListMultiRowTags() {
23
- return (
24
- <Card
25
- padding={2}
26
- style={{
27
- resize: 'horizontal',
28
- overflow: 'hidden',
29
- minWidth: 120,
30
- width: 260,
31
- }}>
32
- <OverflowList
33
- gap={1}
34
- maxRows={2}
35
- overflowRenderer={overflowItems => (
36
- <Badge variant="neutral" label={`+${overflowItems.length}`} />
37
- )}>
38
- {tags.map(tag => (
39
- <Badge key={tag} variant="info" label={tag} />
40
- ))}
41
- </OverflowList>
42
- </Card>
43
- );
44
- }
@@ -1,14 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('../../../../../core/src/docs-types').TemplateDoc} */
4
- export const doc = {
5
- type: 'block',
6
- exampleFor: 'Thumbnail',
7
- name: 'Thumbnail — Elevated',
8
- displayName: 'Thumbnail — Elevated',
9
- description:
10
- 'Image tiles raised at rest with `elevation`. The default keeps the existing hover-only shadow; a set level lifts the tile off the page.',
11
- isReady: true,
12
- aspectRatio: 16 / 9,
13
- componentsUsed: ['Thumbnail', 'Layout', 'Text'],
14
- };
@@ -1,31 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- import {Thumbnail} from '@astryxdesign/core/Thumbnail';
6
- import {Stack} from '@astryxdesign/core/Layout';
7
- import {Text} from '@astryxdesign/core/Text';
8
-
9
- // Demo imagery is inlined as same-origin data URIs so the example is
10
- // self-contained. Thumbnail's remove-button overlay uses useImageMode, which
11
- // FETCHES the image to sample pixels (APCA) for contrast — a cross-origin CDN
12
- // URL without CORS headers cannot be sampled, so contrast detection fails
13
- // silently in hosted previews.
14
- const NIGHT_FOREST =
15
- 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAwFBMVEX18+H08uDy8d/y8N/v7t/m5NXl49Tk4tPc3NDX1cnW1cnW1cjW1MjV1MjV1MfR0cXIyLzEw7m8vLC3t68wM0QvM0MvM0IvMkIuMkEtMEErLz8iJjkhJTggJTcgJDcfJDYfIzYeIjUcITMbHzIWHiwYGzIXGjEWGjAWGTAVGS8VGC8UGC4UGC0UFy4TFy0TFywTFi0SFiwRFSsQFSkNFR4QFCoPFCkPFCgPEykOEygOEigNEicNEScMESYIDw8ECgr+gzZIAAABqElEQVR42uWQ61aCQBRGD5GylNBMKS9FZgNdNE0cyUvB+79VM8MoF9Fgplau2j/8nO8c9hqAD0ngDwjeJYH1lrfBWoCtYDboNu/Zv7qQYNap1bpCN1gxFh1dNe5WAsCScWqoWttbChAKvHZZ0W6XQoIFpW8ooFmLCG2Rl1BgaZAU5Ac8yg0RlC9nngCRQDFuhQRzinWmkCuYk3lxQoFrlskVqlcTMcEFvYIKoFbNfmEFuIypWaGGkt66tqZuEbjAHZl6SQVF1WqNvpDAHVktXTvR9JbgDZii1+tZI7cYMJXkCASvksB4L5VxHmAsibzgRRIYbjgfCgFDSeBZkh8W1HMIniSBR0mOQPCQA3RgBk4M5DhNZwdEcbKgLXy5iDiZg5gApRYbqTql2FZgMxAKeEWSNUFYx3s2SfU28HPWItrpySSwE2cEdqrYtxgmxgHGScFmHrAGk8SxczIxZnNC1McFOGshShyb4yxB9sI3CXDGnH0MLsD7F5LvvptEgH12Zg1Pn2WuHsjvrwt8308t+nwxT38sgiAE8/RTeagH+vx/F8SeF0Ja8Ak14ia/LgmpZQAAAABJRU5ErkJggg==';
16
- const GOLDEN_SUNSET =
17
- 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAkFBMVEX/+uv/7sj6tFr3sVn1r1nzrVjxq1jvqVftp1frpVbpo1bnoVXln1XjnVThm1TfmVPdl1PblVLZk1LXkVLVj1HTjVHRi1DPiVDNh0/LhU/Jg07HgU7Ff03DfU3Be0y/eUy9d0u7dUu5c0u2cEq0bkqybEmwakmuaEisZkiqZEeoYkemYEakXkaiXEVgLjQ8HCjaJlMkAAABI0lEQVR42u3Mx3qDMBSE0Z/03nsvdhxjcnn/twsYjISR5MUkO5+V9N2ZYUPEpogtEdsidkTsitgTsS/iQMShiCMRxyJORJyKOBNxLuJCxKWIKxHXIm5E3Iq4E3Ev4kHEo4gnEc8iXkS8inhLymrJBO8J2UIiw0dc5sRDfEbVRWr1o/pbMMWox9yz6zcLIzMbBTD2VaHu7fr1gjXGA3w5baj5+H0w6x09TDpdaP7rDZhn0sd3y89Y9fcHbPnoYdroZ2w6dQNmg6OHfG4QyrsBC8gdZpVQxtqF4M1mjepFURThTLNgMYsePxaX2WqY6D8GSm2grK2slbGBciHZ9hOE2smNpTuxenhieCdeH06E7qTqvYnImWTbbUSPlKL1wHrgTwZ+AWq+3H5MpRkwAAAAAElFTkSuQmCC';
18
-
19
- export default function ThumbnailElevated() {
20
- return (
21
- <Stack direction="vertical" gap={3}>
22
- <Text type="supporting" color="secondary">
23
- Raised tiles — `elevation` lifts an image off the page at rest
24
- </Text>
25
- <Stack direction="horizontal" gap={3} vAlign="center">
26
- <Thumbnail src={NIGHT_FOREST} alt="Forest at night" elevation="low" />
27
- <Thumbnail src={GOLDEN_SUNSET} alt="Golden sunset" elevation="high" />
28
- </Stack>
29
- </Stack>
30
- );
31
- }