@astryxdesign/cli 0.4.4 → 0.4.5-canary.1fdecb1
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 +5 -0
- package/assets/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +91 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +28 -4
- package/assets/docs/layout.doc.dense.mjs +275 -30
- package/assets/docs/layout.doc.mjs +412 -78
- package/assets/templates/blocks/components/BottomSheet/BottomSheetSnapPoints.doc.mjs +23 -0
- package/assets/templates/blocks/components/BottomSheet/BottomSheetSnapPoints.tsx +155 -0
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.doc.mjs +14 -0
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.tsx +76 -0
- package/package.json +9 -9
|
@@ -7,152 +7,486 @@ export const docs = {
|
|
|
7
7
|
title: 'Layout',
|
|
8
8
|
category: 'guide',
|
|
9
9
|
description:
|
|
10
|
-
'
|
|
10
|
+
'Build an app layout outside-in: scaffold the regions, structure the content, tune the spacing, then adapt across widths.',
|
|
11
11
|
|
|
12
12
|
sections: [
|
|
13
13
|
{
|
|
14
|
-
title: '
|
|
14
|
+
title: 'Overview',
|
|
15
15
|
content: [
|
|
16
16
|
{
|
|
17
17
|
type: 'prose',
|
|
18
|
-
text: '
|
|
18
|
+
text: 'Build a layout outside-in. Settle the shell and its region budgets before any content exists, then work inward. Content-first layouts drift into a padded column of cards, because every section ends up inventing its own container.',
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
type: 'list',
|
|
22
22
|
style: 'ordered',
|
|
23
23
|
items: [
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
24
|
+
'Scaffold: pick the shell, budget each region, and choose navigation',
|
|
25
|
+
'Structure: rank the content in each region, then pick the weakest container that groups it',
|
|
26
|
+
'Spacing: hold one content line per region, then tune gaps and density',
|
|
27
|
+
'Breakpoints: decide what each region does as width changes',
|
|
28
28
|
],
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
|
-
type: '
|
|
32
|
-
|
|
33
|
-
label: 'A three-region tool frame',
|
|
34
|
-
code: `// Frame: nav 256 | content flex | inspector 380 (resizable)
|
|
35
|
-
<AppShell sideNav={<SideNav>{/* nav items */}</SideNav>} contentPadding={0}>
|
|
36
|
-
<Layout>
|
|
37
|
-
<LayoutContent>{/* dense list or table, edge-to-edge */}</LayoutContent>
|
|
38
|
-
<LayoutPanel width={380} resizable={{minSizePx: 320, maxSizePx: 480}} hasDivider>
|
|
39
|
-
{/* inspector for the selected row */}
|
|
40
|
-
</LayoutPanel>
|
|
41
|
-
</Layout>
|
|
42
|
-
</AppShell>`,
|
|
31
|
+
type: 'prose',
|
|
32
|
+
text: 'This guide decides layout, not component APIs. Run `npx astryx build "<idea>"` to start from the closest template for your app type, and `npx astryx component <Name>` for a component\'s props.',
|
|
43
33
|
},
|
|
44
34
|
],
|
|
45
35
|
},
|
|
46
36
|
{
|
|
47
|
-
title: '
|
|
37
|
+
title: 'Scaffold',
|
|
48
38
|
content: [
|
|
39
|
+
{type: 'heading', level: 3, text: 'Shell'},
|
|
49
40
|
{
|
|
50
41
|
type: 'prose',
|
|
51
|
-
text: '
|
|
42
|
+
text: 'Pick the shell and budget its regions before any content exists. Structural widths are the one place raw px belongs; everything inside them uses the spacing scale.',
|
|
52
43
|
},
|
|
53
44
|
{
|
|
54
|
-
type: '
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
],
|
|
62
|
-
[
|
|
63
|
-
'Console / observability (metrics, logs, deploys)',
|
|
64
|
-
'AppShell + SideNav or TopNav + TabList',
|
|
65
|
-
'Card grid for dashboard widgets; Table for everything else',
|
|
66
|
-
],
|
|
67
|
-
[
|
|
68
|
-
'Messaging / feed',
|
|
69
|
-
'Column frame: rail + sidebar + stream + panel',
|
|
70
|
-
'Rows and bubbles. No cards in the stream',
|
|
71
|
-
],
|
|
72
|
-
[
|
|
73
|
-
'Media library / gallery',
|
|
74
|
-
'AppShell + TopNav; grid content',
|
|
75
|
-
'Card grid (ClickableCard) with dense metadata rows in detail views',
|
|
76
|
-
],
|
|
77
|
-
[
|
|
78
|
-
'Settings / forms',
|
|
79
|
-
'AppShell + SideNav or settings template',
|
|
80
|
-
'Sections with FormLayout; Card only to group dangerous or billing actions',
|
|
81
|
-
],
|
|
45
|
+
type: 'list',
|
|
46
|
+
style: 'ordered',
|
|
47
|
+
items: [
|
|
48
|
+
'Pick the frame: AppShell for nav apps, Layout with LayoutPanel in a start or end slot for multi-pane tools, or a plain content column for documents and forms',
|
|
49
|
+
'Give every fixed region a width budget, so no region has to negotiate for space at render time',
|
|
50
|
+
'Read the content to set fill or capped: tables, charts, and boards fill their region; prose, forms, and lists cap with Layout contentWidth so lines never over-stretch',
|
|
51
|
+
'Set each region container policy, rows or card grid, before writing content',
|
|
82
52
|
],
|
|
83
53
|
},
|
|
54
|
+
{
|
|
55
|
+
type: 'code',
|
|
56
|
+
lang: 'tsx',
|
|
57
|
+
label: 'A three-region tool frame',
|
|
58
|
+
code: `// Recommended budgets: SideNav 240–280, icon rail 64–72,
|
|
59
|
+
// side panel 340–420, filter rail 220–260.
|
|
60
|
+
<AppShell sideNav={<SideNav>{/* nav items */}</SideNav>}>
|
|
61
|
+
<Layout
|
|
62
|
+
content={<LayoutContent>{/* table fills its region */}</LayoutContent>}
|
|
63
|
+
end={<LayoutPanel width={380} hasDivider>{/* detail */}</LayoutPanel>}
|
|
64
|
+
/>
|
|
65
|
+
</AppShell>
|
|
66
|
+
|
|
67
|
+
// Capped instead: 640 suits text and forms, 960 mixed content.
|
|
68
|
+
// Dividers stay full-bleed.
|
|
69
|
+
<Layout
|
|
70
|
+
contentWidth={640}
|
|
71
|
+
content={<LayoutContent>{/* settings form */}</LayoutContent>}
|
|
72
|
+
/>`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: 'prose',
|
|
76
|
+
text: 'Verify: every region has a width budget, a fill-or-capped decision, and a container policy written down before any content exists.',
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
{type: 'heading', level: 3, text: 'Navigation'},
|
|
84
80
|
{
|
|
85
81
|
type: 'prose',
|
|
86
|
-
text: '
|
|
82
|
+
text: 'When the frame leaves navigation open, default to SideNav: it absorbs destinations you have not planned yet. App type and destination count are guiding indicators, not determining rules.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'list',
|
|
86
|
+
style: 'unordered',
|
|
87
|
+
items: [
|
|
88
|
+
'SideNav, the default: grouping needed, customizable nav, items with secondary actions, or nav that collapses. Trackers, consoles, and settings usually start here',
|
|
89
|
+
'TopNav: a shallow nav you expect to stay shallow, context that must stay visible, or a control- and filter-heavy page; add a TabList for a second level. Media libraries often sit here, over grid content',
|
|
90
|
+
'Both: a genuine suite, where TopNav carries ecosystem-wide concerns (context switcher, global search) and SideNav carries product nav',
|
|
91
|
+
'Neither: messaging and feeds use a column frame of rail, nav, stream, and panel',
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'code',
|
|
96
|
+
lang: 'tsx',
|
|
97
|
+
label: 'Navigation passed to AppShell',
|
|
98
|
+
code: `// Default: product nav on the side.
|
|
99
|
+
<AppShell sideNav={<SideNav>{/* items */}</SideNav>} />
|
|
100
|
+
|
|
101
|
+
// Shallow, stable nav on a control-heavy page.
|
|
102
|
+
<AppShell topNav={<TopNav>{/* items */}</TopNav>} />
|
|
103
|
+
|
|
104
|
+
// Suite: ecosystem concerns on top, product nav on the side.
|
|
105
|
+
<AppShell topNav={<TopNav />} sideNav={<SideNav />} />`,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'prose',
|
|
109
|
+
text: 'Verify: you can state the reason in one sentence, and the choice still holds if the nav doubles in size. `npx astryx build "<idea>"` names the closest template, and its `--skeleton` shows the pairing already wired up.',
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
{type: 'heading', level: 3, text: 'Best practices'},
|
|
113
|
+
{
|
|
114
|
+
type: 'list',
|
|
115
|
+
style: 'do',
|
|
116
|
+
items: [
|
|
117
|
+
'Decide the frame, region width budgets, and fill or capped before any content exists',
|
|
118
|
+
'State the reason for the navigation choice, or inherit the template pairing',
|
|
119
|
+
'Reserve raw px for structural widths; interior spacing uses tokens',
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: 'list',
|
|
124
|
+
style: 'dont',
|
|
125
|
+
items: [
|
|
126
|
+
'Build content-first and wrap each section in a Card, producing a padded scroll column',
|
|
127
|
+
'Stretch prose, forms, or lists across a wide region instead of capping with contentWidth',
|
|
128
|
+
'SideNav when the nav is really filters or controls, or must hold wide elements like breadcrumbs',
|
|
129
|
+
'TopNav when top-slot ownership is unclear, or the hierarchy is deep or still growing',
|
|
130
|
+
'Both bars when the ecosystem layer is thin, so the second only wastes space',
|
|
131
|
+
'Deviate from the template navigation pairing without a stated reason',
|
|
132
|
+
],
|
|
87
133
|
},
|
|
88
134
|
],
|
|
89
135
|
},
|
|
90
136
|
{
|
|
91
|
-
title: '
|
|
137
|
+
title: 'Structure',
|
|
92
138
|
content: [
|
|
139
|
+
{type: 'heading', level: 3, text: 'Type hierarchy'},
|
|
140
|
+
{
|
|
141
|
+
type: 'prose',
|
|
142
|
+
text: 'Give every region one lead, then rank the rest with weight and color rather than size. Content uses two text colors, primary and secondary, and nothing dimmer: body copy needs no props at all.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'list',
|
|
146
|
+
style: 'unordered',
|
|
147
|
+
items: [
|
|
148
|
+
'Body, the default: plain Text with no type, color, or size prop',
|
|
149
|
+
'Lead: Heading at the level matching page depth, or body Text at a heavier weight',
|
|
150
|
+
'Support: step to the secondary color, not to a smaller size',
|
|
151
|
+
'Metadata: the supporting type, or a StatusDot or Token instead of prose',
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: 'code',
|
|
156
|
+
lang: 'tsx',
|
|
157
|
+
label: 'Body copy, then one row of four ranks',
|
|
158
|
+
code: `// Body copy takes no props. Text already defaults to body
|
|
159
|
+
// size in the primary color.
|
|
160
|
+
<Text>Credentials rotate every 90 days</Text>
|
|
161
|
+
|
|
162
|
+
<HStack gap={2}>
|
|
163
|
+
<Text weight="semibold">Payments API</Text>
|
|
164
|
+
<StatusDot variant="success" label="Healthy" />
|
|
165
|
+
<Text color="secondary">v2.14</Text>
|
|
166
|
+
<Text type="supporting">edited 3h ago</Text>
|
|
167
|
+
</HStack>`,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
type: 'prose',
|
|
171
|
+
text: 'Squint test: blurred, you read lead, then support, then groups, in that order. If everything reads at once, raise contrast with weight and color, not borders and not smaller text.',
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
{type: 'heading', level: 3, text: 'Containers'},
|
|
175
|
+
{
|
|
176
|
+
type: 'prose',
|
|
177
|
+
text: 'Reach for the weakest container that reads as a group, and escalate only when it fails. Weakest to strongest:',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: 'list',
|
|
181
|
+
style: 'ordered',
|
|
182
|
+
items: [
|
|
183
|
+
'spacing and gap: related items inside one group. The default rhythm',
|
|
184
|
+
'Divider: peers in a dense list or toolbar, or fencing a header from a scrollable body',
|
|
185
|
+
'Section: the default page-structure unit, related content under a heading. No border',
|
|
186
|
+
'Card: a self-contained widget (KPI tile, chart, gallery entry), or a hard boundary around critical content',
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: 'code',
|
|
191
|
+
lang: 'tsx',
|
|
192
|
+
label: 'Section as the default unit',
|
|
193
|
+
code: `// Records are rows in one Section, not one Card each.
|
|
194
|
+
// Recommended row height: 32–40px.
|
|
195
|
+
<Section padding={0}>
|
|
196
|
+
<List header={<Heading level={3}>Members</Heading>} hasDividers>
|
|
197
|
+
{/* ListItem per member */}
|
|
198
|
+
</List>
|
|
199
|
+
</Section>`,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'prose',
|
|
203
|
+
text: 'Decision test: records render as rows, Table for columnar and List for single-line; a self-contained widget or hard boundary is a Card; everything else is a Section.',
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
{type: 'heading', level: 3, text: 'Headers and footers'},
|
|
207
|
+
{
|
|
208
|
+
type: 'prose',
|
|
209
|
+
text: 'A region can pin a header or footer while its body scrolls. Both are Layout slots, and padding set once on Layout reaches all three, so header, body, and footer share one content line.',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 'list',
|
|
213
|
+
style: 'unordered',
|
|
214
|
+
items: [
|
|
215
|
+
'LayoutHeader in the header slot: the region title and its primary action',
|
|
216
|
+
'Toolbar instead of LayoutHeader when the header carries interactive controls',
|
|
217
|
+
'LayoutFooter in the footer slot: actions that commit the work and must stay reachable',
|
|
218
|
+
'defaultHasDividers on Layout fences both at once, rather than hasDivider per slot',
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'code',
|
|
223
|
+
lang: 'tsx',
|
|
224
|
+
label: 'Pinned header and footer around a scrolling body',
|
|
225
|
+
code: `// padding on Layout reaches every slot, so all three align.
|
|
226
|
+
<Layout
|
|
227
|
+
padding={4}
|
|
228
|
+
defaultHasDividers
|
|
229
|
+
header={<LayoutHeader>{/* title + primary action */}</LayoutHeader>}
|
|
230
|
+
content={<LayoutContent>{/* rows */}</LayoutContent>}
|
|
231
|
+
footer={<LayoutFooter>{/* Save and Cancel */}</LayoutFooter>}
|
|
232
|
+
/>`,
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
type: 'prose',
|
|
236
|
+
text: 'Verify: scroll the body. The header and footer stay put, their dividers run full-bleed, and all three still share one left content line.',
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
{type: 'heading', level: 3, text: 'Side panels'},
|
|
240
|
+
{
|
|
241
|
+
type: 'prose',
|
|
242
|
+
text: 'Master-detail: selecting a row opens a fixed-width side panel instead of navigating away.',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
type: 'list',
|
|
246
|
+
style: 'unordered',
|
|
247
|
+
items: [
|
|
248
|
+
'LayoutPanel in the start or end slot of Layout, holding a fixed width budget',
|
|
249
|
+
'hasDivider to fence it from the content region; isScrollable so long detail scrolls on its own',
|
|
250
|
+
'For user-adjustable width, pair useResizable() with a ResizeHandle on the panel inner edge: after the panel in a start slot, before it in an end slot with isReversed',
|
|
251
|
+
'The handle then owns the divider, so the panel sets hasDivider={false}',
|
|
252
|
+
'Render an EmptyState when nothing is selected, so the region never collapses',
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: 'code',
|
|
257
|
+
lang: 'tsx',
|
|
258
|
+
label: 'Fixed panel, then the resizable form',
|
|
259
|
+
code: `// Recommended panel width: 340–420.
|
|
260
|
+
<Layout
|
|
261
|
+
content={<LayoutContent>{/* rows */}</LayoutContent>}
|
|
262
|
+
end={
|
|
263
|
+
<LayoutPanel width={380} hasDivider isScrollable label="Details">
|
|
264
|
+
{/* detail fields, or EmptyState when nothing is selected */}
|
|
265
|
+
</LayoutPanel>
|
|
266
|
+
}
|
|
267
|
+
/>
|
|
268
|
+
|
|
269
|
+
// Resizable: handle first in an end slot, and isReversed so
|
|
270
|
+
// dragging left widens the panel.
|
|
271
|
+
end={
|
|
272
|
+
<>
|
|
273
|
+
<ResizeHandle isReversed hasDivider resizable={panel.props}
|
|
274
|
+
label="Resize details" />
|
|
275
|
+
<LayoutPanel width={panel.size} hasDivider={false} />
|
|
276
|
+
</>
|
|
277
|
+
}`,
|
|
278
|
+
},
|
|
93
279
|
{
|
|
94
280
|
type: 'prose',
|
|
95
|
-
text: '
|
|
281
|
+
text: 'Verify: at narrow widths the panel yields width instead of squeezing content (see Breakpoints), and only one element between the regions draws a border.',
|
|
96
282
|
},
|
|
283
|
+
|
|
284
|
+
{type: 'heading', level: 3, text: 'Best practices'},
|
|
97
285
|
{
|
|
98
286
|
type: 'list',
|
|
99
287
|
style: 'do',
|
|
100
288
|
items: [
|
|
101
|
-
'
|
|
102
|
-
'
|
|
103
|
-
'
|
|
104
|
-
'
|
|
289
|
+
'One lead per region; rank with weight and color; one primary action',
|
|
290
|
+
'Leave body copy at its defaults; demote by weight and color, not size',
|
|
291
|
+
'Default to Section; use the weakest container that reads as a group',
|
|
292
|
+
'Render collections as rows (Table or List), edge-to-edge with dividers',
|
|
293
|
+
'Open a fixed-width side panel on select; let it yield width at narrow sizes',
|
|
105
294
|
],
|
|
106
295
|
},
|
|
107
296
|
{
|
|
108
297
|
type: 'list',
|
|
109
298
|
style: 'dont',
|
|
110
299
|
items: [
|
|
111
|
-
'
|
|
112
|
-
'
|
|
113
|
-
'
|
|
114
|
-
'
|
|
300
|
+
'Grey and shrink body copy, so a whole region reads as secondary metadata',
|
|
301
|
+
'The disabled color for content; it fails contrast and is for disabled controls',
|
|
302
|
+
'Card soup: each record wrapped in its own Card instead of rendered as rows',
|
|
303
|
+
'Cards inside Cards, or full-width Cards stacked as page structure',
|
|
304
|
+
'A header or footer rebuilt inside the body, where it scrolls away with the rows',
|
|
305
|
+
'Flexbox soup: nested ad-hoc flexboxes instead of Grid, Layout, Section, or FormLayout',
|
|
306
|
+
'Two competing primary actions in one region',
|
|
307
|
+
'Badge as decoration; use StatusDot or Token for status and metadata',
|
|
115
308
|
],
|
|
116
309
|
},
|
|
117
310
|
],
|
|
118
311
|
},
|
|
119
312
|
{
|
|
120
|
-
title: '
|
|
313
|
+
title: 'Spacing',
|
|
121
314
|
content: [
|
|
315
|
+
{type: 'heading', level: 3, text: 'Alignment'},
|
|
316
|
+
{
|
|
317
|
+
type: 'prose',
|
|
318
|
+
text: 'The container owns padding and child gaps; children zero their margins, and interior spacing is always a token. Pick one content line per region and hold it constant, not the padding: `container_inset = content_line - component_intrinsic_inset`.',
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
type: 'list',
|
|
322
|
+
style: 'unordered',
|
|
323
|
+
items: [
|
|
324
|
+
'Text and Heading carry no inset, so the container takes the full padding',
|
|
325
|
+
'List, Tab, Menu, and nav items carry a small inset, so the container gives up its padding and the component owns the line',
|
|
326
|
+
'Table cells carry a larger inset, so the container gives up its padding and the cell owns the line',
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: 'code',
|
|
331
|
+
lang: 'tsx',
|
|
332
|
+
label: 'One content line, two inset owners',
|
|
333
|
+
code: `// Target content line = 16px.
|
|
334
|
+
// Heading has 0 inset, so the Section takes the full padding.
|
|
335
|
+
<Section padding={4}><Heading level={3}>Members</Heading></Section>
|
|
336
|
+
|
|
337
|
+
// List has ~8px built in, Table cells 12–16px, so the Section
|
|
338
|
+
// gives up its padding and the component owns the inset.
|
|
339
|
+
<Section padding={0}><List>{/* items */}</List></Section>`,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
type: 'prose',
|
|
343
|
+
text: 'Verify: draw one vertical line down the left of the region. Every label touches it; only hover and selected backgrounds cross it.',
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
{type: 'heading', level: 3, text: 'Rhythm'},
|
|
347
|
+
{
|
|
348
|
+
type: 'prose',
|
|
349
|
+
text: 'Grouping comes from contrast between tight and generous gaps, not one repeated value. If every gap is the same step, proximity does no work.',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
type: 'list',
|
|
353
|
+
style: 'unordered',
|
|
354
|
+
items: [
|
|
355
|
+
'Tight gaps bind: the smallest steps, used inside an item or field',
|
|
356
|
+
'Generous gaps separate: several steps up, used between sections',
|
|
357
|
+
'Reach for the in-between steps to tune cadence, rather than rounding everything to the same two values',
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
type: 'code',
|
|
362
|
+
lang: 'tsx',
|
|
363
|
+
label: 'Tight inside, generous between',
|
|
364
|
+
code: `// Tight binds at gap={1}–{2}, generous separates at gap={4}–{6}.
|
|
365
|
+
// In-between steps tune cadence: gap={3} = 12px, gap={5} = 20px.
|
|
366
|
+
<VStack gap={6}>
|
|
367
|
+
<VStack gap={1}>
|
|
368
|
+
<Text weight="semibold">Retention</Text>
|
|
369
|
+
<Text color="secondary">Logs are kept for 30 days</Text>
|
|
370
|
+
</VStack>
|
|
371
|
+
<VStack gap={1}>{/* next label and value */}</VStack>
|
|
372
|
+
</VStack>`,
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
type: 'prose',
|
|
376
|
+
text: 'Verify: with every border removed, you can still name the groups from spacing alone. If you cannot, the intervals are too uniform. Form fields are the exception: FormLayout owns their spacing.',
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
{type: 'heading', level: 3, text: 'Density and size'},
|
|
122
380
|
{
|
|
123
381
|
type: 'prose',
|
|
124
|
-
text: '
|
|
382
|
+
text: 'Match density to how often a region is used, and give every control in a row the same size so heights share a baseline.',
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
type: 'list',
|
|
386
|
+
style: 'unordered',
|
|
387
|
+
items: [
|
|
388
|
+
'Compact: high-volume regions scanned fast, like logs, monitors, and large datasets',
|
|
389
|
+
'Balanced: most Table and List surfaces',
|
|
390
|
+
'Spacious: low-frequency or high-stakes rows, like settings or a short selection list',
|
|
391
|
+
],
|
|
125
392
|
},
|
|
126
393
|
{
|
|
127
394
|
type: 'code',
|
|
128
395
|
lang: 'tsx',
|
|
129
|
-
label: '
|
|
130
|
-
code:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
396
|
+
label: 'Density paired with control size',
|
|
397
|
+
code: `// Pair density with one control size: compact with sm,
|
|
398
|
+
// balanced with sm or md, spacious with md or lg.
|
|
399
|
+
<Table data={rows} columns={columns} density="compact" hasHover />
|
|
400
|
+
<Button label="Retry" size="sm" variant="ghost" />`,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
type: 'prose',
|
|
404
|
+
text: 'Verify: every interactive element in a row shares one size, and that size is paired with the density of the region it sits in.',
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
{type: 'heading', level: 3, text: 'Best practices'},
|
|
408
|
+
{
|
|
409
|
+
type: 'list',
|
|
410
|
+
style: 'do',
|
|
411
|
+
items: [
|
|
412
|
+
'Let the container own padding; children zero their own margins',
|
|
413
|
+
'Hold one content line per region: text on the line, hover backgrounds bleed to the edge',
|
|
414
|
+
'Hold one padding token across a region header, body, and footer',
|
|
415
|
+
'Contrast tight and generous gaps so grouping reads without borders',
|
|
416
|
+
'One control size per row; match density to use frequency',
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
type: 'list',
|
|
421
|
+
style: 'dont',
|
|
422
|
+
items: [
|
|
423
|
+
'Double padding: a component indented past its Section heading (keep one inset owner)',
|
|
424
|
+
'Raw px for interior spacing; tokens only, px is for structural widths',
|
|
425
|
+
'One repeated gap everywhere, which flattens grouping',
|
|
426
|
+
'Mixed control sizes in a single row',
|
|
427
|
+
],
|
|
138
428
|
},
|
|
139
429
|
],
|
|
140
430
|
},
|
|
141
431
|
{
|
|
142
|
-
title: '
|
|
432
|
+
title: 'Breakpoints',
|
|
143
433
|
content: [
|
|
434
|
+
{type: 'heading', level: 3, text: 'Responsive contract'},
|
|
144
435
|
{
|
|
145
436
|
type: 'prose',
|
|
146
|
-
text: '
|
|
437
|
+
text: 'Lock what each region does as width changes, and pair every line of the contract with the prop or hook that enforces it.',
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
type: 'list',
|
|
441
|
+
style: 'unordered',
|
|
442
|
+
items: [
|
|
443
|
+
'Divide: how many regions survive at each width',
|
|
444
|
+
'Reveal: which regions earn their width only when there is room, and open on demand below that',
|
|
445
|
+
'Resize: content flexes while fixed regions hold their budgets, and text stays capped by contentWidth so line length holds',
|
|
446
|
+
'Swap: navigation becomes MobileNav at the AppShell mobileNav breakpoint; the side panel becomes a Dialog or BottomSheet, driven by useMediaQuery',
|
|
447
|
+
],
|
|
147
448
|
},
|
|
148
449
|
{
|
|
149
450
|
type: 'code',
|
|
150
451
|
lang: 'tsx',
|
|
151
|
-
label: 'Contract
|
|
152
|
-
code: `//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// <=
|
|
452
|
+
label: 'Contract wired to props',
|
|
453
|
+
code: `// Recommended thresholds: 3 regions above 1024, 2 from 768,
|
|
454
|
+
// 1 below. Text reads best at 40–60 characters per line.
|
|
455
|
+
// >1024 SideNav 256 | content | side panel 380
|
|
456
|
+
// <=1024 panel moves to a Dialog (useMediaQuery)
|
|
457
|
+
// <=768 nav collapses to MobileNav (mobileNav "md")
|
|
458
|
+
const isNarrow = useMediaQuery('(max-width: 1024px)');
|
|
459
|
+
|
|
460
|
+
<AppShell sideNav={<SideNav />} mobileNav={{breakpoint: 'md'}}>
|
|
461
|
+
<Layout
|
|
462
|
+
content={<LayoutContent>{/* rows */}</LayoutContent>}
|
|
463
|
+
end={isNarrow ? undefined : <LayoutPanel width={380} hasDivider />}
|
|
464
|
+
/>
|
|
465
|
+
</AppShell>`,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
type: 'prose',
|
|
469
|
+
text: 'Verify: every contract line names a mechanism, so the comment cannot drift from the behavior.',
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
{type: 'heading', level: 3, text: 'Best practices'},
|
|
473
|
+
{
|
|
474
|
+
type: 'list',
|
|
475
|
+
style: 'do',
|
|
476
|
+
items: [
|
|
477
|
+
'Write the contract down for every region before you call the layout done',
|
|
478
|
+
'Decide per region whether it is revealed, resized, or swapped at each width',
|
|
479
|
+
'Drop a region rather than let it compete for width it does not have',
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
type: 'list',
|
|
484
|
+
style: 'dont',
|
|
485
|
+
items: [
|
|
486
|
+
'Hold three regions at a width where none of them has usable space',
|
|
487
|
+
'Shrink every region uniformly instead of swapping or dropping one',
|
|
488
|
+
'Wire a breakpoint in CSS that the contract comment never mentions',
|
|
489
|
+
],
|
|
156
490
|
},
|
|
157
491
|
],
|
|
158
492
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'BottomSheet',
|
|
7
|
+
name: 'Bottom Sheet — Snap points',
|
|
8
|
+
displayName: 'Bottom Sheet — Snap points',
|
|
9
|
+
description:
|
|
10
|
+
'Drag-to-resize stops: a half-height working surface, and a peek that slides away and thins the scrim.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
aspectRatio: 3 / 4,
|
|
13
|
+
componentsUsed: [
|
|
14
|
+
'BottomSheet',
|
|
15
|
+
'Button',
|
|
16
|
+
'Divider',
|
|
17
|
+
'Heading',
|
|
18
|
+
'Icon',
|
|
19
|
+
'Item',
|
|
20
|
+
'Stack',
|
|
21
|
+
'Text',
|
|
22
|
+
],
|
|
23
|
+
};
|