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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
//
|
|
3
|
+
// AR*10 is the live count: GALLERY_SCENES has 10 entries and a bare .map renders
|
|
4
|
+
// all 10, with no slice. The header said 9 while 10 rendered.
|
|
5
|
+
// L > LC[p=6] > G[c={min:280} g8 a=center] > (V[g6] > (V[g3] > Tx"AFTER DARK"[t=supporting] + Hd"Make every night"[level=1] + Tx"The smallest rituals"[t=body]) + B.primary"Explore the night" + (V[g4] > D + (H[g6] > (V > Tx"12k+"[t=display-3] + Tx"Night shots"[t=supporting])*3))) + (G[c3 g3] > AR*10)
|
|
4
6
|
|
|
5
7
|
import type {CSSProperties} from 'react';
|
|
6
8
|
import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
@@ -9,103 +11,33 @@ import {Button} from '@astryxdesign/core/Button';
|
|
|
9
11
|
import {AspectRatio} from '@astryxdesign/core/AspectRatio';
|
|
10
12
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
11
13
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// replace these with component props once it lands.
|
|
17
|
-
// Fills the AspectRatio box. No objectFit prop on AspectRatio (#2582).
|
|
18
|
-
const galleryImage: CSSProperties = {
|
|
19
|
-
width: '100%',
|
|
20
|
-
height: '100%',
|
|
21
|
-
display: 'block',
|
|
22
|
-
};
|
|
23
|
-
// Rounds the gallery corners. No radius prop on AspectRatio (#2582);
|
|
24
|
-
// overflow clip masks the SVG scene to the rounded corners.
|
|
25
|
-
const galleryImageClip: CSSProperties = {
|
|
26
|
-
borderRadius: 'var(--radius-container)',
|
|
27
|
-
overflow: 'clip',
|
|
28
|
-
};
|
|
14
|
+
import {galleryImageClip} from 'astryx-dracula/shared/gallery-image';
|
|
15
|
+
import {SceneTile} from 'astryx-dracula/shared/scene-tile';
|
|
16
|
+
import {SCENE_TILE_ALTS} from 'astryx-dracula/shared/scene-hues';
|
|
17
|
+
import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
|
|
29
18
|
|
|
30
19
|
// ─── Gallery Data ─────────────────────────────────────────────────────────────
|
|
31
20
|
|
|
32
|
-
// One Dracula accent per tile from the fixed categorical vocabulary
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{alt:
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
{alt:
|
|
41
|
-
{alt:
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
hue: 'var(--dracula-orange)',
|
|
45
|
-
},
|
|
46
|
-
{alt: 'Castle silhouette over sleeping hills', hue: 'var(--dracula-cyan)'},
|
|
47
|
-
{alt: 'Attic window glowing amber at 2am', hue: 'var(--dracula-yellow)'},
|
|
48
|
-
{alt: 'Cellar shelves lined with bottled dusk', hue: 'var(--dracula-green)'},
|
|
21
|
+
// One Dracula accent per tile from the fixed categorical vocabulary; alts are
|
|
22
|
+
// shared with classic-gallery via SCENE_TILE_ALTS.
|
|
23
|
+
const GALLERY_SCENES: Array<{alt: string; hue: SceneHue}> = [
|
|
24
|
+
{alt: SCENE_TILE_ALTS[0], hue: 'var(--dracula-cyan)'},
|
|
25
|
+
{alt: SCENE_TILE_ALTS[1], hue: 'var(--dracula-pink)'},
|
|
26
|
+
{alt: SCENE_TILE_ALTS[2], hue: 'var(--dracula-yellow)'},
|
|
27
|
+
{alt: SCENE_TILE_ALTS[3], hue: 'var(--dracula-green)'},
|
|
28
|
+
{alt: SCENE_TILE_ALTS[4], hue: 'var(--dracula-pink)'},
|
|
29
|
+
{alt: SCENE_TILE_ALTS[5], hue: 'var(--dracula-orange)'},
|
|
30
|
+
{alt: SCENE_TILE_ALTS[6], hue: 'var(--dracula-cyan)'},
|
|
31
|
+
{alt: SCENE_TILE_ALTS[7], hue: 'var(--dracula-yellow)'},
|
|
32
|
+
{alt: SCENE_TILE_ALTS[8], hue: 'var(--dracula-green)'},
|
|
49
33
|
];
|
|
50
34
|
|
|
51
|
-
// ─── Gallery Art ────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
function NightScene({alt, hue, index}: {alt: string; hue: string; index: number}) {
|
|
54
|
-
const moonX = 90 + ((index * 53) % 220);
|
|
55
|
-
const moonY = 62 + ((index * 29) % 60);
|
|
56
|
-
const hillA = 190 + ((index * 13) % 40);
|
|
57
|
-
const hillB = 215 + ((index * 17) % 40);
|
|
58
|
-
return (
|
|
59
|
-
<svg
|
|
60
|
-
viewBox="0 0 400 300"
|
|
61
|
-
preserveAspectRatio="xMidYMid slice"
|
|
62
|
-
style={galleryImage}
|
|
63
|
-
role="img"
|
|
64
|
-
aria-label={alt}>
|
|
65
|
-
<rect width="400" height="300" fill="var(--dracula-bg-light)" />
|
|
66
|
-
<g fill="var(--dracula-purple)" opacity={0.55}>
|
|
67
|
-
<circle cx={40 + ((index * 37) % 320)} cy={30} r={2} />
|
|
68
|
-
<circle cx={120 + ((index * 23) % 200)} cy={52} r={1.6} />
|
|
69
|
-
<circle cx={260 + ((index * 11) % 110)} cy={26} r={2.2} />
|
|
70
|
-
<circle cx={330} cy={70 + ((index * 7) % 30)} r={1.6} />
|
|
71
|
-
</g>
|
|
72
|
-
<circle cx={moonX} cy={moonY} r={34} fill={hue} opacity={0.9} />
|
|
73
|
-
<circle
|
|
74
|
-
cx={moonX - 12}
|
|
75
|
-
cy={moonY - 8}
|
|
76
|
-
r={28}
|
|
77
|
-
fill="var(--dracula-bg-light)"
|
|
78
|
-
opacity={0.55}
|
|
79
|
-
/>
|
|
80
|
-
<path
|
|
81
|
-
d={`M0 ${hillA} Q100 ${hillA - 50} 200 ${hillA - 10} T400 ${hillA - 30} V300 H0 Z`}
|
|
82
|
-
fill="var(--dracula-current-line)"
|
|
83
|
-
/>
|
|
84
|
-
<path
|
|
85
|
-
d={`M0 ${hillB} Q120 ${hillB - 40} 240 ${hillB} T400 ${hillB - 20} V300 H0 Z`}
|
|
86
|
-
fill="var(--dracula-bg)"
|
|
87
|
-
/>
|
|
88
|
-
<g
|
|
89
|
-
transform={`translate(${60 + ((index * 41) % 280)} ${hillB - 34})`}
|
|
90
|
-
fill="none"
|
|
91
|
-
stroke={hue}
|
|
92
|
-
strokeWidth="4"
|
|
93
|
-
strokeLinecap="round"
|
|
94
|
-
strokeLinejoin="round">
|
|
95
|
-
<rect x="-22" y="-22" width="44" height="44" rx={4} />
|
|
96
|
-
<circle cx="9" cy="-9" r="2" fill={hue} stroke="none" />
|
|
97
|
-
<path d="M-17 15 L-4 0 L5 9 L10 4 L17 12" />
|
|
98
|
-
</g>
|
|
99
|
-
</svg>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
35
|
// ─── Stat Block ─────────────────────────────────────────────────────────────
|
|
104
36
|
|
|
105
37
|
function StatBlock({value, label}: {value: string; label: string}) {
|
|
106
38
|
return (
|
|
107
39
|
<VStack gap={0}>
|
|
108
|
-
<Text type="
|
|
40
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
109
41
|
{value}
|
|
110
42
|
</Text>
|
|
111
43
|
<Text type="supporting" color="secondary">
|
|
@@ -122,7 +54,12 @@ function ImageGrid() {
|
|
|
122
54
|
<Grid columns={3} gap={3}>
|
|
123
55
|
{GALLERY_SCENES.map((scene, index) => (
|
|
124
56
|
<AspectRatio key={scene.alt} ratio={1} style={galleryImageClip}>
|
|
125
|
-
<
|
|
57
|
+
<SceneTile
|
|
58
|
+
label={scene.alt}
|
|
59
|
+
hue={scene.hue}
|
|
60
|
+
size="sm"
|
|
61
|
+
index={index}
|
|
62
|
+
/>
|
|
126
63
|
</AspectRatio>
|
|
127
64
|
))}
|
|
128
65
|
</Grid>
|
|
@@ -131,18 +68,27 @@ function ImageGrid() {
|
|
|
131
68
|
|
|
132
69
|
// ─── Main Page ──────────────────────────────────────────────────────────────
|
|
133
70
|
|
|
71
|
+
// A definite ancestor for `Layout height="fill"`. 100dvh, not minHeight: '100%':
|
|
72
|
+
// a percentage min-height against an indefinite containing block computes to 0
|
|
73
|
+
// (CSS 2.1 10.5), so the soft failure would survive and the document would keep
|
|
74
|
+
// the scroll. LayoutContent is overflow:auto, so the definite height lands as a
|
|
75
|
+
// content-pane scroller rather than a clip. Same shape as dashboard.tsx and
|
|
76
|
+
// editor.tsx:302.
|
|
77
|
+
const pageStyle: CSSProperties = {height: '100dvh'};
|
|
78
|
+
|
|
134
79
|
export default function SideGallery() {
|
|
135
80
|
return (
|
|
136
81
|
<Layout
|
|
82
|
+
style={pageStyle}
|
|
137
83
|
height="fill"
|
|
138
84
|
contentWidth={1400}
|
|
139
85
|
content={
|
|
140
86
|
<LayoutContent padding={6}>
|
|
141
|
-
<Grid columns={{minWidth:
|
|
87
|
+
<Grid columns={{minWidth: 280, repeat: 'fit'}} gap={8} align="center">
|
|
142
88
|
{/* Left side: Text + CTA */}
|
|
143
89
|
<VStack gap={6} vAlign="center">
|
|
144
90
|
<VStack gap={3}>
|
|
145
|
-
<Text type="supporting" color="secondary"
|
|
91
|
+
<Text type="supporting" color="secondary">
|
|
146
92
|
AFTER DARK
|
|
147
93
|
</Text>
|
|
148
94
|
<Heading level={1}>
|
|
@@ -157,9 +103,7 @@ export default function SideGallery() {
|
|
|
157
103
|
</Text>
|
|
158
104
|
</VStack>
|
|
159
105
|
|
|
160
|
-
<
|
|
161
|
-
<Button label="Explore the night" variant="primary" />
|
|
162
|
-
</HStack>
|
|
106
|
+
<Button label="Explore the night" variant="primary" />
|
|
163
107
|
|
|
164
108
|
<VStack gap={4}>
|
|
165
109
|
<Divider />
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > (LH[divider] > V[g=4] > (H[j=between a=center] > Hd"Night-shift issues"[level=
|
|
3
|
+
// L > (LH[divider] > V[g=4] > (H[j=between a=center] > Hd"Night-shift issues"[level=1] + B.primary"Raise issue"[opens=#raise-issue]) + (H[g=2 a=center] > PS + Po > B.secondary"View options")) + (LC[p=0] > T[hover] > (TR > THC*7) + (TR > TC*7)*4) + (LP > V[g=4] > (H > Tx[t=supporting] + IB"Close panel") + (V[g=1] > Hd[level=2] + Tx[t=body]) + ML + (D + (V[g=2] > Tx"Labels"[t=label] + (H[g=2] > Tk*2)))) ;; Dlg#raise-issue > (DH"Raise issue" + (LC[p=4] > V[g=4] > TI*4) + (LF > H[j=end g=2] > B.secondary"Cancel" + B.primary"Raise issue"))
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Table Grouped — the night-shift issue tracker: a grouped, collapsible issue table with a PowerSearch bar and a resizable detail inspector. (Frame/responsive/container: see XLE header above.)
|
|
7
|
+
*/
|
|
4
8
|
|
|
5
9
|
import React, {useState, useMemo} from 'react';
|
|
6
10
|
import {useResizable, ResizeHandle} from '@astryxdesign/core/Resizable';
|
|
@@ -27,11 +31,12 @@ import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
|
|
|
27
31
|
import {Popover} from '@astryxdesign/core/Popover';
|
|
28
32
|
import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
|
|
29
33
|
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
30
|
-
import {Center} from '@astryxdesign/core/Center';
|
|
31
34
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
32
35
|
import {StatusDot} from '@astryxdesign/core/StatusDot';
|
|
33
36
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
34
37
|
import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
|
|
38
|
+
import {Token} from '@astryxdesign/core/Token';
|
|
39
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
35
40
|
import {
|
|
36
41
|
Table,
|
|
37
42
|
TableRow,
|
|
@@ -87,9 +92,9 @@ interface TaskRow extends Record<string, unknown> {
|
|
|
87
92
|
|
|
88
93
|
const STATUS_DOT_VARIANT: Record<
|
|
89
94
|
TaskStatus,
|
|
90
|
-
'success' | '
|
|
95
|
+
'success' | 'neutral' | 'warning' | 'info'
|
|
91
96
|
> = {
|
|
92
|
-
in_progress: '
|
|
97
|
+
in_progress: 'info',
|
|
93
98
|
todo: 'warning',
|
|
94
99
|
backlog: 'neutral',
|
|
95
100
|
done: 'success',
|
|
@@ -97,10 +102,10 @@ const STATUS_DOT_VARIANT: Record<
|
|
|
97
102
|
|
|
98
103
|
const PRIORITY_COLOR: Record<
|
|
99
104
|
TaskPriority,
|
|
100
|
-
'
|
|
105
|
+
'error' | 'warning' | 'secondary' | 'disabled'
|
|
101
106
|
> = {
|
|
102
|
-
urgent: '
|
|
103
|
-
high: '
|
|
107
|
+
urgent: 'error',
|
|
108
|
+
high: 'warning',
|
|
104
109
|
medium: 'secondary',
|
|
105
110
|
low: 'disabled',
|
|
106
111
|
none: 'disabled',
|
|
@@ -126,7 +131,7 @@ const allTasks: TaskRow[] = [
|
|
|
126
131
|
{
|
|
127
132
|
id: '2',
|
|
128
133
|
taskId: 'T235040470',
|
|
129
|
-
title: '
|
|
134
|
+
title: 'Chart the crypt to organize haunts for rituals or releases',
|
|
130
135
|
subtitle: '',
|
|
131
136
|
status: 'in_progress',
|
|
132
137
|
priority: 'medium',
|
|
@@ -141,7 +146,7 @@ const allTasks: TaskRow[] = [
|
|
|
141
146
|
{
|
|
142
147
|
id: '3',
|
|
143
148
|
taskId: 'T235040471',
|
|
144
|
-
title: '
|
|
149
|
+
title: 'Follow moon cycles to focus haunts over n-weeks',
|
|
145
150
|
subtitle: '',
|
|
146
151
|
status: 'in_progress',
|
|
147
152
|
priority: 'medium',
|
|
@@ -156,7 +161,7 @@ const allTasks: TaskRow[] = [
|
|
|
156
161
|
{
|
|
157
162
|
id: '4',
|
|
158
163
|
taskId: 'T235040472',
|
|
159
|
-
title: '
|
|
164
|
+
title: 'Test the gate wards before moonrise',
|
|
160
165
|
subtitle: 'Update castle gate integration',
|
|
161
166
|
status: 'todo',
|
|
162
167
|
priority: 'medium',
|
|
@@ -231,7 +236,7 @@ const allTasks: TaskRow[] = [
|
|
|
231
236
|
{
|
|
232
237
|
id: '9',
|
|
233
238
|
taskId: 'T235040477',
|
|
234
|
-
title: 'Invite your
|
|
239
|
+
title: 'Invite your fellow familiars to the crypt',
|
|
235
240
|
subtitle: '',
|
|
236
241
|
status: 'todo',
|
|
237
242
|
priority: 'low',
|
|
@@ -246,7 +251,7 @@ const allTasks: TaskRow[] = [
|
|
|
246
251
|
{
|
|
247
252
|
id: '10',
|
|
248
253
|
taskId: 'T235040478',
|
|
249
|
-
title: 'Next
|
|
254
|
+
title: 'Next rites after moonrise',
|
|
250
255
|
subtitle: '',
|
|
251
256
|
status: 'todo',
|
|
252
257
|
priority: 'none',
|
|
@@ -456,7 +461,7 @@ const allTasks: TaskRow[] = [
|
|
|
456
461
|
{
|
|
457
462
|
id: '24',
|
|
458
463
|
taskId: 'T235040492',
|
|
459
|
-
title: '
|
|
464
|
+
title: 'Audit dashboard contrast against the Dracula ramp',
|
|
460
465
|
subtitle: '',
|
|
461
466
|
status: 'backlog',
|
|
462
467
|
priority: 'low',
|
|
@@ -588,7 +593,7 @@ function groupTasks(
|
|
|
588
593
|
}
|
|
589
594
|
const map = new Map<string, TaskRow[]>();
|
|
590
595
|
for (const task of tasks) {
|
|
591
|
-
const key = String(task[groupBy]) || '
|
|
596
|
+
const key = String(task[groupBy]) || 'None';
|
|
592
597
|
let group = map.get(key);
|
|
593
598
|
if (!group) {
|
|
594
599
|
group = [];
|
|
@@ -630,7 +635,7 @@ const columns: TableColumn<TaskRow>[] = [
|
|
|
630
635
|
{
|
|
631
636
|
key: 'project',
|
|
632
637
|
header: 'Project',
|
|
633
|
-
width:
|
|
638
|
+
width: proportional(1),
|
|
634
639
|
},
|
|
635
640
|
{
|
|
636
641
|
key: 'created',
|
|
@@ -731,7 +736,7 @@ function TaskDetailPanel({
|
|
|
731
736
|
</HStack>
|
|
732
737
|
|
|
733
738
|
<VStack gap={1}>
|
|
734
|
-
<Heading level={
|
|
739
|
+
<Heading level={2}>{task.title}</Heading>
|
|
735
740
|
{task.subtitle && (
|
|
736
741
|
<Text type="body" color="secondary">
|
|
737
742
|
{task.subtitle}
|
|
@@ -741,10 +746,11 @@ function TaskDetailPanel({
|
|
|
741
746
|
|
|
742
747
|
<MetadataList label={{position: 'start'}}>
|
|
743
748
|
<MetadataListItem label="Status">
|
|
744
|
-
<HStack gap={
|
|
749
|
+
<HStack gap={1} vAlign="center">
|
|
745
750
|
<StatusDot
|
|
746
751
|
variant={STATUS_DOT_VARIANT[task.status]}
|
|
747
752
|
label={STATUS_LABEL[task.status]}
|
|
753
|
+
isPulsing={task.status === 'in_progress'}
|
|
748
754
|
/>
|
|
749
755
|
<Text type="body">{STATUS_LABEL[task.status]}</Text>
|
|
750
756
|
</HStack>
|
|
@@ -770,7 +776,7 @@ function TaskDetailPanel({
|
|
|
770
776
|
<Text type="body">{task.project}</Text>
|
|
771
777
|
) : (
|
|
772
778
|
<Text type="supporting" color="secondary">
|
|
773
|
-
|
|
779
|
+
None
|
|
774
780
|
</Text>
|
|
775
781
|
)}
|
|
776
782
|
</MetadataListItem>
|
|
@@ -789,7 +795,7 @@ function TaskDetailPanel({
|
|
|
789
795
|
<Text type="label">Labels</Text>
|
|
790
796
|
<HStack gap={2}>
|
|
791
797
|
{task.tags.map(tag => (
|
|
792
|
-
<
|
|
798
|
+
<Token key={tag} color="yellow" label={tag} />
|
|
793
799
|
))}
|
|
794
800
|
</HStack>
|
|
795
801
|
</VStack>
|
|
@@ -815,6 +821,10 @@ export default function TableGrouped() {
|
|
|
815
821
|
() => new Set(GROUP_ORDER as string[]),
|
|
816
822
|
);
|
|
817
823
|
|
|
824
|
+
// Responsive contract (see file header): below 1024px the inspector would
|
|
825
|
+
// squeeze the grouped table, so it steps aside entirely.
|
|
826
|
+
const isNarrow = useMediaQuery('(max-width: 1024px)');
|
|
827
|
+
|
|
818
828
|
const filtered = useMemo(() => {
|
|
819
829
|
return applyFilters(powerSearchFilters, allTasks);
|
|
820
830
|
}, [powerSearchFilters, applyFilters]);
|
|
@@ -824,11 +834,13 @@ export default function TableGrouped() {
|
|
|
824
834
|
[filtered, groupBy],
|
|
825
835
|
);
|
|
826
836
|
|
|
827
|
-
const groupKeys =
|
|
837
|
+
const groupKeys = Array.from(grouped.keys());
|
|
828
838
|
|
|
839
|
+
// Expand every group whenever the grouping itself changes. Keyed on the
|
|
840
|
+
// memoized Map, not on groupKeys, which is a fresh array each render.
|
|
829
841
|
React.useEffect(() => {
|
|
830
|
-
setExpandedGroups(new Set(
|
|
831
|
-
}, [
|
|
842
|
+
setExpandedGroups(new Set(grouped.keys()));
|
|
843
|
+
}, [grouped]);
|
|
832
844
|
|
|
833
845
|
const toggleGroup = (key: string) => {
|
|
834
846
|
setExpandedGroups(prev => {
|
|
@@ -856,16 +868,21 @@ export default function TableGrouped() {
|
|
|
856
868
|
<Layout
|
|
857
869
|
height="fill"
|
|
858
870
|
header={
|
|
859
|
-
<LayoutHeader hasDivider padding={
|
|
871
|
+
<LayoutHeader hasDivider padding={6}>
|
|
860
872
|
<VStack gap={4}>
|
|
861
873
|
<HStack gap={3} vAlign="center">
|
|
862
874
|
<StackItem size="fill">
|
|
863
|
-
|
|
875
|
+
{/* maxLines={1}: this is the worst case in the set — 18
|
|
876
|
+
characters at 35px is 378px of a 342px header column, so
|
|
877
|
+
it wraps to THREE lines and the header measures 180px
|
|
878
|
+
(21.3% of a 390x844 viewport). Core wires maxLines to
|
|
879
|
+
useTruncation, so the full title stays reachable in a
|
|
880
|
+
Tooltip (SC 1.4.10). */}
|
|
881
|
+
<Heading level={1} type="display-2" maxLines={1}>Night-shift issues</Heading>
|
|
864
882
|
</StackItem>
|
|
865
883
|
<Button
|
|
866
884
|
label="Raise issue"
|
|
867
885
|
variant="primary"
|
|
868
|
-
size="lg"
|
|
869
886
|
onClick={() => setDialogOpen(true)}
|
|
870
887
|
/>
|
|
871
888
|
</HStack>
|
|
@@ -875,14 +892,14 @@ export default function TableGrouped() {
|
|
|
875
892
|
config={powerSearchConfig}
|
|
876
893
|
filters={powerSearchFilters}
|
|
877
894
|
onChange={newFilters => setPowerSearchFilters(newFilters)}
|
|
878
|
-
placeholder="Filter issues
|
|
895
|
+
placeholder="Filter issues…"
|
|
879
896
|
resultCount={`${filtered.length} issue${filtered.length !== 1 ? 's' : ''}`}
|
|
880
897
|
/>
|
|
881
898
|
</StackItem>
|
|
882
899
|
<Popover
|
|
883
900
|
placement="below"
|
|
884
901
|
alignment="end"
|
|
885
|
-
width=
|
|
902
|
+
width="min(20rem, calc(100vw - 2 * var(--space-viewport)))"
|
|
886
903
|
label="Grouping options"
|
|
887
904
|
content={
|
|
888
905
|
<VStack gap={4}>
|
|
@@ -900,7 +917,7 @@ export default function TableGrouped() {
|
|
|
900
917
|
</RadioList>
|
|
901
918
|
</VStack>
|
|
902
919
|
}>
|
|
903
|
-
<Button label="View
|
|
920
|
+
<Button label="View options" variant="secondary" size="md" />
|
|
904
921
|
</Popover>
|
|
905
922
|
</HStack>
|
|
906
923
|
</VStack>
|
|
@@ -951,7 +968,7 @@ export default function TableGrouped() {
|
|
|
951
968
|
size="sm"
|
|
952
969
|
color="secondary"
|
|
953
970
|
/>
|
|
954
|
-
<Text type="body" weight="
|
|
971
|
+
<Text type="body" weight="semibold">
|
|
955
972
|
{getGroupLabel(groupBy, key)}
|
|
956
973
|
</Text>
|
|
957
974
|
<Badge
|
|
@@ -968,12 +985,21 @@ export default function TableGrouped() {
|
|
|
968
985
|
key={task.id}
|
|
969
986
|
onClick={() => setSelectedTask(task)}>
|
|
970
987
|
<TableCell>
|
|
971
|
-
|
|
988
|
+
{/* StatusDot paints nothing but an 8px dot — its
|
|
989
|
+
label is aria-label on a role="img" span — so
|
|
990
|
+
four states here read as hue alone. Same
|
|
991
|
+
dot-plus-word pairing as the summary row at
|
|
992
|
+
:751-757. */}
|
|
993
|
+
<HStack gap={1} vAlign="center">
|
|
972
994
|
<StatusDot
|
|
973
995
|
variant={STATUS_DOT_VARIANT[task.status]}
|
|
974
996
|
label={STATUS_LABEL[task.status]}
|
|
997
|
+
isPulsing={task.status === 'in_progress'}
|
|
975
998
|
/>
|
|
976
|
-
|
|
999
|
+
<Text type="supporting" color="secondary">
|
|
1000
|
+
{STATUS_LABEL[task.status]}
|
|
1001
|
+
</Text>
|
|
1002
|
+
</HStack>
|
|
977
1003
|
</TableCell>
|
|
978
1004
|
<TableCell>
|
|
979
1005
|
<HStack gap={3} vAlign="center" wrap="wrap">
|
|
@@ -1005,7 +1031,7 @@ export default function TableGrouped() {
|
|
|
1005
1031
|
</Text>
|
|
1006
1032
|
) : (
|
|
1007
1033
|
<Text type="supporting" color="secondary">
|
|
1008
|
-
|
|
1034
|
+
None
|
|
1009
1035
|
</Text>
|
|
1010
1036
|
)}
|
|
1011
1037
|
</TableCell>
|
|
@@ -1087,6 +1113,7 @@ export default function TableGrouped() {
|
|
|
1087
1113
|
</LayoutContent>
|
|
1088
1114
|
}
|
|
1089
1115
|
end={
|
|
1116
|
+
!isNarrow &&
|
|
1090
1117
|
selectedTask && (
|
|
1091
1118
|
<>
|
|
1092
1119
|
<ResizeHandle
|
|
@@ -1105,9 +1132,10 @@ export default function TableGrouped() {
|
|
|
1105
1132
|
/>
|
|
1106
1133
|
<Dialog isOpen={dialogOpen} onOpenChange={open => setDialogOpen(open)}>
|
|
1107
1134
|
<Layout
|
|
1135
|
+
height="auto"
|
|
1108
1136
|
header={
|
|
1109
1137
|
<DialogHeader
|
|
1110
|
-
title="Raise
|
|
1138
|
+
title="Raise issue"
|
|
1111
1139
|
onOpenChange={open => setDialogOpen(open)}
|
|
1112
1140
|
/>
|
|
1113
1141
|
}
|
|
@@ -1161,7 +1189,7 @@ export default function TableGrouped() {
|
|
|
1161
1189
|
onClick={() => setDialogOpen(false)}
|
|
1162
1190
|
/>
|
|
1163
1191
|
<Button
|
|
1164
|
-
label="Raise"
|
|
1192
|
+
label="Raise issue"
|
|
1165
1193
|
variant="primary"
|
|
1166
1194
|
size="md"
|
|
1167
1195
|
onClick={() => setDialogOpen(false)}
|