@astryxdesign/cli 0.4.5-canary.33ba2b6 → 0.4.5
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/assets/docs/layout.doc.dense.mjs +30 -275
- package/assets/docs/layout.doc.mjs +78 -412
- package/package.json +9 -9
|
@@ -4,336 +4,91 @@
|
|
|
4
4
|
|
|
5
5
|
export const docsDense = {
|
|
6
6
|
description:
|
|
7
|
-
'
|
|
7
|
+
'frame-first app layout: shell choice, region budgets, cards vs rows',
|
|
8
8
|
sections: [
|
|
9
9
|
{
|
|
10
|
-
section: '
|
|
11
|
-
title: '
|
|
10
|
+
section: 'Frame First',
|
|
11
|
+
title: 'Frame First',
|
|
12
12
|
content: [
|
|
13
13
|
{
|
|
14
14
|
type: 'prose',
|
|
15
|
-
text: '
|
|
15
|
+
text: 'decide frame before content. content-first (Card-wrapped sections in a scroll column) = prototype look.',
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
type: 'list',
|
|
19
19
|
items: [
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
20
|
+
'pick frame: AppShell (nav apps) | Layout+LayoutPanel+LayoutContent (multi-pane tools) | plain column (docs/forms)',
|
|
21
|
+
'budget regions in px first: side nav 240-280, rail 64-72, inspector 340-420, facet rail 220-260',
|
|
22
|
+
'container policy per region: dense data = rows; dashboards/galleries = card grids',
|
|
23
|
+
'write responsive contract up front',
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
|
-
|
|
27
|
-
type: 'prose',
|
|
28
|
-
text: 'decides layout, not component APIs. npx astryx build "<idea>" = closest template for your app type. npx astryx component <Name> = props.',
|
|
29
|
-
},
|
|
26
|
+
null,
|
|
30
27
|
],
|
|
31
28
|
},
|
|
32
29
|
{
|
|
33
|
-
section: '
|
|
34
|
-
title: '
|
|
30
|
+
section: 'App Archetypes',
|
|
31
|
+
title: 'App Archetypes',
|
|
35
32
|
content: [
|
|
36
|
-
// Shell
|
|
37
|
-
null,
|
|
38
33
|
{
|
|
39
34
|
type: 'prose',
|
|
40
|
-
text: '
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
type: 'list',
|
|
44
|
-
items: [
|
|
45
|
-
'pick frame: AppShell (nav apps) | Layout + LayoutPanel in a start/end slot (multi-pane tools) | plain column (docs/forms)',
|
|
46
|
-
'give every fixed region a width budget, so none negotiates for space at render time',
|
|
47
|
-
'read content to set fill vs capped: tables/charts/boards fill; prose/forms/lists cap via Layout contentWidth',
|
|
48
|
-
'set container policy (rows or card grid) before writing content',
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
null,
|
|
52
|
-
{
|
|
53
|
-
type: 'prose',
|
|
54
|
-
text: 'verify: every region has a width budget + fill-or-capped + a container policy written down before any content.',
|
|
55
|
-
},
|
|
56
|
-
// Navigation
|
|
57
|
-
null,
|
|
58
|
-
{
|
|
59
|
-
type: 'prose',
|
|
60
|
-
text: 'nav left open? default SideNav: it absorbs destinations you have not planned yet. app type + destination count are guiding indicators, not determining rules.',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
type: 'list',
|
|
64
|
-
items: [
|
|
65
|
-
'SideNav (default): need grouping, customizable, items carry secondary actions, or must collapse. trackers, consoles, settings usually start here',
|
|
66
|
-
'TopNav: shallow nav you expect to stay shallow, context must stay visible, or control/filter-heavy page; + TabList for a 2nd level. media libraries often sit over grid content',
|
|
67
|
-
'both: a genuine suite. TopNav = ecosystem concerns (context switcher, global search), SideNav = product nav',
|
|
68
|
-
'neither: messaging/feeds use a column frame of rail, nav, stream, panel',
|
|
69
|
-
],
|
|
35
|
+
text: 'container choice tracks archetype, not preference.',
|
|
70
36
|
},
|
|
71
37
|
null,
|
|
72
38
|
{
|
|
73
39
|
type: 'prose',
|
|
74
|
-
text: '
|
|
75
|
-
},
|
|
76
|
-
// Best practices
|
|
77
|
-
null,
|
|
78
|
-
{
|
|
79
|
-
type: 'list',
|
|
80
|
-
items: [
|
|
81
|
-
'decide frame + region width budgets + fill-or-capped before content',
|
|
82
|
-
'state the reason for the nav choice, or inherit template pairing',
|
|
83
|
-
'raw px for structural widths; interior = tokens',
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
type: 'list',
|
|
88
|
-
items: [
|
|
89
|
-
'build content-first, Card-wrapping each section',
|
|
90
|
-
'stretch prose/forms/lists across a wide region instead of capping with contentWidth',
|
|
91
|
-
'SideNav when the nav is really filters/controls, or must hold wide elements like breadcrumbs',
|
|
92
|
-
'TopNav when top-slot ownership is unclear, or hierarchy is deep or still growing',
|
|
93
|
-
'both bars when the ecosystem layer is thin',
|
|
94
|
-
'break template nav pairing without a reason',
|
|
95
|
-
],
|
|
40
|
+
text: 'start from matching template (astryx template --list), study with --skeleton.',
|
|
96
41
|
},
|
|
97
42
|
],
|
|
98
43
|
},
|
|
99
44
|
{
|
|
100
|
-
section: '
|
|
101
|
-
title: '
|
|
45
|
+
section: 'Cards vs Rows',
|
|
46
|
+
title: 'Cards vs Rows',
|
|
102
47
|
content: [
|
|
103
|
-
// Type hierarchy
|
|
104
|
-
null,
|
|
105
|
-
{
|
|
106
|
-
type: 'prose',
|
|
107
|
-
text: 'one lead per region, then rank with weight+color, not size. two text colors only: primary + secondary, nothing dimmer. body copy needs no props.',
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
type: 'list',
|
|
111
|
-
items: [
|
|
112
|
-
'body (default): plain Text, no type/color/size prop',
|
|
113
|
-
'lead: Heading at the level matching page depth, or body Text at a heavier weight',
|
|
114
|
-
'support: step to secondary color, not to a smaller size',
|
|
115
|
-
'meta: the supporting type, or StatusDot/Token instead of prose',
|
|
116
|
-
],
|
|
117
|
-
},
|
|
118
|
-
null,
|
|
119
|
-
{
|
|
120
|
-
type: 'prose',
|
|
121
|
-
text: 'squint test: read lead, then support, then groups, in order. everything at once = raise contrast (weight/color), not borders and not smaller text.',
|
|
122
|
-
},
|
|
123
|
-
// Containers
|
|
124
|
-
null,
|
|
125
|
-
{
|
|
126
|
-
type: 'prose',
|
|
127
|
-
text: 'weakest container that reads as a group, escalate only when it fails. weakest to strongest:',
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
type: 'list',
|
|
131
|
-
items: [
|
|
132
|
-
'spacing/gap: related items inside one group. the default rhythm',
|
|
133
|
-
'Divider: peers in a dense list/toolbar, or fencing a header from a scrollable body',
|
|
134
|
-
'Section: default page-structure unit, related content under a heading. no border',
|
|
135
|
-
'Card: self-contained widget (KPI tile, chart, gallery entry) or hard boundary',
|
|
136
|
-
],
|
|
137
|
-
},
|
|
138
|
-
null,
|
|
139
|
-
{
|
|
140
|
-
type: 'prose',
|
|
141
|
-
text: 'test: records -> rows (Table columnar, List single-line); self-contained widget or hard boundary -> Card; everything else -> Section.',
|
|
142
|
-
},
|
|
143
|
-
// Headers and footers
|
|
144
|
-
null,
|
|
145
|
-
{
|
|
146
|
-
type: 'prose',
|
|
147
|
-
text: 'a region can pin a header/footer while its body scrolls. both are Layout slots, and padding set once on Layout reaches all three, so header/body/footer share one content line.',
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
type: 'list',
|
|
151
|
-
items: [
|
|
152
|
-
'LayoutHeader in the header slot: region title + its primary action',
|
|
153
|
-
'Toolbar instead of LayoutHeader when the header carries interactive controls',
|
|
154
|
-
'LayoutFooter in the footer slot: actions that commit the work + must stay reachable',
|
|
155
|
-
'defaultHasDividers on Layout fences both at once, rather than hasDivider per slot',
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
null,
|
|
159
|
-
{
|
|
160
|
-
type: 'prose',
|
|
161
|
-
text: 'verify: scroll the body. header + footer stay put, dividers run full-bleed, all three still share one left content line.',
|
|
162
|
-
},
|
|
163
|
-
// Side panels
|
|
164
|
-
null,
|
|
165
|
-
{
|
|
166
|
-
type: 'prose',
|
|
167
|
-
text: 'master-detail: select a row -> fixed-width side panel, no navigation away.',
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
type: 'list',
|
|
171
|
-
items: [
|
|
172
|
-
'LayoutPanel in the start or end slot of Layout, holding a fixed width budget',
|
|
173
|
-
'hasDivider fences it from content; isScrollable so long detail scrolls on its own',
|
|
174
|
-
'user-adjustable width: useResizable() + ResizeHandle on the panel inner edge. after the panel in a start slot, before it in an end slot w/ isReversed',
|
|
175
|
-
'the handle then owns the divider, so the panel sets hasDivider={false}',
|
|
176
|
-
'EmptyState when nothing is selected, so the region never collapses',
|
|
177
|
-
],
|
|
178
|
-
},
|
|
179
|
-
null,
|
|
180
48
|
{
|
|
181
49
|
type: 'prose',
|
|
182
|
-
text: '
|
|
50
|
+
text: 'Card = widget container, NOT list-item wrapper. dense/scannable/selectable data = rows: Table (columnar) or List/Item (single-line), edge-to-edge, 32-40px rows, dividers.',
|
|
183
51
|
},
|
|
184
|
-
// Best practices
|
|
185
|
-
null,
|
|
186
52
|
{
|
|
187
53
|
type: 'list',
|
|
188
54
|
items: [
|
|
189
|
-
'
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
'
|
|
193
|
-
'side panel on select; it yields width at narrow sizes',
|
|
55
|
+
'Table+plugins: hosts, deployments, monitors, users',
|
|
56
|
+
'List/Item rows: issues, files, conversations',
|
|
57
|
+
'Card: KPI tiles, chart panels, gallery entries, settings groups',
|
|
58
|
+
'EmptyState for zero-match',
|
|
194
59
|
],
|
|
195
60
|
},
|
|
196
61
|
{
|
|
197
62
|
type: 'list',
|
|
198
63
|
items: [
|
|
199
|
-
'
|
|
200
|
-
'
|
|
201
|
-
'
|
|
202
|
-
'
|
|
203
|
-
'a header/footer rebuilt inside the body, where it scrolls away with the rows',
|
|
204
|
-
'flexbox soup instead of Grid/Layout/Section/FormLayout',
|
|
205
|
-
'two competing primary actions in one region',
|
|
206
|
-
'Badge as decoration; use StatusDot/Token for status',
|
|
64
|
+
'no Card-wrapped list items (card soup)',
|
|
65
|
+
'no stacked full-width Cards as page structure',
|
|
66
|
+
'no Cards in Cards',
|
|
67
|
+
'no decorative Badge: counts/enums only; StatusDot/Token for status',
|
|
207
68
|
],
|
|
208
69
|
},
|
|
209
70
|
],
|
|
210
71
|
},
|
|
211
72
|
{
|
|
212
|
-
section: '
|
|
213
|
-
title: '
|
|
73
|
+
section: 'Panels and Inspectors',
|
|
74
|
+
title: 'Panels and Inspectors',
|
|
214
75
|
content: [
|
|
215
|
-
// Alignment
|
|
216
|
-
null,
|
|
217
|
-
{
|
|
218
|
-
type: 'prose',
|
|
219
|
-
text: 'container owns padding + child gaps; children zero margins; interior spacing = token. one content line per region, hold the line not the padding: container_inset = content_line - component_intrinsic_inset.',
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
type: 'list',
|
|
223
|
-
items: [
|
|
224
|
-
'Text/Heading carry no inset -> container takes the full padding',
|
|
225
|
-
'List/Tab/Menu/nav items carry a small inset -> container gives up padding, component owns the line',
|
|
226
|
-
'Table cells carry a larger inset -> container gives up padding, cell owns the line',
|
|
227
|
-
],
|
|
228
|
-
},
|
|
229
|
-
null,
|
|
230
76
|
{
|
|
231
77
|
type: 'prose',
|
|
232
|
-
text: '
|
|
78
|
+
text: 'master-detail: row select opens fixed-width inspector (LayoutPanel end slot + width budget + resizable/useResizable). overlay content <=1024px, do not compress.',
|
|
233
79
|
},
|
|
234
|
-
// Rhythm
|
|
235
80
|
null,
|
|
236
|
-
{
|
|
237
|
-
type: 'prose',
|
|
238
|
-
text: 'grouping = contrast between tight and generous gaps, not one repeated value. same step everywhere = proximity does no work.',
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
type: 'list',
|
|
242
|
-
items: [
|
|
243
|
-
'tight gaps bind: the smallest steps, inside an item or field',
|
|
244
|
-
'generous gaps separate: several steps up, between sections',
|
|
245
|
-
'reach for the in-between steps to tune cadence, not the same two values everywhere',
|
|
246
|
-
],
|
|
247
|
-
},
|
|
248
|
-
null,
|
|
249
|
-
{
|
|
250
|
-
type: 'prose',
|
|
251
|
-
text: 'verify: borders removed, you can still name the groups from spacing alone. cannot = intervals too uniform. form fields excepted: FormLayout owns their spacing.',
|
|
252
|
-
},
|
|
253
|
-
// Density and size
|
|
254
|
-
null,
|
|
255
|
-
{
|
|
256
|
-
type: 'prose',
|
|
257
|
-
text: 'density by use frequency; every control in a row shares one size so heights share a baseline.',
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
type: 'list',
|
|
261
|
-
items: [
|
|
262
|
-
'compact: high-volume, fast scan (logs, monitors, large datasets)',
|
|
263
|
-
'balanced: most Table/List surfaces',
|
|
264
|
-
'spacious: low-frequency or high-stakes rows (settings, short selection list)',
|
|
265
|
-
],
|
|
266
|
-
},
|
|
267
|
-
null,
|
|
268
|
-
{
|
|
269
|
-
type: 'prose',
|
|
270
|
-
text: 'verify: one size per row, paired with the density of the region it sits in.',
|
|
271
|
-
},
|
|
272
|
-
// Best practices
|
|
273
|
-
null,
|
|
274
|
-
{
|
|
275
|
-
type: 'list',
|
|
276
|
-
items: [
|
|
277
|
-
'container owns padding; children zero margins',
|
|
278
|
-
'one content line: text on line, hover bleeds to edge',
|
|
279
|
-
'one padding token across region header/body/footer',
|
|
280
|
-
'contrast tight vs generous gaps',
|
|
281
|
-
'one control size per row; density by use frequency',
|
|
282
|
-
],
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
type: 'list',
|
|
286
|
-
items: [
|
|
287
|
-
'double padding (component past its heading); keep one inset owner',
|
|
288
|
-
'raw px for interior spacing; tokens only',
|
|
289
|
-
'one repeated gap everywhere',
|
|
290
|
-
'mixed control sizes in one row',
|
|
291
|
-
],
|
|
292
|
-
},
|
|
293
81
|
],
|
|
294
82
|
},
|
|
295
83
|
{
|
|
296
|
-
section: '
|
|
297
|
-
title: '
|
|
84
|
+
section: 'Responsive Contract',
|
|
85
|
+
title: 'Responsive Contract',
|
|
298
86
|
content: [
|
|
299
|
-
// Responsive contract
|
|
300
|
-
null,
|
|
301
87
|
{
|
|
302
88
|
type: 'prose',
|
|
303
|
-
text: '
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
type: 'list',
|
|
307
|
-
items: [
|
|
308
|
-
'divide: how many regions survive at each width',
|
|
309
|
-
'reveal: which regions earn their width only when there is room, opening on demand below that',
|
|
310
|
-
'resize: content flexes, fixed regions hold their budgets, text capped by contentWidth so line length holds',
|
|
311
|
-
'swap: nav -> MobileNav at the AppShell mobileNav breakpoint; side panel -> Dialog/BottomSheet via useMediaQuery',
|
|
312
|
-
],
|
|
89
|
+
text: 'declare breakpoint behavior as comment at frame root: which regions collapse/overlay/drop at which widths.',
|
|
313
90
|
},
|
|
314
91
|
null,
|
|
315
|
-
{
|
|
316
|
-
type: 'prose',
|
|
317
|
-
text: 'verify: every contract line names a mechanism, so the comment cannot drift from the behavior.',
|
|
318
|
-
},
|
|
319
|
-
// Best practices
|
|
320
|
-
null,
|
|
321
|
-
{
|
|
322
|
-
type: 'list',
|
|
323
|
-
items: [
|
|
324
|
-
'write the contract down for every region before calling the layout done',
|
|
325
|
-
'decide per region: revealed, resized, or swapped at each width',
|
|
326
|
-
'drop a region rather than let it fight for width it lacks',
|
|
327
|
-
],
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
type: 'list',
|
|
331
|
-
items: [
|
|
332
|
-
'3 regions at a width where none has usable space',
|
|
333
|
-
'shrink every region uniformly instead of swapping/dropping one',
|
|
334
|
-
'a CSS breakpoint the contract comment never mentions',
|
|
335
|
-
],
|
|
336
|
-
},
|
|
337
92
|
],
|
|
338
93
|
},
|
|
339
94
|
],
|
|
@@ -7,486 +7,152 @@ export const docs = {
|
|
|
7
7
|
title: 'Layout',
|
|
8
8
|
category: 'guide',
|
|
9
9
|
description:
|
|
10
|
-
'
|
|
10
|
+
'Frame-first app layout: choosing a shell, budgeting regions, and when to use cards vs rows.',
|
|
11
11
|
|
|
12
12
|
sections: [
|
|
13
13
|
{
|
|
14
|
-
title: '
|
|
14
|
+
title: 'Frame First',
|
|
15
15
|
content: [
|
|
16
16
|
{
|
|
17
17
|
type: 'prose',
|
|
18
|
-
text: '
|
|
18
|
+
text: 'Decide the frame before writing any content. Real applications are built top-down: pick the shell, name its regions, give each region an explicit size budget, then fill regions with content. Content-first layout (writing sections and wrapping each one in a Card) produces a padded scroll column that reads as a prototype, not a product.',
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
type: 'list',
|
|
22
22
|
style: 'ordered',
|
|
23
23
|
items: [
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
24
|
+
'Pick the frame: AppShell (top nav and/or side nav apps), Layout + LayoutPanel + LayoutContent (multi-pane tools like explorers and consoles), or a plain content column (documents, marketing, forms)',
|
|
25
|
+
'Budget regions in px before filling them: side nav 240–280, icon rail 64–72, detail/inspector panel 340–420, filter/facet rail 220–260',
|
|
26
|
+
'Decide the container policy per region: dense data renders as rows; widget dashboards and galleries render as card grids (see Cards vs Rows)',
|
|
27
|
+
'Write the responsive contract up front: which regions collapse, overlay, or drop at which breakpoints (see Responsive Contract)',
|
|
28
28
|
],
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
|
-
type: '
|
|
32
|
-
|
|
31
|
+
type: 'code',
|
|
32
|
+
lang: 'tsx',
|
|
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>`,
|
|
33
43
|
},
|
|
34
44
|
],
|
|
35
45
|
},
|
|
36
46
|
{
|
|
37
|
-
title: '
|
|
47
|
+
title: 'App Archetypes',
|
|
38
48
|
content: [
|
|
39
|
-
{type: 'heading', level: 3, text: 'Shell'},
|
|
40
49
|
{
|
|
41
50
|
type: 'prose',
|
|
42
|
-
text: '
|
|
51
|
+
text: 'Match the frame and container policy to the kind of app you are building. These recipes are distilled from product-scale apps built with the design system; container choice tracks the archetype, not personal preference.',
|
|
43
52
|
},
|
|
44
53
|
{
|
|
45
|
-
type: '
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
type: 'table',
|
|
55
|
+
headers: ['Archetype', 'Frame', 'Container policy'],
|
|
56
|
+
rows: [
|
|
57
|
+
[
|
|
58
|
+
'Tracker / work tool (issues, tickets, CRM)',
|
|
59
|
+
'AppShell + SideNav; inspector LayoutPanel on select',
|
|
60
|
+
'Rows only. Grouped edge-to-edge lists, zero cards',
|
|
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
|
+
],
|
|
52
82
|
],
|
|
53
83
|
},
|
|
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'},
|
|
80
84
|
{
|
|
81
85
|
type: 'prose',
|
|
82
|
-
text: '
|
|
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
|
-
],
|
|
86
|
+
text: 'Start from a template that matches the archetype (`npx astryx template --list`), then study its structure with `--skeleton` before customizing.',
|
|
133
87
|
},
|
|
134
88
|
],
|
|
135
89
|
},
|
|
136
90
|
{
|
|
137
|
-
title: '
|
|
91
|
+
title: 'Cards vs Rows',
|
|
138
92
|
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
|
-
},
|
|
279
93
|
{
|
|
280
94
|
type: 'prose',
|
|
281
|
-
text: '
|
|
95
|
+
text: 'Card is a widget container, not a list-item wrapper. The fastest way to make an app look like a generic AI prototype is to wrap every record in a Card with a Badge. Dense data (anything the user scans, filters, or selects) belongs in rows: Table for columnar data, List/Item for single-line records, edge-to-edge with dividers and 32–40px row height.',
|
|
282
96
|
},
|
|
283
|
-
|
|
284
|
-
{type: 'heading', level: 3, text: 'Best practices'},
|
|
285
97
|
{
|
|
286
98
|
type: 'list',
|
|
287
99
|
style: 'do',
|
|
288
100
|
items: [
|
|
289
|
-
'
|
|
290
|
-
'
|
|
291
|
-
'
|
|
292
|
-
'
|
|
293
|
-
'Open a fixed-width side panel on select; let it yield width at narrow sizes',
|
|
101
|
+
'Table (with selection/sorting plugins) for columnar records: hosts, deployments, monitors, users',
|
|
102
|
+
'List/Item rows for scannable single-line records: issues, files, conversations',
|
|
103
|
+
'Card for self-contained widgets: KPI tiles, chart panels, gallery entries, settings groups',
|
|
104
|
+
'EmptyState inside the region when a filter matches nothing',
|
|
294
105
|
],
|
|
295
106
|
},
|
|
296
107
|
{
|
|
297
108
|
type: 'list',
|
|
298
109
|
style: 'dont',
|
|
299
110
|
items: [
|
|
300
|
-
'
|
|
301
|
-
'
|
|
302
|
-
'
|
|
303
|
-
'
|
|
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',
|
|
111
|
+
'Wrapping each list item in a Card (card soup)',
|
|
112
|
+
'Stacking full-width Cards as a substitute for page structure',
|
|
113
|
+
'Nesting Cards inside Cards',
|
|
114
|
+
'Using Badge as decoration: reserve it for counts and enumerated states; use StatusDot or Token for status and metadata',
|
|
308
115
|
],
|
|
309
116
|
},
|
|
310
117
|
],
|
|
311
118
|
},
|
|
312
119
|
{
|
|
313
|
-
title: '
|
|
120
|
+
title: 'Panels and Inspectors',
|
|
314
121
|
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'},
|
|
380
122
|
{
|
|
381
123
|
type: 'prose',
|
|
382
|
-
text: '
|
|
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
|
-
],
|
|
124
|
+
text: 'Master-detail is the backbone of tool UIs: selecting a row opens a fixed-width inspector panel rather than navigating away. Use LayoutPanel in the end slot with an explicit width budget; add resizable (useResizable) for user control, and let the panel overlay the content region below ~1024px instead of compressing it.',
|
|
392
125
|
},
|
|
393
126
|
{
|
|
394
127
|
type: 'code',
|
|
395
128
|
lang: 'tsx',
|
|
396
|
-
label: '
|
|
397
|
-
code:
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
],
|
|
129
|
+
label: 'Inspector that overlays at narrow widths',
|
|
130
|
+
code: `<LayoutPanel
|
|
131
|
+
width={380}
|
|
132
|
+
hasDivider
|
|
133
|
+
isScrollable
|
|
134
|
+
label="Details"
|
|
135
|
+
resizable={{minSizePx: 320, maxSizePx: 480, autoSaveId: 'inspector'}}>
|
|
136
|
+
{selected ? <DetailFields item={selected} /> : <EmptyState title="Nothing selected" />}
|
|
137
|
+
</LayoutPanel>`,
|
|
428
138
|
},
|
|
429
139
|
],
|
|
430
140
|
},
|
|
431
141
|
{
|
|
432
|
-
title: '
|
|
142
|
+
title: 'Responsive Contract',
|
|
433
143
|
content: [
|
|
434
|
-
{type: 'heading', level: 3, text: 'Responsive contract'},
|
|
435
144
|
{
|
|
436
145
|
type: 'prose',
|
|
437
|
-
text: '
|
|
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
|
-
],
|
|
146
|
+
text: 'Declare breakpoint behavior as a contract before building, and keep it in a comment at the frame root. A typical contract: full frame above 1024px; inspector panels overlay the content column at 1024px and below; the side nav collapses into MobileNav at 768px and below. Deciding this up front keeps every region change intentional instead of emergent.',
|
|
448
147
|
},
|
|
449
148
|
{
|
|
450
149
|
type: 'code',
|
|
451
150
|
lang: 'tsx',
|
|
452
|
-
label: 'Contract
|
|
453
|
-
code: `//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
456
|
-
// <=
|
|
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
|
-
],
|
|
151
|
+
label: 'Contract comment at the frame root',
|
|
152
|
+
code: `// Responsive contract:
|
|
153
|
+
// > 1024px nav 256 | content | inspector 380
|
|
154
|
+
// <= 1024px inspector overlays content (position: absolute, end-aligned)
|
|
155
|
+
// <= 768px nav collapses into MobileNav drawer; toolbar actions wrap`,
|
|
490
156
|
},
|
|
491
157
|
],
|
|
492
158
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.4.5
|
|
3
|
+
"version": "0.4.5",
|
|
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",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"zod": "^4.4.3"
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@astryxdesign/charts": "
|
|
91
|
-
"@astryxdesign/core": "
|
|
92
|
-
"@astryxdesign/lab": "
|
|
93
|
-
"@astryxdesign/theme-neutral": "
|
|
90
|
+
"@astryxdesign/charts": "*",
|
|
91
|
+
"@astryxdesign/core": "*",
|
|
92
|
+
"@astryxdesign/lab": "*",
|
|
93
|
+
"@astryxdesign/theme-neutral": "*",
|
|
94
94
|
"gpt-tokenizer": "^3.4.0"
|
|
95
95
|
},
|
|
96
96
|
"peerDependenciesMeta": {
|
|
@@ -108,10 +108,10 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"devDependencies": {
|
|
111
|
-
"@astryxdesign/charts": "
|
|
112
|
-
"@astryxdesign/core": "
|
|
113
|
-
"@astryxdesign/lab": "
|
|
114
|
-
"@astryxdesign/theme-neutral": "
|
|
111
|
+
"@astryxdesign/charts": "*",
|
|
112
|
+
"@astryxdesign/core": "*",
|
|
113
|
+
"@astryxdesign/lab": "*",
|
|
114
|
+
"@astryxdesign/theme-neutral": "*",
|
|
115
115
|
"gpt-tokenizer": "^3.4.0"
|
|
116
116
|
},
|
|
117
117
|
"scripts": {
|