astryx-dracula 0.2.1 → 0.3.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 +30 -18
- package/README.md +17 -12
- package/USAGE.md +29 -10
- package/astryx-dracula.js +19 -13
- package/astryx-dracula.variants.d.ts +15 -0
- package/package.json +59 -17
- package/shared/auth-chrome-config.ts +18 -0
- package/shared/auth-chrome.tsx +25 -0
- package/shared/auth-copy.ts +22 -0
- package/shared/chaptered-doc-config.ts +92 -0
- package/shared/chaptered-doc.tsx +332 -0
- package/shared/chart-hues.ts +27 -0
- package/shared/chart-labels.tsx +44 -0
- package/shared/chart-legend.tsx +100 -0
- package/shared/chart-panel-style.ts +13 -0
- package/shared/data-bar.tsx +160 -0
- package/shared/gallery-image.ts +20 -0
- package/shared/login-demo.ts +7 -0
- package/shared/metric-delta.tsx +36 -0
- package/shared/order-desk.tsx +273 -0
- package/shared/revenue-chart.tsx +174 -0
- package/shared/scene-castle.tsx +322 -0
- package/shared/scene-frame.tsx +88 -0
- package/shared/scene-hues.ts +49 -0
- package/shared/scene-tile.tsx +128 -0
- package/shared/settings-data.ts +125 -0
- package/shared/settings-rows.tsx +179 -0
- package/shared/sparkline.tsx +94 -0
- package/shared/sso-icons.tsx +30 -0
- package/templates/ai-chat-landing.tsx +66 -33
- package/templates/ai-chat.tsx +36 -19
- package/templates/blank.tsx +4 -3
- package/templates/centered-hero.tsx +14 -125
- package/templates/classic-gallery.tsx +20 -66
- package/templates/contact-form.tsx +59 -39
- package/templates/dashboard-portfolio.tsx +143 -226
- package/templates/dashboard.tsx +122 -122
- package/templates/detail-page.tsx +95 -91
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +83 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +29 -19
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +52 -74
- package/templates/editor.tsx +112 -84
- package/templates/file-explorer.tsx +131 -84
- package/templates/form-two-column.tsx +61 -125
- package/templates/gallery-hero.tsx +15 -171
- package/templates/ide.tsx +22 -20
- package/templates/incident-console.tsx +104 -70
- package/templates/kanban-board.tsx +99 -42
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +37 -42
- package/templates/login-card.tsx +58 -87
- package/templates/login-split.tsx +88 -126
- package/templates/login-sso.tsx +105 -64
- package/templates/login.tsx +107 -53
- package/templates/messaging-shell.tsx +171 -144
- package/templates/mixed-gallery.tsx +29 -130
- package/templates/payment-form.tsx +368 -290
- package/templates/product-detail.tsx +39 -58
- package/templates/product-gallery.tsx +30 -41
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +135 -282
- package/templates/settings-sidebar.tsx +178 -268
- package/templates/settings.tsx +41 -14
- package/templates/shell-nav.tsx +31 -24
- package/templates/shell-side-nav.tsx +29 -20
- package/templates/shell-top-nav.tsx +80 -44
- package/templates/side-gallery.tsx +38 -94
- package/templates/table-grouped.tsx +62 -34
- package/templates/table-page-chart.tsx +23 -302
- package/templates/table-page-heatmap-status.tsx +156 -92
- package/templates/table-page-shoe-store-heatmap.tsx +18 -293
- package/templates/table-page.tsx +23 -7
- package/templates/table.tsx +23 -8
- package/templates/tech-report.template.mjs +6 -0
- package/templates/tech-report.tsx +1486 -0
- package/templates/theme-showcase.template.mjs +1 -1
- package/templates/theme-showcase.tsx +299 -237
- package/theme.css +76 -14
- package/tokens.css +425 -30
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
3
|
// L > LC[p=6] > V[g=10] > (V[g=6 a=center] > (V[g=3 a=center] > Hd"Little haunts, everywhere you roam"[level=1 t=display-2] + Tx"Sometimes all it takes"[t=body]) + (H[j=center g=3] > B.primary"Enter the gallery" + B.secondary"Read the lore")) + (G[c={min:200} g=4] > AR*3)
|
|
4
4
|
|
|
5
|
-
import type {CSSProperties} from 'react';
|
|
6
5
|
import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
7
6
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
8
7
|
import {Button} from '@astryxdesign/core/Button';
|
|
@@ -10,171 +9,16 @@ import {Icon} from '@astryxdesign/core/Icon';
|
|
|
10
9
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
11
10
|
import {AspectRatio} from '@astryxdesign/core/AspectRatio';
|
|
12
11
|
import {ArrowRight} from 'lucide-react';
|
|
12
|
+
import {galleryImage, galleryImageClip} from 'astryx-dracula/shared/gallery-image';
|
|
13
|
+
import {SceneCastle} from 'astryx-dracula/shared/scene-castle';
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
height: '100%',
|
|
22
|
-
display: 'block',
|
|
23
|
-
};
|
|
24
|
-
// Rounds the gallery corners. No radius prop on AspectRatio (#2582);
|
|
25
|
-
// overflow clip masks the SVG scene to the rounded corners.
|
|
26
|
-
const galleryImageClip: CSSProperties = {
|
|
27
|
-
borderRadius: 'var(--radius-container)',
|
|
28
|
-
overflow: 'clip',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
function NightCastleScene() {
|
|
32
|
-
return (
|
|
33
|
-
<svg
|
|
34
|
-
style={galleryImage}
|
|
35
|
-
viewBox="0 0 400 500"
|
|
36
|
-
role="img"
|
|
37
|
-
aria-label="Moonlit castle on midnight hills">
|
|
38
|
-
<title>Castle under a harvest moon</title>
|
|
39
|
-
<rect width="400" height="500" fill="var(--dracula-bg-dark)" />
|
|
40
|
-
<g fill="var(--dracula-purple)">
|
|
41
|
-
<circle cx="50" cy="60" r="2.5" />
|
|
42
|
-
<circle cx="140" cy="130" r="2" />
|
|
43
|
-
<circle cx="240" cy="50" r="2.5" />
|
|
44
|
-
<circle cx="330" cy="110" r="2" />
|
|
45
|
-
<circle cx="300" cy="200" r="2.5" />
|
|
46
|
-
</g>
|
|
47
|
-
<g fill="var(--dracula-cyan)">
|
|
48
|
-
<circle cx="95" cy="90" r="2" />
|
|
49
|
-
<circle cx="200" cy="100" r="2.5" />
|
|
50
|
-
<circle cx="360" cy="60" r="2" />
|
|
51
|
-
</g>
|
|
52
|
-
<circle cx="290" cy="120" r="52" fill="var(--dracula-yellow)" />
|
|
53
|
-
<circle
|
|
54
|
-
cx="272"
|
|
55
|
-
cy="106"
|
|
56
|
-
r="9"
|
|
57
|
-
fill="var(--dracula-orange)"
|
|
58
|
-
opacity={0.5}
|
|
59
|
-
/>
|
|
60
|
-
<g
|
|
61
|
-
fill="none"
|
|
62
|
-
stroke="var(--dracula-purple)"
|
|
63
|
-
strokeWidth={4}
|
|
64
|
-
strokeLinecap="round">
|
|
65
|
-
<path d="M70 170 q12 -12 24 0 q12 -12 24 0" />
|
|
66
|
-
<path d="M150 130 q9 -9 18 0 q9 -9 18 0" />
|
|
67
|
-
</g>
|
|
68
|
-
<path
|
|
69
|
-
d="M0 360 Q140 300 260 350 T400 335 V500 H0 Z"
|
|
70
|
-
fill="var(--dracula-selection)"
|
|
71
|
-
/>
|
|
72
|
-
<path
|
|
73
|
-
d="M0 420 Q160 365 300 410 T400 400 V500 H0 Z"
|
|
74
|
-
fill="var(--dracula-bg-light)"
|
|
75
|
-
/>
|
|
76
|
-
<g>
|
|
77
|
-
<rect
|
|
78
|
-
x="140"
|
|
79
|
-
y="270"
|
|
80
|
-
width="120"
|
|
81
|
-
height="110"
|
|
82
|
-
rx={4}
|
|
83
|
-
fill="var(--dracula-bg-lighter)"
|
|
84
|
-
/>
|
|
85
|
-
<rect
|
|
86
|
-
x="118"
|
|
87
|
-
y="235"
|
|
88
|
-
width="44"
|
|
89
|
-
height="145"
|
|
90
|
-
rx={4}
|
|
91
|
-
fill="var(--dracula-bg-lighter)"
|
|
92
|
-
/>
|
|
93
|
-
<rect
|
|
94
|
-
x="238"
|
|
95
|
-
y="235"
|
|
96
|
-
width="44"
|
|
97
|
-
height="145"
|
|
98
|
-
rx={4}
|
|
99
|
-
fill="var(--dracula-bg-lighter)"
|
|
100
|
-
/>
|
|
101
|
-
<g fill="var(--dracula-yellow)">
|
|
102
|
-
<rect x="160" y="300" width="14" height="20" rx={2} />
|
|
103
|
-
<rect x="196" y="300" width="14" height="20" rx={2} />
|
|
104
|
-
<rect x="232" y="300" width="14" height="20" rx={2} />
|
|
105
|
-
<rect x="131" y="262" width="10" height="14" rx={2} />
|
|
106
|
-
<rect x="253" y="262" width="10" height="14" rx={2} />
|
|
107
|
-
</g>
|
|
108
|
-
<path
|
|
109
|
-
d="M185 380 v-28 a15 15 0 0 1 30 0 v28 Z"
|
|
110
|
-
fill="var(--dracula-bg-dark)"
|
|
111
|
-
/>
|
|
112
|
-
</g>
|
|
113
|
-
</svg>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function HarvestMoonScene() {
|
|
118
|
-
return (
|
|
119
|
-
<svg
|
|
120
|
-
style={galleryImage}
|
|
121
|
-
viewBox="0 0 400 500"
|
|
122
|
-
role="img"
|
|
123
|
-
aria-label="Harvest moon over midnight pines">
|
|
124
|
-
<title>Harvest moon over the pines</title>
|
|
125
|
-
<rect width="400" height="500" fill="var(--dracula-bg-dark)" />
|
|
126
|
-
<g fill="var(--dracula-pink)">
|
|
127
|
-
<circle cx="60" cy="80" r="2" />
|
|
128
|
-
<circle cx="160" cy="50" r="2.5" />
|
|
129
|
-
<circle cx="320" cy="70" r="2" />
|
|
130
|
-
<circle cx="90" cy="190" r="2.5" />
|
|
131
|
-
<circle cx="250" cy="160" r="2" />
|
|
132
|
-
</g>
|
|
133
|
-
<g fill="var(--dracula-cyan)">
|
|
134
|
-
<circle cx="110" cy="120" r="2" />
|
|
135
|
-
<circle cx="220" cy="90" r="2" />
|
|
136
|
-
<circle cx="350" cy="150" r="2.5" />
|
|
137
|
-
</g>
|
|
138
|
-
<circle cx="200" cy="190" r="72" fill="var(--dracula-yellow)" />
|
|
139
|
-
<circle
|
|
140
|
-
cx="176"
|
|
141
|
-
cy="170"
|
|
142
|
-
r="12"
|
|
143
|
-
fill="var(--dracula-orange)"
|
|
144
|
-
opacity={0.5}
|
|
145
|
-
/>
|
|
146
|
-
<circle
|
|
147
|
-
cx="222"
|
|
148
|
-
cy="210"
|
|
149
|
-
r="8"
|
|
150
|
-
fill="var(--dracula-orange)"
|
|
151
|
-
opacity={0.5}
|
|
152
|
-
/>
|
|
153
|
-
<g
|
|
154
|
-
fill="none"
|
|
155
|
-
stroke="var(--dracula-purple)"
|
|
156
|
-
strokeWidth={4}
|
|
157
|
-
strokeLinecap="round">
|
|
158
|
-
<path d="M90 260 q12 -12 24 0 q12 -12 24 0" />
|
|
159
|
-
<path d="M260 250 q9 -9 18 0 q9 -9 18 0" />
|
|
160
|
-
</g>
|
|
161
|
-
<path
|
|
162
|
-
d="M0 380 Q200 320 400 370 V500 H0 Z"
|
|
163
|
-
fill="var(--dracula-selection)"
|
|
164
|
-
/>
|
|
165
|
-
<g fill="var(--dracula-bg-light)">
|
|
166
|
-
<path d="M70 500 V400 l28 -44 28 44 v100 Z" />
|
|
167
|
-
<path d="M170 500 V420 l28 -44 28 44 v80 Z" />
|
|
168
|
-
<path d="M270 500 V410 l28 -44 28 44 v90 Z" />
|
|
169
|
-
</g>
|
|
170
|
-
<g fill="var(--dracula-yellow)">
|
|
171
|
-
<rect x="81" y="442" width="8" height="12" rx={2} />
|
|
172
|
-
<rect x="181" y="458" width="8" height="12" rx={2} />
|
|
173
|
-
<rect x="281" y="450" width="8" height="12" rx={2} />
|
|
174
|
-
</g>
|
|
175
|
-
</svg>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
15
|
+
// Castle + pines banners live in shared/scene-castle (`card`/`tall` variants:
|
|
16
|
+
// harvest-yellow moon, fg-stroke bats, per-scene hill constants). Fill +
|
|
17
|
+
// radius-clip live in shared/gallery-image (no Image primitive, #2582).
|
|
18
|
+
// The bats tile stays local: it is deliberately moonless-magic (the violet
|
|
19
|
+
// disc is ambient glow (comment grey), never tappable purple — the alt names the moonless sky), so the
|
|
20
|
+
// harvest-yellow rule does not apply; yellowing it would duplicate the pines
|
|
21
|
+
// tile instead.
|
|
178
22
|
|
|
179
23
|
function BatFlightScene() {
|
|
180
24
|
return (
|
|
@@ -182,10 +26,9 @@ function BatFlightScene() {
|
|
|
182
26
|
style={galleryImage}
|
|
183
27
|
viewBox="0 0 400 500"
|
|
184
28
|
role="img"
|
|
185
|
-
aria-label="Bats crossing a
|
|
186
|
-
<title>Bats over violet hills</title>
|
|
29
|
+
aria-label="Bats crossing a moonless night sky">
|
|
187
30
|
<rect width="400" height="500" fill="var(--dracula-bg-dark)" />
|
|
188
|
-
<circle cx="200" cy="140" r="60" fill="var(--dracula-
|
|
31
|
+
<circle cx="200" cy="140" r="60" fill="var(--dracula-comment)" />
|
|
189
32
|
<circle
|
|
190
33
|
cx="200"
|
|
191
34
|
cy="140"
|
|
@@ -231,14 +74,15 @@ function BatFlightScene() {
|
|
|
231
74
|
}
|
|
232
75
|
|
|
233
76
|
const SCENES = [
|
|
234
|
-
{alt: 'Moonlit castle on midnight hills', scene: <
|
|
235
|
-
{alt: 'Harvest moon over midnight pines', scene: <
|
|
236
|
-
{alt: 'Bats crossing a
|
|
77
|
+
{alt: 'Moonlit castle on midnight hills', scene: <SceneCastle variant="card" />},
|
|
78
|
+
{alt: 'Harvest moon over midnight pines', scene: <SceneCastle variant="tall" />},
|
|
79
|
+
{alt: 'Bats crossing a moonless night sky', scene: <BatFlightScene />},
|
|
237
80
|
];
|
|
238
81
|
|
|
239
82
|
export default function GalleryHero() {
|
|
240
83
|
return (
|
|
241
84
|
<Layout
|
|
85
|
+
height="auto"
|
|
242
86
|
content={
|
|
243
87
|
<LayoutContent padding={6}>
|
|
244
88
|
<VStack gap={10}>
|
package/templates/ide.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > (LP[p=4] > V[g=2] > TI"Search files" + TreeList) + (LC[p=0] > V > (SI[fill] > Cd) + (V > (TL > Tab"Terminal"! + Tab"Problems" + Tab"Output" + Tab"Debug") + Cd)) + (LP[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")))
|
|
3
|
+
// L > (LP[p=4] > V[g=2] > TI"Search files" + TreeList) + (LC[p=0] > (Hd"Code editor"[level=1] + V > (SI[fill] > Cd) + (V > (TL > Tab"Terminal"! + Tab"Problems" + Tab"Output" + Tab"Debug") + Cd))) + (LP[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")))
|
|
4
4
|
|
|
5
5
|
import {useState, useMemo, type CSSProperties} from 'react';
|
|
6
6
|
|
|
@@ -8,6 +8,7 @@ import {Layout, LayoutContent, LayoutPanel} from '@astryxdesign/core/Layout';
|
|
|
8
8
|
import {StatusDot} from '@astryxdesign/core/StatusDot';
|
|
9
9
|
import {ResizeHandle, useResizable} from '@astryxdesign/core/Resizable';
|
|
10
10
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
11
|
+
import {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
|
|
11
12
|
import {Center} from '@astryxdesign/core/Center';
|
|
12
13
|
import {CodeBlock} from '@astryxdesign/core/CodeBlock';
|
|
13
14
|
import {EmptyState} from '@astryxdesign/core/EmptyState';
|
|
@@ -38,20 +39,21 @@ const styles: Record<string, CSSProperties> = {
|
|
|
38
39
|
},
|
|
39
40
|
tabListPadding: {
|
|
40
41
|
paddingTop: 'var(--spacing-2)',
|
|
42
|
+
// 4px inline = offset 2 + ring thickness 2, so the focus ring lands INSIDE
|
|
43
|
+
// this box's clip instead of on its edge being cut in half. Deliberately
|
|
44
|
+
// NOT applied to `terminalPanel`: that would inset the CodeBlock itself,
|
|
45
|
+
// moving the code rather than giving the ring somewhere to live.
|
|
46
|
+
paddingInline: 'var(--spacing-1)',
|
|
41
47
|
},
|
|
42
48
|
metadataCompact: {
|
|
43
49
|
gap: 'var(--spacing-1) var(--spacing-3)',
|
|
44
50
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
marginTop: 6,
|
|
51
|
-
flexShrink: 0,
|
|
52
|
-
},
|
|
51
|
+
// No `overflow` here, deliberately. The only child is a `CodeBlock` at
|
|
52
|
+
// `height: 100%` whose own root is `overflow: hidden`, so this box could
|
|
53
|
+
// never overflow and the scroller never fired — it only clipped. An
|
|
54
|
+
// `overflow: auto` on a parent whose sole child is a fixed-height
|
|
55
|
+
// `overflow: hidden` box is inert, which is what the deleted line was.
|
|
53
56
|
editorArea: {
|
|
54
|
-
overflow: 'auto',
|
|
55
57
|
minHeight: 0,
|
|
56
58
|
},
|
|
57
59
|
fileExplorer: {
|
|
@@ -83,11 +85,6 @@ const containerStyle = {
|
|
|
83
85
|
gap: 8,
|
|
84
86
|
padding: 16,
|
|
85
87
|
};
|
|
86
|
-
const counterStyle = {
|
|
87
|
-
fontSize: 48,
|
|
88
|
-
fontWeight: 700,
|
|
89
|
-
fontVariantNumeric: 'tabular-nums',
|
|
90
|
-
};
|
|
91
88
|
|
|
92
89
|
export default function NightCounter() {
|
|
93
90
|
const [count, setCount] = useState(0);
|
|
@@ -101,14 +98,14 @@ export default function NightCounter() {
|
|
|
101
98
|
}, []);
|
|
102
99
|
|
|
103
100
|
return (
|
|
104
|
-
<
|
|
101
|
+
<Stack direction="vertical" gap={2} style={containerStyle}>
|
|
105
102
|
<Text type="label">Night counter</Text>
|
|
106
|
-
<
|
|
103
|
+
<Text type="display-1" hasTabularNumbers>
|
|
107
104
|
{count}
|
|
108
|
-
</
|
|
105
|
+
</Text>
|
|
109
106
|
<Button label="Increment" onClick={increment} />
|
|
110
107
|
<Button label="Reset" variant="secondary" onClick={reset} />
|
|
111
|
-
</
|
|
108
|
+
</Stack>
|
|
112
109
|
);
|
|
113
110
|
}`;
|
|
114
111
|
|
|
@@ -243,10 +240,12 @@ export default function IdeWorkspace() {
|
|
|
243
240
|
collapsedSize: 50,
|
|
244
241
|
});
|
|
245
242
|
|
|
243
|
+
// Capped so the terminal cannot swallow the canvas: the editor stack above
|
|
244
|
+
// has no minimum of its own, and an unbounded drag leaves nothing to grab.
|
|
246
245
|
const bottomPanel = useResizable({
|
|
247
246
|
defaultSize: 300,
|
|
248
247
|
minSizePx: 80,
|
|
249
|
-
maxSizePx:
|
|
248
|
+
maxSizePx: 520,
|
|
250
249
|
collapsible: true,
|
|
251
250
|
collapsedSize: 40,
|
|
252
251
|
});
|
|
@@ -258,6 +257,9 @@ export default function IdeWorkspace() {
|
|
|
258
257
|
height="fill"
|
|
259
258
|
content={
|
|
260
259
|
<LayoutContent padding={0}>
|
|
260
|
+
<VisuallyHidden as="h1">
|
|
261
|
+
Code editor
|
|
262
|
+
</VisuallyHidden>
|
|
261
263
|
<Layout
|
|
262
264
|
height="fill"
|
|
263
265
|
start={
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > (LH > H[g3 a=center] > (H[g2 a=center] > Hd"Night watch"[level=1] + Tx"3 investigating"[t=supporting]) + SegmentedControl"Filter by status" + B"Declare incident") + (LC[p0] > V[g0] > (H > PowerSearch"Search the night watch
|
|
3
|
+
// L > (LH > H[g3 a=center] > (H[g2 a=center] > Hd"Night watch"[level=1] + Tx"3 investigating"[t=supporting]) + SegmentedControl"Filter by status" + B"Declare incident") + (LC[p0] > V[g0] > (H > PowerSearch"Search the night watch…") + (List > (ListItem)*7)) + (LayoutPanel[w380 p0] > V[g4] > (V[g2] > (H[g2 a=center] > StatusDot + Tx"INC-2417"[t=supporting] + Token"Investigating") + Hd"Checkout API elevated 5xx rate"[level=2]) + (H[g2] > B.primary"Mark mitigated" + B.secondary"Escalate") + Divider + MetadataList + Divider + (V[g2] > Hd"Timeline"[level=3] + List))
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Incident Console — an on-call incident response tool for the night watch.
|
|
7
|
-
*
|
|
8
|
-
* Frame-first layout (see `npx astryx docs layout`), distilled from
|
|
9
|
-
* product-scale apps built with the design system:
|
|
10
|
-
*
|
|
11
|
-
* Frame: header | grouped incident rows (fill) | inspector 380 (resizable)
|
|
12
|
-
*
|
|
13
|
-
* Responsive contract:
|
|
14
|
-
* > 1024px header | rows | inspector 380
|
|
15
|
-
* <= 1024px inspector hidden; rows keep full width
|
|
16
|
-
*
|
|
17
|
-
* Container policy (tracker archetype): dense data renders as rows —
|
|
18
|
-
* edge-to-edge lists grouped by status, zero cards. Status is carried by
|
|
19
|
-
* StatusDot (severity) and Token (state), not decorative badges.
|
|
6
|
+
* Incident Console — an on-call incident response tool for the night watch. Frame-first layout (see `bunx astryx docs layout`), distilled fro (Frame/responsive/container: see XLE header above.)
|
|
20
7
|
*/
|
|
21
8
|
|
|
22
9
|
import {useMemo, useState, type CSSProperties} from 'react';
|
|
@@ -68,14 +55,23 @@ const styles: Record<string, CSSProperties> = {
|
|
|
68
55
|
padding: 'var(--spacing-2) var(--spacing-3)',
|
|
69
56
|
backgroundColor: 'var(--color-background-muted)',
|
|
70
57
|
},
|
|
58
|
+
// All FOUR edges, not block-only. With `overflowY: auto` and no `overflowX`,
|
|
59
|
+
// the inline axis computes to `auto` too, so this box clips horizontally as
|
|
60
|
+
// well as vertically. A block-only padding lands green and still leaves the
|
|
61
|
+
// inline half of the ring clipped.
|
|
71
62
|
rows: {
|
|
72
63
|
overflowY: 'auto',
|
|
73
64
|
minHeight: 0,
|
|
65
|
+
padding: 'var(--spacing-1)',
|
|
74
66
|
},
|
|
75
67
|
inspector: {
|
|
76
68
|
padding: 'var(--spacing-4)',
|
|
77
69
|
height: '100%',
|
|
78
|
-
|
|
70
|
+
},
|
|
71
|
+
controlsRow: {
|
|
72
|
+
// The four-option filter plus the action never fit a phone header; the
|
|
73
|
+
// row scrolls instead of clipping options the pointer cannot reach.
|
|
74
|
+
overflowX: 'auto',
|
|
79
75
|
},
|
|
80
76
|
};
|
|
81
77
|
|
|
@@ -271,8 +267,9 @@ const STATUS_LABEL: Record<Status, string> = {
|
|
|
271
267
|
resolved: 'Resolved',
|
|
272
268
|
};
|
|
273
269
|
|
|
274
|
-
const STATUS_TOKEN_COLOR: Record<Status, '
|
|
275
|
-
|
|
270
|
+
const STATUS_TOKEN_COLOR: Record<Status, 'cyan' | 'yellow' | 'green'> = {
|
|
271
|
+
// Info vocab: an open investigation pulses cyan, never red.
|
|
272
|
+
investigating: 'cyan',
|
|
276
273
|
mitigated: 'yellow',
|
|
277
274
|
resolved: 'green',
|
|
278
275
|
};
|
|
@@ -329,8 +326,8 @@ function IncidentRows({
|
|
|
329
326
|
return (
|
|
330
327
|
<EmptyState
|
|
331
328
|
title="Quiet in the crypt"
|
|
332
|
-
description="No incidents match
|
|
333
|
-
icon={<Icon icon={BellRing} size="lg" />}
|
|
329
|
+
description="No incidents match. Adjust the status filter or clear search filters."
|
|
330
|
+
icon={<Icon icon={BellRing} size="lg" color="secondary" />}
|
|
334
331
|
/>
|
|
335
332
|
);
|
|
336
333
|
}
|
|
@@ -340,10 +337,13 @@ function IncidentRows({
|
|
|
340
337
|
{groups.map(group => (
|
|
341
338
|
<VStack gap={0} key={group.status}>
|
|
342
339
|
<HStack gap={2} vAlign="center" style={styles.groupHeader}>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
340
|
+
{/* Primary, not secondary: --color-text-secondary #9AA1BC on
|
|
341
|
+
--color-background-muted #44475A is 3.57:1, under the 4.5:1
|
|
342
|
+
floor. The group heading is the string this sidebar is scanned
|
|
343
|
+
for. Hierarchy comes from the two type roles (principle 6),
|
|
344
|
+
not from dimming the count. */}
|
|
345
|
+
<Text type="label">{STATUS_LABEL[group.status]}</Text>
|
|
346
|
+
<Text type="supporting" hasTabularNumbers>
|
|
347
347
|
{group.items.length}
|
|
348
348
|
</Text>
|
|
349
349
|
</HStack>
|
|
@@ -354,14 +354,20 @@ function IncidentRows({
|
|
|
354
354
|
label={incident.title}
|
|
355
355
|
description={`${incident.id} · ${incident.service} · ${incident.impact}`}
|
|
356
356
|
startContent={
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
357
|
+
// StatusDot's label is aria-label on a role="img" span
|
|
358
|
+
// (core StatusDot.js:107-120), so it reaches assistive tech
|
|
359
|
+
// and paints nothing. A bare dot leaves SEV1/2/3 as hue
|
|
360
|
+
// alone. Dot and word travel together, as at :751-757.
|
|
361
|
+
<HStack gap={1} vAlign="center">
|
|
362
|
+
<StatusDot
|
|
363
|
+
variant={SEVERITY_DOT[incident.severity]}
|
|
364
|
+
label={incident.severity.toUpperCase()}
|
|
365
|
+
isPulsing={incident.status === 'investigating'}
|
|
366
|
+
/>
|
|
367
|
+
<Text type="supporting" color="secondary">
|
|
368
|
+
{incident.severity.toUpperCase()}
|
|
369
|
+
</Text>
|
|
370
|
+
</HStack>
|
|
365
371
|
}
|
|
366
372
|
endContent={
|
|
367
373
|
<HStack gap={3} vAlign="center">
|
|
@@ -399,11 +405,15 @@ function IncidentInspector({incident}: {incident: Incident}) {
|
|
|
399
405
|
return (
|
|
400
406
|
<VStack gap={4} style={styles.inspector}>
|
|
401
407
|
<VStack gap={2}>
|
|
402
|
-
<HStack gap={
|
|
408
|
+
<HStack gap={1} vAlign="center">
|
|
403
409
|
<StatusDot
|
|
404
410
|
variant={SEVERITY_DOT[incident.severity]}
|
|
405
411
|
label={incident.severity.toUpperCase()}
|
|
412
|
+
isPulsing={incident.status === 'investigating'}
|
|
406
413
|
/>
|
|
414
|
+
<Text type="supporting" color="secondary">
|
|
415
|
+
{incident.severity.toUpperCase()}
|
|
416
|
+
</Text>
|
|
407
417
|
<Text type="supporting" color="secondary">
|
|
408
418
|
{incident.id}
|
|
409
419
|
</Text>
|
|
@@ -417,8 +427,8 @@ function IncidentInspector({incident}: {incident: Incident}) {
|
|
|
417
427
|
</VStack>
|
|
418
428
|
|
|
419
429
|
<HStack gap={2}>
|
|
420
|
-
<Button label={nextAction} variant="primary"
|
|
421
|
-
<Button label="Escalate" variant="secondary"
|
|
430
|
+
<Button label={nextAction} variant="primary" />
|
|
431
|
+
<Button label="Escalate" variant="secondary" />
|
|
422
432
|
</HStack>
|
|
423
433
|
|
|
424
434
|
<Divider />
|
|
@@ -491,46 +501,65 @@ export default function IncidentConsole() {
|
|
|
491
501
|
incident => incident.status === 'investigating',
|
|
492
502
|
).length;
|
|
493
503
|
|
|
504
|
+
// Extracted so a narrow header can drop them onto their own scrollable row
|
|
505
|
+
// instead of squeezing the title, the filter and the action into one line.
|
|
506
|
+
const statusFilterControl = (
|
|
507
|
+
<SegmentedControl
|
|
508
|
+
label="Filter by status"
|
|
509
|
+
value={statusFilter}
|
|
510
|
+
onChange={setStatusFilter}
|
|
511
|
+
size="sm">
|
|
512
|
+
<SegmentedControlItem label="All" value="all" />
|
|
513
|
+
<SegmentedControlItem label="Investigating" value="investigating" />
|
|
514
|
+
<SegmentedControlItem label="Mitigated" value="mitigated" />
|
|
515
|
+
<SegmentedControlItem label="Resolved" value="resolved" />
|
|
516
|
+
</SegmentedControl>
|
|
517
|
+
);
|
|
518
|
+
const declareButton = (
|
|
519
|
+
<Button
|
|
520
|
+
label="Declare incident"
|
|
521
|
+
icon={<Icon icon={Plus} size="sm" />}
|
|
522
|
+
/>
|
|
523
|
+
);
|
|
524
|
+
const titleGroup = (
|
|
525
|
+
<HStack gap={2} vAlign="center">
|
|
526
|
+
<Heading level={1} type="display-2">Night watch</Heading>
|
|
527
|
+
<Text type="supporting" color="secondary" hasTabularNumbers>
|
|
528
|
+
{openCount} investigating
|
|
529
|
+
</Text>
|
|
530
|
+
</HStack>
|
|
531
|
+
);
|
|
532
|
+
|
|
494
533
|
return (
|
|
495
534
|
<Layout
|
|
496
535
|
height="fill"
|
|
497
536
|
header={
|
|
498
|
-
<LayoutHeader hasDivider>
|
|
499
|
-
|
|
500
|
-
<
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
537
|
+
<LayoutHeader hasDivider padding={6}>
|
|
538
|
+
{isNarrow ? (
|
|
539
|
+
<VStack gap={2}>
|
|
540
|
+
{titleGroup}
|
|
541
|
+
<HStack
|
|
542
|
+
gap={2}
|
|
543
|
+
vAlign="center"
|
|
544
|
+
style={styles.controlsRow}
|
|
545
|
+
tabIndex={0}
|
|
546
|
+
role="region"
|
|
547
|
+
aria-label="Incident filter actions">
|
|
548
|
+
{statusFilterControl}
|
|
549
|
+
{declareButton}
|
|
509
550
|
</HStack>
|
|
510
|
-
</
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
label="Investigating"
|
|
519
|
-
value="investigating"
|
|
520
|
-
/>
|
|
521
|
-
<SegmentedControlItem label="Mitigated" value="mitigated" />
|
|
522
|
-
<SegmentedControlItem label="Resolved" value="resolved" />
|
|
523
|
-
</SegmentedControl>
|
|
524
|
-
<Button
|
|
525
|
-
label="Declare incident"
|
|
526
|
-
icon={<Icon icon={Plus} size="sm" />}
|
|
527
|
-
size="sm"
|
|
528
|
-
/>
|
|
529
|
-
</HStack>
|
|
551
|
+
</VStack>
|
|
552
|
+
) : (
|
|
553
|
+
<HStack gap={3} vAlign="center">
|
|
554
|
+
<StackItem size="fill">{titleGroup}</StackItem>
|
|
555
|
+
{statusFilterControl}
|
|
556
|
+
{declareButton}
|
|
557
|
+
</HStack>
|
|
558
|
+
)}
|
|
530
559
|
</LayoutHeader>
|
|
531
560
|
}
|
|
532
561
|
content={
|
|
533
|
-
<LayoutContent padding={0}>
|
|
562
|
+
<LayoutContent padding={0} isScrollable={false}>
|
|
534
563
|
<VStack gap={0} style={styles.contentFill}>
|
|
535
564
|
<HStack style={styles.searchRow}>
|
|
536
565
|
<StackItem size="fill">
|
|
@@ -538,12 +567,17 @@ export default function IncidentConsole() {
|
|
|
538
567
|
config={config}
|
|
539
568
|
filters={filters}
|
|
540
569
|
onChange={newFilters => setFilters([...newFilters])}
|
|
541
|
-
placeholder="Search the night watch
|
|
570
|
+
placeholder="Search the night watch…"
|
|
542
571
|
resultCount={visible.length}
|
|
543
572
|
/>
|
|
544
573
|
</StackItem>
|
|
545
574
|
</HStack>
|
|
546
|
-
<StackItem
|
|
575
|
+
<StackItem
|
|
576
|
+
size="fill"
|
|
577
|
+
style={styles.rows}
|
|
578
|
+
tabIndex={0}
|
|
579
|
+
role="region"
|
|
580
|
+
aria-label="Incident rows">
|
|
547
581
|
<IncidentRows
|
|
548
582
|
incidents={visible}
|
|
549
583
|
selectedId={selected?.id ?? null}
|
|
@@ -573,7 +607,7 @@ export default function IncidentConsole() {
|
|
|
573
607
|
<EmptyState
|
|
574
608
|
title="No incident under the lens"
|
|
575
609
|
description="Select an incident from the rows to inspect its timeline."
|
|
576
|
-
icon={<Icon icon={BellRing} size="lg" />}
|
|
610
|
+
icon={<Icon icon={BellRing} size="lg" color="secondary" />}
|
|
577
611
|
isCompact
|
|
578
612
|
/>
|
|
579
613
|
)}
|