astryx-dracula 0.6.2 → 0.7.0
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/BRAND.md +54 -3
- package/README.md +11 -4
- package/USAGE.md +1 -1
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +26 -4
- package/astryx-dracula.variants.d.ts +2 -2
- package/package.json +11 -11
- package/shared/auth-chrome-config.ts +0 -4
- package/shared/auth-chrome.tsx +2 -3
- package/shared/chaptered-doc-config.ts +3 -2
- package/shared/order-desk.tsx +52 -54
- package/shared/revenue-chart.tsx +1 -1
- package/shared/scene-castle.tsx +21 -13
- package/templates/ai-chat-landing.tsx +6 -9
- package/templates/ai-chat.tsx +5 -1
- package/templates/centered-hero.tsx +2 -2
- package/templates/classic-gallery.tsx +5 -0
- package/templates/contact-form.tsx +9 -0
- package/templates/dashboard-portfolio.tsx +17 -4
- package/templates/dashboard.tsx +5 -5
- package/templates/detail-page.tsx +20 -28
- package/templates/documentation-design.tsx +3 -0
- package/templates/documentation-technical.tsx +8 -5
- package/templates/documentation.tsx +13 -6
- package/templates/form-two-column.tsx +11 -2
- package/templates/form-wizard.template.mjs +15 -0
- package/templates/form-wizard.tsx +878 -0
- package/templates/gallery-hero.tsx +10 -4
- package/templates/ide.tsx +177 -96
- package/templates/incident-console.tsx +15 -5
- package/templates/kanban-board.tsx +46 -4
- package/templates/library.tsx +12 -3
- package/templates/login-split.tsx +3 -3
- package/templates/login-sso.tsx +9 -17
- package/templates/messaging-shell.tsx +109 -46
- package/templates/mixed-gallery.tsx +6 -0
- package/templates/payment-form.tsx +29 -28
- package/templates/product-detail.tsx +12 -3
- package/templates/product-gallery.tsx +8 -3
- package/templates/product-tour.tsx +11 -7
- package/templates/settings-dialog.tsx +5 -0
- package/templates/settings-sidebar.tsx +603 -584
- package/templates/settings.tsx +5 -2
- package/templates/shell-nav.tsx +23 -3
- package/templates/shell-side-nav.tsx +94 -81
- package/templates/shell-top-nav.tsx +98 -86
- package/templates/table-grouped.tsx +146 -132
- package/templates/{table-page-shoe-store-heatmap.template.mjs → table-page-shoe-store.template.mjs} +1 -1
- package/templates/{table-page-shoe-store-heatmap.tsx → table-page-shoe-store.tsx} +1 -1
- package/templates/table-page.tsx +8 -0
- package/templates/table-tree.template.mjs +16 -0
- package/templates/table-tree.tsx +707 -0
- package/templates/tech-report.tsx +8 -5
- package/templates/theme-showcase.tsx +18 -11
- package/templates/work-item-detail.template.mjs +8 -0
- package/templates/work-item-detail.tsx +1049 -0
- package/theme.css +38 -3
- package/theme.css.d.ts +2 -2
- package/tokens.css +41 -4
|
@@ -67,9 +67,15 @@ function BatFlightScene() {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const SCENES = [
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
{
|
|
71
|
+
alt: "Moonlit castle on midnight hills",
|
|
72
|
+
render: (label: string) => <SceneCastle variant="card" label={label} />,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
alt: "Harvest moon over midnight pines",
|
|
76
|
+
render: (label: string) => <SceneCastle variant="tall" label={label} />,
|
|
77
|
+
},
|
|
78
|
+
{ alt: "Bats crossing a moonless night sky", render: () => <BatFlightScene /> },
|
|
73
79
|
];
|
|
74
80
|
|
|
75
81
|
export default function GalleryHero() {
|
|
@@ -100,7 +106,7 @@ export default function GalleryHero() {
|
|
|
100
106
|
<Grid columns={{ minWidth: 200, repeat: "fit" }} gap={4}>
|
|
101
107
|
{SCENES.map((item) => (
|
|
102
108
|
<AspectRatio key={item.alt} ratio={4 / 5} style={galleryImageClip}>
|
|
103
|
-
{item.
|
|
109
|
+
{item.render(item.alt)}
|
|
104
110
|
</AspectRatio>
|
|
105
111
|
))}
|
|
106
112
|
</Grid>
|
package/templates/ide.tsx
CHANGED
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
3
|
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
4
|
// human drift check — update it with the file, never alone):
|
|
5
|
-
// L[h=fill] > LC[p=0] > (VH"Code editor" + L[h=fill] > (LP[w=256 p=4] > V[g=2] > TI"Search the night" + TrL) + RH + (LC[p=0] > L[h=fill] > (LC[p=0] > V > (SI[fill] > Cd) + RH + (V[h=300] > (TL[size=sm] > Tab"Terminal"! + Tab"Problems" + Tab"Output" + Tab"Debug") + (SI[fill] > Cd))) + (RH + (LP[w=320 p=4] > V[g=3] > (SG > SGI"Properties"! + SGI"History") + (V[g=3] > (V[g=1] > Hd"NightCounter.tsx"[level=3] + Tx"src/components/NightCounter.tsx"[t=supporting]) + ML + (V[g=2] > B.secondary"Format Document" + B.secondary"Go to Definition" + B.secondary"Find References")) + (V[g=1] > UL > LI*3)))))
|
|
5
|
+
// L[h=fill] > (LH[divider p=4 narrow] > (H[g=2] > B.secondary"Browse files" + B.secondary"File properties")) + LC[p=0] > (VH"Code editor" + L[h=fill] > (LP[w=256 p=4] > V[g=2] > TI"Search the night" + TrL) + RH + (LC[p=0] > L[h=fill] > (LC[p=0] > V > (SI[fill] > Cd) + RH + (V[h=300] > (TL[size=sm] > Tab"Terminal"! + Tab"Problems" + Tab"Output" + Tab"Debug") + (SI[fill] > Cd))) + (RH + (LP[w=320 p=4] > V[g=3] > (SG > SGI"Properties"! + SGI"History") + (V[g=3] > (V[g=1] > Hd"NightCounter.tsx"[level=3] + Tx"src/components/NightCounter.tsx"[t=supporting]) + ML + (V[g=2] > B.secondary"Format Document" + B.secondary"Go to Definition" + B.secondary"Find References")) + (V[g=1] > UL > LI*3))))) ;; Drawer"File tree"[side=start narrow] > (L[h=fill] > (DrawerHeader"File tree" + LC[p=4] > V[g=2] > TI"Search the night" + TrL)) + Drawer"File properties"[side=end narrow] > (L[h=fill] > (DrawerHeader"File properties" + LC[p=4] > V[g=3] > (SG > SGI"Properties" + SGI"History" + ML + (V[g=2] > B.secondary*3 + UL))))
|
|
6
6
|
|
|
7
7
|
import { Button } from "@astryxdesign/core/Button";
|
|
8
8
|
import { Center } from "@astryxdesign/core/Center";
|
|
9
9
|
import { CodeBlock } from "@astryxdesign/core/CodeBlock";
|
|
10
|
+
import { Drawer, DrawerHeader } from "@astryxdesign/core/Drawer";
|
|
10
11
|
import { EmptyState } from "@astryxdesign/core/EmptyState";
|
|
11
12
|
import { useMediaQuery } from "@astryxdesign/core/hooks";
|
|
12
13
|
import { Icon } from "@astryxdesign/core/Icon";
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Layout,
|
|
16
|
+
LayoutContent,
|
|
17
|
+
LayoutHeader,
|
|
18
|
+
LayoutPanel,
|
|
19
|
+
Stack,
|
|
20
|
+
StackItem,
|
|
21
|
+
} from "@astryxdesign/core/Layout";
|
|
14
22
|
import { List, ListItem } from "@astryxdesign/core/List";
|
|
15
23
|
import { MetadataList, MetadataListItem } from "@astryxdesign/core/MetadataList";
|
|
16
24
|
import { ResizeHandle, useResizable } from "@astryxdesign/core/Resizable";
|
|
@@ -84,6 +92,13 @@ const codeBlockFlush: CSSProperties = {
|
|
|
84
92
|
borderRadius: 0,
|
|
85
93
|
};
|
|
86
94
|
|
|
95
|
+
// Three nested `Layout height="fill"`s resolve against their parent's definite
|
|
96
|
+
// height, and a host's <html>/<body> set none — so without this the outermost
|
|
97
|
+
// one computes to auto and the document scrolls instead of the editor panes.
|
|
98
|
+
// 100dvh is absolute and needs no ancestor; minHeight: '100%' would compute to
|
|
99
|
+
// 0 here and look like a fix while doing nothing.
|
|
100
|
+
const pageStyle: CSSProperties = { height: "100dvh" };
|
|
101
|
+
|
|
87
102
|
const EDITOR_CODE = `import {useState, useCallback} from 'react';
|
|
88
103
|
import {Button} from '@astryxdesign/core/Button';
|
|
89
104
|
import {Text} from '@astryxdesign/core/Text';
|
|
@@ -213,7 +228,20 @@ export default function IdeWorkspace() {
|
|
|
213
228
|
const [activeTermTab, setActiveTermTab] = useState("terminal");
|
|
214
229
|
const [activePropertiesTab, setActivePropertiesTab] = useState("properties");
|
|
215
230
|
const [query, setQuery] = useState("");
|
|
216
|
-
|
|
231
|
+
// Below 768 there is no room for a file tree and a properties rail beside
|
|
232
|
+
// the editor, so each panel becomes a Drawer opened from the header.
|
|
233
|
+
const [isFileTreeOpen, setFileTreeOpen] = useState(false);
|
|
234
|
+
const [isPropertiesOpen, setPropertiesOpen] = useState(false);
|
|
235
|
+
const fileTree = useMemo(
|
|
236
|
+
() =>
|
|
237
|
+
buildFileTree((name) => {
|
|
238
|
+
setActiveFile(name);
|
|
239
|
+
// A drawer covers the editor on a phone, so picking a file closes
|
|
240
|
+
// it; on the desktop column the setter is already false.
|
|
241
|
+
setFileTreeOpen(false);
|
|
242
|
+
}),
|
|
243
|
+
[],
|
|
244
|
+
);
|
|
217
245
|
const visibleTree = useMemo(() => {
|
|
218
246
|
const q = query.trim().toLowerCase();
|
|
219
247
|
return q ? filterFileTree(fileTree, q) : fileTree;
|
|
@@ -241,15 +269,127 @@ export default function IdeWorkspace() {
|
|
|
241
269
|
defaultSize: 300,
|
|
242
270
|
minSize: 80,
|
|
243
271
|
maxSize: 520,
|
|
272
|
+
// Must match the ResizeHandle's `direction`; the hook defaults to
|
|
273
|
+
// horizontal, and a mismatch makes core warn and picks the wrong
|
|
274
|
+
// percentage basis.
|
|
275
|
+
direction: "vertical",
|
|
244
276
|
collapsible: true,
|
|
245
277
|
collapsedSize: 40,
|
|
246
278
|
});
|
|
247
279
|
|
|
248
280
|
const isMobile = useMediaQuery("(max-width: 768px)");
|
|
249
281
|
|
|
282
|
+
// One render of each panel's body, shared by the desktop column and the
|
|
283
|
+
// narrow-viewport Drawer so the two cannot drift apart.
|
|
284
|
+
const fileExplorer = (
|
|
285
|
+
<Stack direction="vertical" style={styles.fileExplorer} gap={2}>
|
|
286
|
+
<TextInput
|
|
287
|
+
label="Search files"
|
|
288
|
+
isLabelHidden
|
|
289
|
+
value={query}
|
|
290
|
+
onChange={setQuery}
|
|
291
|
+
placeholder="Search the night"
|
|
292
|
+
size="md"
|
|
293
|
+
startIcon={Search}
|
|
294
|
+
hasClear
|
|
295
|
+
/>
|
|
296
|
+
{visibleTree.length > 0 ? (
|
|
297
|
+
<TreeList items={visibleTree} density="compact" />
|
|
298
|
+
) : (
|
|
299
|
+
<EmptyState
|
|
300
|
+
title="No files match"
|
|
301
|
+
description={`Nothing in the night answers to "${query.trim()}".`}
|
|
302
|
+
isCompact
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
</Stack>
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const propertiesBody = (
|
|
309
|
+
<Stack direction="vertical" gap={3} style={styles.propertiesPanel}>
|
|
310
|
+
<SegmentedControl
|
|
311
|
+
label="Properties panel sections"
|
|
312
|
+
value={activePropertiesTab}
|
|
313
|
+
onChange={setActivePropertiesTab}
|
|
314
|
+
size="sm"
|
|
315
|
+
layout="fill"
|
|
316
|
+
>
|
|
317
|
+
<SegmentedControlItem label="Properties" value="properties" />
|
|
318
|
+
<SegmentedControlItem label="History" value="history" />
|
|
319
|
+
</SegmentedControl>
|
|
320
|
+
{activePropertiesTab === "properties" ? (
|
|
321
|
+
<Stack direction="vertical" gap={3} style={styles.propertiesContent}>
|
|
322
|
+
<Stack direction="vertical" gap={1}>
|
|
323
|
+
<Heading level={3} maxLines={1}>
|
|
324
|
+
{activeFile}
|
|
325
|
+
</Heading>
|
|
326
|
+
<Text color="secondary" type="supporting" maxLines={1}>
|
|
327
|
+
src/components/{activeFile}
|
|
328
|
+
</Text>
|
|
329
|
+
</Stack>
|
|
330
|
+
<MetadataList style={styles.metadataCompact}>
|
|
331
|
+
{PROPERTIES.map((prop) => (
|
|
332
|
+
<MetadataListItem key={prop.label} label={prop.label}>
|
|
333
|
+
{prop.value}
|
|
334
|
+
</MetadataListItem>
|
|
335
|
+
))}
|
|
336
|
+
</MetadataList>
|
|
337
|
+
<Stack direction="vertical" gap={2} style={styles.propertyActions}>
|
|
338
|
+
<Button label="Format Document" size="sm" variant="secondary" />
|
|
339
|
+
<Button label="Go to Definition" size="sm" variant="secondary" />
|
|
340
|
+
<Button label="Find References" size="sm" variant="secondary" />
|
|
341
|
+
</Stack>
|
|
342
|
+
</Stack>
|
|
343
|
+
) : (
|
|
344
|
+
<Stack direction="vertical" gap={1}>
|
|
345
|
+
<List>
|
|
346
|
+
{HISTORY_ITEMS.map((item) => (
|
|
347
|
+
<ListItem
|
|
348
|
+
key={item.label}
|
|
349
|
+
label={item.label}
|
|
350
|
+
endContent={
|
|
351
|
+
<Text type="supporting" color="secondary" maxLines={1} hasTabularNumbers>
|
|
352
|
+
{item.time}
|
|
353
|
+
</Text>
|
|
354
|
+
}
|
|
355
|
+
startContent={<StatusDot variant="neutral" label="Timeline marker" />}
|
|
356
|
+
/>
|
|
357
|
+
))}
|
|
358
|
+
</List>
|
|
359
|
+
</Stack>
|
|
360
|
+
)}
|
|
361
|
+
</Stack>
|
|
362
|
+
);
|
|
363
|
+
|
|
250
364
|
return (
|
|
251
365
|
<Layout
|
|
252
366
|
height="fill"
|
|
367
|
+
style={pageStyle}
|
|
368
|
+
// Below 768 the two side panels have no room beside the editor; these
|
|
369
|
+
// are the only controls that reach them there. Desktop keeps the
|
|
370
|
+
// resize handles and omits the header entirely.
|
|
371
|
+
header={
|
|
372
|
+
isMobile ? (
|
|
373
|
+
<LayoutHeader hasDivider padding={4}>
|
|
374
|
+
<Stack direction="horizontal" gap={2} vAlign="center" wrap="wrap">
|
|
375
|
+
<Button
|
|
376
|
+
label="Browse files"
|
|
377
|
+
variant="secondary"
|
|
378
|
+
aria-haspopup="dialog"
|
|
379
|
+
aria-expanded={isFileTreeOpen}
|
|
380
|
+
onClick={() => setFileTreeOpen(true)}
|
|
381
|
+
/>
|
|
382
|
+
<Button
|
|
383
|
+
label="File properties"
|
|
384
|
+
variant="secondary"
|
|
385
|
+
aria-haspopup="dialog"
|
|
386
|
+
aria-expanded={isPropertiesOpen}
|
|
387
|
+
onClick={() => setPropertiesOpen(true)}
|
|
388
|
+
/>
|
|
389
|
+
</Stack>
|
|
390
|
+
</LayoutHeader>
|
|
391
|
+
) : undefined
|
|
392
|
+
}
|
|
253
393
|
content={
|
|
254
394
|
<LayoutContent padding={0}>
|
|
255
395
|
<VisuallyHidden as="h1">Code editor</VisuallyHidden>
|
|
@@ -260,27 +400,7 @@ export default function IdeWorkspace() {
|
|
|
260
400
|
<>
|
|
261
401
|
{!startPanel.isCollapsed && (
|
|
262
402
|
<LayoutPanel width={startPanel.size} hasDivider={false} padding={4}>
|
|
263
|
-
|
|
264
|
-
<TextInput
|
|
265
|
-
label="Search files"
|
|
266
|
-
isLabelHidden
|
|
267
|
-
value={query}
|
|
268
|
-
onChange={setQuery}
|
|
269
|
-
placeholder="Search the night"
|
|
270
|
-
size="md"
|
|
271
|
-
startIcon={Search}
|
|
272
|
-
hasClear
|
|
273
|
-
/>
|
|
274
|
-
{visibleTree.length > 0 ? (
|
|
275
|
-
<TreeList items={visibleTree} density="compact" />
|
|
276
|
-
) : (
|
|
277
|
-
<EmptyState
|
|
278
|
-
title="No files match"
|
|
279
|
-
description={`Nothing in the night answers to "${query.trim()}".`}
|
|
280
|
-
isCompact
|
|
281
|
-
/>
|
|
282
|
-
)}
|
|
283
|
-
</Stack>
|
|
403
|
+
{fileExplorer}
|
|
284
404
|
</LayoutPanel>
|
|
285
405
|
)}
|
|
286
406
|
<ResizeHandle
|
|
@@ -399,78 +519,7 @@ export default function IdeWorkspace() {
|
|
|
399
519
|
/>
|
|
400
520
|
{!endPanel.isCollapsed && (
|
|
401
521
|
<LayoutPanel width={endPanel.size} hasDivider={false} padding={4}>
|
|
402
|
-
|
|
403
|
-
<SegmentedControl
|
|
404
|
-
label="Properties panel sections"
|
|
405
|
-
value={activePropertiesTab}
|
|
406
|
-
onChange={setActivePropertiesTab}
|
|
407
|
-
size="sm"
|
|
408
|
-
layout="fill"
|
|
409
|
-
>
|
|
410
|
-
<SegmentedControlItem label="Properties" value="properties" />
|
|
411
|
-
<SegmentedControlItem label="History" value="history" />
|
|
412
|
-
</SegmentedControl>
|
|
413
|
-
{activePropertiesTab === "properties" ? (
|
|
414
|
-
<Stack
|
|
415
|
-
direction="vertical"
|
|
416
|
-
gap={3}
|
|
417
|
-
style={styles.propertiesContent}
|
|
418
|
-
>
|
|
419
|
-
<Stack direction="vertical" gap={1}>
|
|
420
|
-
<Heading level={3} maxLines={1}>
|
|
421
|
-
{activeFile}
|
|
422
|
-
</Heading>
|
|
423
|
-
<Text color="secondary" type="supporting" maxLines={1}>
|
|
424
|
-
src/components/{activeFile}
|
|
425
|
-
</Text>
|
|
426
|
-
</Stack>
|
|
427
|
-
<MetadataList style={styles.metadataCompact}>
|
|
428
|
-
{PROPERTIES.map((prop) => (
|
|
429
|
-
<MetadataListItem key={prop.label} label={prop.label}>
|
|
430
|
-
{prop.value}
|
|
431
|
-
</MetadataListItem>
|
|
432
|
-
))}
|
|
433
|
-
</MetadataList>
|
|
434
|
-
<Stack
|
|
435
|
-
direction="vertical"
|
|
436
|
-
gap={2}
|
|
437
|
-
style={styles.propertyActions}
|
|
438
|
-
>
|
|
439
|
-
<Button label="Format Document" size="sm" variant="secondary" />
|
|
440
|
-
<Button
|
|
441
|
-
label="Go to Definition"
|
|
442
|
-
size="sm"
|
|
443
|
-
variant="secondary"
|
|
444
|
-
/>
|
|
445
|
-
<Button label="Find References" size="sm" variant="secondary" />
|
|
446
|
-
</Stack>
|
|
447
|
-
</Stack>
|
|
448
|
-
) : (
|
|
449
|
-
<Stack direction="vertical" gap={1}>
|
|
450
|
-
<List>
|
|
451
|
-
{HISTORY_ITEMS.map((item) => (
|
|
452
|
-
<ListItem
|
|
453
|
-
key={item.label}
|
|
454
|
-
label={item.label}
|
|
455
|
-
endContent={
|
|
456
|
-
<Text
|
|
457
|
-
type="supporting"
|
|
458
|
-
color="secondary"
|
|
459
|
-
maxLines={1}
|
|
460
|
-
hasTabularNumbers
|
|
461
|
-
>
|
|
462
|
-
{item.time}
|
|
463
|
-
</Text>
|
|
464
|
-
}
|
|
465
|
-
startContent={
|
|
466
|
-
<StatusDot variant="neutral" label="Timeline marker" />
|
|
467
|
-
}
|
|
468
|
-
/>
|
|
469
|
-
))}
|
|
470
|
-
</List>
|
|
471
|
-
</Stack>
|
|
472
|
-
)}
|
|
473
|
-
</Stack>
|
|
522
|
+
{propertiesBody}
|
|
474
523
|
</LayoutPanel>
|
|
475
524
|
)}
|
|
476
525
|
</>
|
|
@@ -480,6 +529,38 @@ export default function IdeWorkspace() {
|
|
|
480
529
|
</LayoutContent>
|
|
481
530
|
}
|
|
482
531
|
/>
|
|
532
|
+
{/* Below 768 the file tree and the properties rail are Drawers
|
|
533
|
+
opened from the header: as columns they would leave the
|
|
534
|
+
editor nothing. Both are gated on `isMobile`, so widening
|
|
535
|
+
the viewport restores the desktop panes without leaving an
|
|
536
|
+
overlay behind. DrawerHeader draws the close button only
|
|
537
|
+
when it is given onOpenChange. */}
|
|
538
|
+
<Drawer
|
|
539
|
+
side="start"
|
|
540
|
+
label="File tree"
|
|
541
|
+
isOpen={isMobile && isFileTreeOpen}
|
|
542
|
+
onOpenChange={setFileTreeOpen}
|
|
543
|
+
width={startPanel.size}
|
|
544
|
+
>
|
|
545
|
+
<Layout
|
|
546
|
+
height="fill"
|
|
547
|
+
header={<DrawerHeader title="File tree" onOpenChange={setFileTreeOpen} />}
|
|
548
|
+
content={<LayoutContent padding={4}>{fileExplorer}</LayoutContent>}
|
|
549
|
+
/>
|
|
550
|
+
</Drawer>
|
|
551
|
+
<Drawer
|
|
552
|
+
side="end"
|
|
553
|
+
label="File properties"
|
|
554
|
+
isOpen={isMobile && isPropertiesOpen}
|
|
555
|
+
onOpenChange={setPropertiesOpen}
|
|
556
|
+
width={endPanel.size}
|
|
557
|
+
>
|
|
558
|
+
<Layout
|
|
559
|
+
height="fill"
|
|
560
|
+
header={<DrawerHeader title="File properties" onOpenChange={setPropertiesOpen} />}
|
|
561
|
+
content={<LayoutContent padding={4}>{propertiesBody}</LayoutContent>}
|
|
562
|
+
/>
|
|
563
|
+
</Drawer>
|
|
483
564
|
</LayoutContent>
|
|
484
565
|
}
|
|
485
566
|
/>
|
|
@@ -47,6 +47,12 @@ import { BellRing, Plus } from "lucide-react";
|
|
|
47
47
|
import { type CSSProperties, useMemo, useState } from "react";
|
|
48
48
|
|
|
49
49
|
const styles: Record<string, CSSProperties> = {
|
|
50
|
+
// The page's definite height. `Layout height="fill"` is height:100%, which
|
|
51
|
+
// resolves to auto against a content-sized host and hands the scroll to the
|
|
52
|
+
// document; 100dvh is absolute and needs no ancestor.
|
|
53
|
+
page: {
|
|
54
|
+
height: "100dvh",
|
|
55
|
+
},
|
|
50
56
|
contentFill: {
|
|
51
57
|
height: "100%",
|
|
52
58
|
minHeight: 0,
|
|
@@ -56,7 +62,12 @@ const styles: Record<string, CSSProperties> = {
|
|
|
56
62
|
},
|
|
57
63
|
groupHeader: {
|
|
58
64
|
padding: "var(--spacing-2) var(--spacing-3)",
|
|
59
|
-
|
|
65
|
+
// A band sitting over its own rows, so it lifts off the page surface.
|
|
66
|
+
// It used to lean on --color-background-muted, which held Selection
|
|
67
|
+
// #44475A; that token is Background Dark #21222C now, a RECESS, and a
|
|
68
|
+
// header painted in it reads as a trough between the rows instead of a
|
|
69
|
+
// band above them.
|
|
70
|
+
backgroundColor: "var(--color-background-surface)",
|
|
60
71
|
},
|
|
61
72
|
// All FOUR edges, not block-only. With `overflowY: auto` and no `overflowX`,
|
|
62
73
|
// the inline axis computes to `auto` too, so this box clips horizontally as
|
|
@@ -360,10 +371,8 @@ function IncidentRows({
|
|
|
360
371
|
{groups.map((group) => (
|
|
361
372
|
<VStack gap={0} key={group.status}>
|
|
362
373
|
<HStack gap={2} vAlign="center" style={styles.groupHeader}>
|
|
363
|
-
{/*
|
|
364
|
-
|
|
365
|
-
floor. The group heading is the string this sidebar is scanned
|
|
366
|
-
for. Hierarchy comes from the two type roles (principle 6),
|
|
374
|
+
{/* The group heading is the string this sidebar is scanned
|
|
375
|
+
for, so hierarchy comes from the two type roles (principle 6),
|
|
367
376
|
not from dimming the count. */}
|
|
368
377
|
<Text type="label">{STATUS[group.status].label}</Text>
|
|
369
378
|
<Text type="supporting" hasTabularNumbers>
|
|
@@ -547,6 +556,7 @@ export default function IncidentConsole() {
|
|
|
547
556
|
<>
|
|
548
557
|
<Layout
|
|
549
558
|
height="fill"
|
|
559
|
+
style={styles.page}
|
|
550
560
|
header={
|
|
551
561
|
<LayoutHeader hasDivider padding={6}>
|
|
552
562
|
{isNarrow ? (
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
3
|
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
4
|
// human drift check — update it with the file, never alone):
|
|
5
|
-
// S[h=100dvh] > (L[h=fill] > (LH[divider p=6] > Tbar[g=2] > (Hd"Sprint Board"[level=1 t=display-2] + Bd.neutral"8") + (H[g=2 wrap] > SE"Sprint" + D.strong + (H[g=1 a=center wrap] > IB"Sort" + IB"Filter" + IB"Search") + B.primary"Add task")) + (LC[p=0 !scroll] > G[c={min:280,max:4} g=4] > (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"To-do"[level=2] + (Po > IB)) + Tx"2") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)*2)) + (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"In progress"[level=2] + (Po > IB)) + Tx"2") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)*2)) + (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"In review"[level=2] + (Po > IB)) + Tx"1") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx))) + (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"Done"[level=2] + (Po > IB)) + Tx"3") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)*3))) + (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)
|
|
5
|
+
// S[h=100dvh] > VH + (L[h=fill] > (LH[divider p=6] > Tbar[g=2] > (Hd"Sprint Board"[level=1 t=display-2] + Bd.neutral"8") + (H[g=2 wrap] > SE"Sprint" + D.strong + (H[g=1 a=center wrap] > IB"Sort" + IB"Filter" + IB"Search") + B.primary"Add task")) + (LC[p=0 !scroll] > G[c={min:280,max:4} g=4] > (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"To-do"[level=2] + (Po > IB)) + Tx"2") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)*2)) + (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"In progress"[level=2] + (Po > IB)) + Tx"2") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)*2)) + (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"In review"[level=2] + (Po > IB)) + Tx"1") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx))) + (C.muted[p=0] > L[h=fill] > (LH[divider p=3] > H[j=between a=center] > (H[g=1 a=center] > SD + Hd"Done"[level=2] + (Po > IB)) + Tx"3") + (LC[p=3] > V[g=2] > (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)*3))) + (C[p=3] > V[g=2] > (H[j=between] > (H[g=2] > Tk + Bd) + MM) + (V[g=1] > Hd[level=3] + Tx) + Tx)
|
|
6
6
|
|
|
7
7
|
import { Badge } from "@astryxdesign/core/Badge";
|
|
8
8
|
import { Button } from "@astryxdesign/core/Button";
|
|
@@ -21,6 +21,7 @@ import { StatusDot } from "@astryxdesign/core/StatusDot";
|
|
|
21
21
|
import { Heading, Text } from "@astryxdesign/core/Text";
|
|
22
22
|
import { Token } from "@astryxdesign/core/Token";
|
|
23
23
|
import { Toolbar } from "@astryxdesign/core/Toolbar";
|
|
24
|
+
import { VisuallyHidden } from "@astryxdesign/core/VisuallyHidden";
|
|
24
25
|
import {
|
|
25
26
|
ArrowUpDown,
|
|
26
27
|
CircleCheck,
|
|
@@ -312,6 +313,10 @@ function BoardCard({
|
|
|
312
313
|
// focused card across columns. The pointer DnD below stays quarantined and
|
|
313
314
|
// untouched; this only reuses its moveItem commit.
|
|
314
315
|
const onKeyDown = (e: ReactKeyboardEvent) => {
|
|
316
|
+
// Let the card's own controls (the actions menu) keep arrow keys.
|
|
317
|
+
if ((e.target as HTMLElement).closest('button, [role="menuitem"], [role="menu"]')) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
315
320
|
const at = COLUMNS.findIndex((c) => c.id === item.column);
|
|
316
321
|
const to =
|
|
317
322
|
e.key === "ArrowRight"
|
|
@@ -333,7 +338,11 @@ function BoardCard({
|
|
|
333
338
|
// drag clone floats above the board with the high shadow token instead.
|
|
334
339
|
style={{ cursor: "grab", userSelect: "none", touchAction: "none" }}
|
|
335
340
|
tabIndex={0}
|
|
336
|
-
|
|
341
|
+
// Not `button`: the card contains its own actions menu and a heading,
|
|
342
|
+
// and `button` makes every descendant presentational (ARIA
|
|
343
|
+
// "children presentational"), hiding both from assistive tech. The
|
|
344
|
+
// card is a member of the column list that happens to be focusable.
|
|
345
|
+
role="listitem"
|
|
337
346
|
aria-label={`${item.title}. In ${COLUMNS.find((c) => c.id === item.column)?.title}. Press left or right arrow to move across columns.`}
|
|
338
347
|
onPointerDown={(e) => onPointerDown(e, item.id)}
|
|
339
348
|
onKeyDown={onKeyDown}
|
|
@@ -357,7 +366,15 @@ function BoardColumn({
|
|
|
357
366
|
children: ReactNode;
|
|
358
367
|
}) {
|
|
359
368
|
return (
|
|
360
|
-
<Card
|
|
369
|
+
<Card
|
|
370
|
+
variant="muted"
|
|
371
|
+
padding={0}
|
|
372
|
+
style={{ minWidth: 0, height: "100%" }}
|
|
373
|
+
// Each column is a landmark: the board region alone announces four
|
|
374
|
+
// undifferentiated lists of cards.
|
|
375
|
+
role="region"
|
|
376
|
+
aria-label={`${meta.title} column`}
|
|
377
|
+
>
|
|
361
378
|
<Layout
|
|
362
379
|
height="fill"
|
|
363
380
|
header={
|
|
@@ -421,6 +438,9 @@ export default function KanbanBoard() {
|
|
|
421
438
|
const [items, setItems] = useState<WorkItem[]>(INITIAL_ITEMS);
|
|
422
439
|
const [sprint, setSprint] = useState("003");
|
|
423
440
|
const [drag, setDrag] = useState<DragState | null>(null);
|
|
441
|
+
// Polite announcement for moves that have no visual follow (keyboard arrows,
|
|
442
|
+
// menu "Move to …"). Pointer drags show their own landing ghost.
|
|
443
|
+
const [announcement, setAnnouncement] = useState("");
|
|
424
444
|
|
|
425
445
|
// Live element registries for pointer hit-testing (kept out of render state).
|
|
426
446
|
const columnEls = useRef(new Map<ColumnId, HTMLElement>());
|
|
@@ -453,7 +473,18 @@ export default function KanbanBoard() {
|
|
|
453
473
|
const itemsByColumn = groupByColumn(items);
|
|
454
474
|
|
|
455
475
|
const moveItem = (id: string, to: ColumnId) => {
|
|
476
|
+
// A cross-column move remounts the card inside the destination list, so
|
|
477
|
+
// focus would fall to <body> and the next arrow press would be lost.
|
|
478
|
+
const hadFocus = cardEls.current.get(id)?.contains(document.activeElement) ?? false;
|
|
479
|
+
const moved = items.find((item) => item.id === id);
|
|
480
|
+
const column = COLUMNS.find((meta) => meta.id === to);
|
|
456
481
|
setItems((prev) => prev.map((item) => (item.id === id ? { ...item, column: to } : item)));
|
|
482
|
+
if (moved && column) {
|
|
483
|
+
setAnnouncement(`Moved ${moved.title} to ${column.title}`);
|
|
484
|
+
}
|
|
485
|
+
if (hadFocus) {
|
|
486
|
+
requestAnimationFrame(() => cardEls.current.get(id)?.focus());
|
|
487
|
+
}
|
|
457
488
|
};
|
|
458
489
|
|
|
459
490
|
// Resolve the pointer position to a column + insertion index, ignoring the
|
|
@@ -621,6 +652,8 @@ export default function KanbanBoard() {
|
|
|
621
652
|
<VStack
|
|
622
653
|
key="drag-ghost"
|
|
623
654
|
// Placeholder marking the landing slot; matches the dragged card's height.
|
|
655
|
+
// Hidden from assistive tech: it is a visual target, not a list member.
|
|
656
|
+
aria-hidden="true"
|
|
624
657
|
style={{
|
|
625
658
|
height: ghostTarget.height,
|
|
626
659
|
borderRadius: "var(--radius-element)",
|
|
@@ -630,11 +663,16 @@ export default function KanbanBoard() {
|
|
|
630
663
|
);
|
|
631
664
|
}
|
|
632
665
|
|
|
633
|
-
return
|
|
666
|
+
return (
|
|
667
|
+
<VStack gap={2} role="list">
|
|
668
|
+
{nodes}
|
|
669
|
+
</VStack>
|
|
670
|
+
);
|
|
634
671
|
};
|
|
635
672
|
|
|
636
673
|
return (
|
|
637
674
|
<Section height="100dvh">
|
|
675
|
+
<VisuallyHidden role="status">{announcement}</VisuallyHidden>
|
|
638
676
|
<Layout
|
|
639
677
|
height="fill"
|
|
640
678
|
header={
|
|
@@ -718,6 +756,10 @@ export default function KanbanBoard() {
|
|
|
718
756
|
<Card
|
|
719
757
|
padding={3}
|
|
720
758
|
elevation="none"
|
|
759
|
+
// Visual proxy for the pointer drag: its contents duplicate the
|
|
760
|
+
// real card (including an actions menu), so keep it out of the
|
|
761
|
+
// accessibility tree and the tab order.
|
|
762
|
+
inert
|
|
721
763
|
style={{
|
|
722
764
|
position: "fixed",
|
|
723
765
|
insetBlockStart: 0,
|
package/templates/library.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
3
|
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
4
|
// human drift check — update it with the file, never alone):
|
|
5
|
-
// L[h=fill] > (LH[divider p=6] > (V[g=1] > Hd"Library"[level=1] + Tx"
|
|
5
|
+
// L[h=fill] > (LH[divider p=6] > (V[g=1] > Hd"Library"[level=1 t=display-2] + Tx"{ITEMS.length} components across {CATEGORIES.length - 1} categories"[t=body])) + (LC[p=6] > V[g=8] > (V[g=4] > TI"Search" + (H[g=3 a=center] > (SI[fill] > V > TgG"Filter" > OFL > Tg"All"! + Tg"Layout" + Tg"Forms" + Tg"Navigation" + Tg"Feedback" + Tg"Data") + DD"A-Z")) + (V[g=8] > (D + (V[g=8] > (H[j=between a=center] > Hd"Layout"[level=2] + Tx"{section.items.length} items"[t=body]) + (G[c={min:280} g=4] > (C[p=0] > AR[ratio=16/9] > Scene{SceneTile}) + (S.transparent[p=4] > V[g=1] > Hd"Card"[level=3] + Tx"Description"[t=body]))*{section.items.length}))*{CATEGORIES.length - 1}))
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Library — a browsable grid of design-system entries grouped by category. Frame: page header (title) | content column (search, filter row, s (Frame/responsive/container: see XLE header above.)
|
|
@@ -26,6 +26,7 @@ import { ToggleButton, ToggleButtonGroup } from "@astryxdesign/core/ToggleButton
|
|
|
26
26
|
import type { SceneHue } from "astryx-dracula/shared/scene-hues";
|
|
27
27
|
import { SceneTile } from "astryx-dracula/shared/scene-tile";
|
|
28
28
|
import { Search } from "lucide-react";
|
|
29
|
+
import type { CSSProperties } from "react";
|
|
29
30
|
import { useState } from "react";
|
|
30
31
|
|
|
31
32
|
interface LibraryItem {
|
|
@@ -285,6 +286,11 @@ function LibraryCard({ item }: { item: LibraryItem }) {
|
|
|
285
286
|
);
|
|
286
287
|
}
|
|
287
288
|
|
|
289
|
+
// Definite ancestor for `height="fill"`: 100dvh, not minHeight: '100%' (a
|
|
290
|
+
// percentage min-height against an indefinite block computes to 0 — CSS 2.1
|
|
291
|
+
// 10.5). Templates ship standalone, so the page owns this, not the host.
|
|
292
|
+
const pageStyle: CSSProperties = { height: "100dvh" };
|
|
293
|
+
|
|
288
294
|
export default function LibraryGrid() {
|
|
289
295
|
const [activeTab, setActiveTab] = useState("All");
|
|
290
296
|
const [search, setSearch] = useState("");
|
|
@@ -329,12 +335,15 @@ export default function LibraryGrid() {
|
|
|
329
335
|
return (
|
|
330
336
|
<Layout
|
|
331
337
|
height="fill"
|
|
338
|
+
style={pageStyle}
|
|
332
339
|
header={
|
|
333
340
|
<LayoutHeader hasDivider padding={6}>
|
|
334
341
|
<VStack gap={1}>
|
|
335
|
-
<Heading level={1}>
|
|
342
|
+
<Heading level={1} type="display-2">
|
|
343
|
+
Library
|
|
344
|
+
</Heading>
|
|
336
345
|
<Text type="body" color="secondary">
|
|
337
|
-
|
|
346
|
+
{ITEMS.length} components across {CATEGORIES.length - 1} categories
|
|
338
347
|
</Text>
|
|
339
348
|
</VStack>
|
|
340
349
|
</LayoutHeader>
|
|
@@ -15,7 +15,7 @@ import { Link } from "@astryxdesign/core/Link";
|
|
|
15
15
|
import { Section } from "@astryxdesign/core/Section";
|
|
16
16
|
import { Heading, Text } from "@astryxdesign/core/Text";
|
|
17
17
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
18
|
-
import { authPageStyle
|
|
18
|
+
import { authPageStyle } from "astryx-dracula/shared/auth-chrome-config";
|
|
19
19
|
import {
|
|
20
20
|
AUTH_BRAND_NAME,
|
|
21
21
|
AUTH_EMAIL_PLACEHOLDER,
|
|
@@ -136,7 +136,7 @@ export default function LoginSplit() {
|
|
|
136
136
|
label="Email"
|
|
137
137
|
isLabelHidden
|
|
138
138
|
type="email"
|
|
139
|
-
|
|
139
|
+
autoComplete="email"
|
|
140
140
|
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
141
141
|
value={email}
|
|
142
142
|
onChange={(v: string) => {
|
|
@@ -153,7 +153,7 @@ export default function LoginSplit() {
|
|
|
153
153
|
isLabelHidden
|
|
154
154
|
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
155
155
|
type="password"
|
|
156
|
-
|
|
156
|
+
autoComplete="current-password"
|
|
157
157
|
value={password}
|
|
158
158
|
onChange={(v: string) => {
|
|
159
159
|
setPassword(v);
|