astryx-dracula 0.2.0 → 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 +22 -11
- 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 +67 -32
- package/templates/ai-chat.tsx +41 -20
- package/templates/blank.tsx +5 -2
- package/templates/centered-hero.tsx +15 -124
- package/templates/classic-gallery.tsx +21 -65
- package/templates/contact-form.tsx +62 -40
- package/templates/dashboard-portfolio.tsx +146 -227
- package/templates/dashboard.tsx +123 -121
- package/templates/detail-page.tsx +101 -95
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +85 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +33 -21
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +54 -74
- package/templates/editor.tsx +119 -89
- package/templates/file-explorer.tsx +133 -84
- package/templates/form-two-column.tsx +62 -124
- package/templates/gallery-hero.tsx +17 -171
- package/templates/ide.tsx +27 -21
- package/templates/incident-console.tsx +105 -69
- package/templates/kanban-board.tsx +100 -41
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +38 -41
- package/templates/login-card.tsx +59 -86
- package/templates/login-split.tsx +88 -128
- package/templates/login-sso.tsx +107 -68
- package/templates/login.tsx +108 -52
- package/templates/messaging-shell.tsx +172 -143
- package/templates/mixed-gallery.tsx +31 -130
- package/templates/payment-form.tsx +369 -289
- package/templates/product-detail.tsx +40 -57
- package/templates/product-gallery.tsx +31 -40
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +145 -286
- package/templates/settings-sidebar.tsx +179 -267
- package/templates/settings.tsx +42 -13
- package/templates/shell-nav.tsx +32 -23
- package/templates/shell-side-nav.tsx +30 -19
- package/templates/shell-top-nav.tsx +81 -43
- package/templates/side-gallery.tsx +39 -93
- package/templates/table-grouped.tsx +63 -33
- package/templates/table-page-chart.tsx +24 -297
- package/templates/table-page-heatmap-status.tsx +157 -91
- package/templates/table-page-shoe-store-heatmap.tsx +19 -288
- package/templates/table-page.tsx +24 -6
- package/templates/table.tsx +24 -7
- 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 +313 -249
- package/theme.css +76 -14
- package/tokens.css +425 -30
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
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)
|
|
2
6
|
|
|
3
7
|
import type {CSSProperties} from 'react';
|
|
4
8
|
import {VStack, HStack, Layout, LayoutContent} from '@astryxdesign/core/Layout';
|
|
@@ -7,103 +11,33 @@ import {Button} from '@astryxdesign/core/Button';
|
|
|
7
11
|
import {AspectRatio} from '@astryxdesign/core/AspectRatio';
|
|
8
12
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
9
13
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// replace these with component props once it lands.
|
|
15
|
-
// Fills the AspectRatio box. No objectFit prop on AspectRatio (#2582).
|
|
16
|
-
const galleryImage: CSSProperties = {
|
|
17
|
-
width: '100%',
|
|
18
|
-
height: '100%',
|
|
19
|
-
display: 'block',
|
|
20
|
-
};
|
|
21
|
-
// Rounds the gallery corners. No radius prop on AspectRatio (#2582);
|
|
22
|
-
// overflow clip masks the SVG scene to the rounded corners.
|
|
23
|
-
const galleryImageClip: CSSProperties = {
|
|
24
|
-
borderRadius: 'var(--radius-container)',
|
|
25
|
-
overflow: 'clip',
|
|
26
|
-
};
|
|
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';
|
|
27
18
|
|
|
28
19
|
// ─── Gallery Data ─────────────────────────────────────────────────────────────
|
|
29
20
|
|
|
30
|
-
// One Dracula accent per tile from the fixed categorical vocabulary
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{alt:
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
{alt:
|
|
39
|
-
{alt:
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
hue: 'var(--dracula-orange)',
|
|
43
|
-
},
|
|
44
|
-
{alt: 'Castle silhouette over sleeping hills', hue: 'var(--dracula-cyan)'},
|
|
45
|
-
{alt: 'Attic window glowing amber at 2am', hue: 'var(--dracula-yellow)'},
|
|
46
|
-
{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)'},
|
|
47
33
|
];
|
|
48
34
|
|
|
49
|
-
// ─── Gallery Art ────────────────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
function NightScene({alt, hue, index}: {alt: string; hue: string; index: number}) {
|
|
52
|
-
const moonX = 90 + ((index * 53) % 220);
|
|
53
|
-
const moonY = 62 + ((index * 29) % 60);
|
|
54
|
-
const hillA = 190 + ((index * 13) % 40);
|
|
55
|
-
const hillB = 215 + ((index * 17) % 40);
|
|
56
|
-
return (
|
|
57
|
-
<svg
|
|
58
|
-
viewBox="0 0 400 300"
|
|
59
|
-
preserveAspectRatio="xMidYMid slice"
|
|
60
|
-
style={galleryImage}
|
|
61
|
-
role="img"
|
|
62
|
-
aria-label={alt}>
|
|
63
|
-
<rect width="400" height="300" fill="var(--dracula-bg-light)" />
|
|
64
|
-
<g fill="var(--dracula-purple)" opacity={0.55}>
|
|
65
|
-
<circle cx={40 + ((index * 37) % 320)} cy={30} r={2} />
|
|
66
|
-
<circle cx={120 + ((index * 23) % 200)} cy={52} r={1.6} />
|
|
67
|
-
<circle cx={260 + ((index * 11) % 110)} cy={26} r={2.2} />
|
|
68
|
-
<circle cx={330} cy={70 + ((index * 7) % 30)} r={1.6} />
|
|
69
|
-
</g>
|
|
70
|
-
<circle cx={moonX} cy={moonY} r={34} fill={hue} opacity={0.9} />
|
|
71
|
-
<circle
|
|
72
|
-
cx={moonX - 12}
|
|
73
|
-
cy={moonY - 8}
|
|
74
|
-
r={28}
|
|
75
|
-
fill="var(--dracula-bg-light)"
|
|
76
|
-
opacity={0.55}
|
|
77
|
-
/>
|
|
78
|
-
<path
|
|
79
|
-
d={`M0 ${hillA} Q100 ${hillA - 50} 200 ${hillA - 10} T400 ${hillA - 30} V300 H0 Z`}
|
|
80
|
-
fill="var(--dracula-current-line)"
|
|
81
|
-
/>
|
|
82
|
-
<path
|
|
83
|
-
d={`M0 ${hillB} Q120 ${hillB - 40} 240 ${hillB} T400 ${hillB - 20} V300 H0 Z`}
|
|
84
|
-
fill="var(--dracula-bg)"
|
|
85
|
-
/>
|
|
86
|
-
<g
|
|
87
|
-
transform={`translate(${60 + ((index * 41) % 280)} ${hillB - 34})`}
|
|
88
|
-
fill="none"
|
|
89
|
-
stroke={hue}
|
|
90
|
-
strokeWidth="4"
|
|
91
|
-
strokeLinecap="round"
|
|
92
|
-
strokeLinejoin="round">
|
|
93
|
-
<rect x="-22" y="-22" width="44" height="44" rx={4} />
|
|
94
|
-
<circle cx="9" cy="-9" r="2" fill={hue} stroke="none" />
|
|
95
|
-
<path d="M-17 15 L-4 0 L5 9 L10 4 L17 12" />
|
|
96
|
-
</g>
|
|
97
|
-
</svg>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
35
|
// ─── Stat Block ─────────────────────────────────────────────────────────────
|
|
102
36
|
|
|
103
37
|
function StatBlock({value, label}: {value: string; label: string}) {
|
|
104
38
|
return (
|
|
105
39
|
<VStack gap={0}>
|
|
106
|
-
<Text type="
|
|
40
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
107
41
|
{value}
|
|
108
42
|
</Text>
|
|
109
43
|
<Text type="supporting" color="secondary">
|
|
@@ -120,7 +54,12 @@ function ImageGrid() {
|
|
|
120
54
|
<Grid columns={3} gap={3}>
|
|
121
55
|
{GALLERY_SCENES.map((scene, index) => (
|
|
122
56
|
<AspectRatio key={scene.alt} ratio={1} style={galleryImageClip}>
|
|
123
|
-
<
|
|
57
|
+
<SceneTile
|
|
58
|
+
label={scene.alt}
|
|
59
|
+
hue={scene.hue}
|
|
60
|
+
size="sm"
|
|
61
|
+
index={index}
|
|
62
|
+
/>
|
|
124
63
|
</AspectRatio>
|
|
125
64
|
))}
|
|
126
65
|
</Grid>
|
|
@@ -129,18 +68,27 @@ function ImageGrid() {
|
|
|
129
68
|
|
|
130
69
|
// ─── Main Page ──────────────────────────────────────────────────────────────
|
|
131
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
|
+
|
|
132
79
|
export default function SideGallery() {
|
|
133
80
|
return (
|
|
134
81
|
<Layout
|
|
82
|
+
style={pageStyle}
|
|
135
83
|
height="fill"
|
|
136
84
|
contentWidth={1400}
|
|
137
85
|
content={
|
|
138
86
|
<LayoutContent padding={6}>
|
|
139
|
-
<Grid columns={{minWidth:
|
|
87
|
+
<Grid columns={{minWidth: 280, repeat: 'fit'}} gap={8} align="center">
|
|
140
88
|
{/* Left side: Text + CTA */}
|
|
141
89
|
<VStack gap={6} vAlign="center">
|
|
142
90
|
<VStack gap={3}>
|
|
143
|
-
<Text type="supporting" color="secondary"
|
|
91
|
+
<Text type="supporting" color="secondary">
|
|
144
92
|
AFTER DARK
|
|
145
93
|
</Text>
|
|
146
94
|
<Heading level={1}>
|
|
@@ -155,9 +103,7 @@ export default function SideGallery() {
|
|
|
155
103
|
</Text>
|
|
156
104
|
</VStack>
|
|
157
105
|
|
|
158
|
-
<
|
|
159
|
-
<Button label="Explore the night" variant="primary" />
|
|
160
|
-
</HStack>
|
|
106
|
+
<Button label="Explore the night" variant="primary" />
|
|
161
107
|
|
|
162
108
|
<VStack gap={4}>
|
|
163
109
|
<Divider />
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
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=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
|
+
*/
|
|
2
8
|
|
|
3
9
|
import React, {useState, useMemo} from 'react';
|
|
4
10
|
import {useResizable, ResizeHandle} from '@astryxdesign/core/Resizable';
|
|
@@ -25,11 +31,12 @@ import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
|
|
|
25
31
|
import {Popover} from '@astryxdesign/core/Popover';
|
|
26
32
|
import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
|
|
27
33
|
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
28
|
-
import {Center} from '@astryxdesign/core/Center';
|
|
29
34
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
30
35
|
import {StatusDot} from '@astryxdesign/core/StatusDot';
|
|
31
36
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
32
37
|
import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
|
|
38
|
+
import {Token} from '@astryxdesign/core/Token';
|
|
39
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
33
40
|
import {
|
|
34
41
|
Table,
|
|
35
42
|
TableRow,
|
|
@@ -85,9 +92,9 @@ interface TaskRow extends Record<string, unknown> {
|
|
|
85
92
|
|
|
86
93
|
const STATUS_DOT_VARIANT: Record<
|
|
87
94
|
TaskStatus,
|
|
88
|
-
'success' | '
|
|
95
|
+
'success' | 'neutral' | 'warning' | 'info'
|
|
89
96
|
> = {
|
|
90
|
-
in_progress: '
|
|
97
|
+
in_progress: 'info',
|
|
91
98
|
todo: 'warning',
|
|
92
99
|
backlog: 'neutral',
|
|
93
100
|
done: 'success',
|
|
@@ -95,10 +102,10 @@ const STATUS_DOT_VARIANT: Record<
|
|
|
95
102
|
|
|
96
103
|
const PRIORITY_COLOR: Record<
|
|
97
104
|
TaskPriority,
|
|
98
|
-
'
|
|
105
|
+
'error' | 'warning' | 'secondary' | 'disabled'
|
|
99
106
|
> = {
|
|
100
|
-
urgent: '
|
|
101
|
-
high: '
|
|
107
|
+
urgent: 'error',
|
|
108
|
+
high: 'warning',
|
|
102
109
|
medium: 'secondary',
|
|
103
110
|
low: 'disabled',
|
|
104
111
|
none: 'disabled',
|
|
@@ -124,7 +131,7 @@ const allTasks: TaskRow[] = [
|
|
|
124
131
|
{
|
|
125
132
|
id: '2',
|
|
126
133
|
taskId: 'T235040470',
|
|
127
|
-
title: '
|
|
134
|
+
title: 'Chart the crypt to organize haunts for rituals or releases',
|
|
128
135
|
subtitle: '',
|
|
129
136
|
status: 'in_progress',
|
|
130
137
|
priority: 'medium',
|
|
@@ -139,7 +146,7 @@ const allTasks: TaskRow[] = [
|
|
|
139
146
|
{
|
|
140
147
|
id: '3',
|
|
141
148
|
taskId: 'T235040471',
|
|
142
|
-
title: '
|
|
149
|
+
title: 'Follow moon cycles to focus haunts over n-weeks',
|
|
143
150
|
subtitle: '',
|
|
144
151
|
status: 'in_progress',
|
|
145
152
|
priority: 'medium',
|
|
@@ -154,7 +161,7 @@ const allTasks: TaskRow[] = [
|
|
|
154
161
|
{
|
|
155
162
|
id: '4',
|
|
156
163
|
taskId: 'T235040472',
|
|
157
|
-
title: '
|
|
164
|
+
title: 'Test the gate wards before moonrise',
|
|
158
165
|
subtitle: 'Update castle gate integration',
|
|
159
166
|
status: 'todo',
|
|
160
167
|
priority: 'medium',
|
|
@@ -229,7 +236,7 @@ const allTasks: TaskRow[] = [
|
|
|
229
236
|
{
|
|
230
237
|
id: '9',
|
|
231
238
|
taskId: 'T235040477',
|
|
232
|
-
title: 'Invite your
|
|
239
|
+
title: 'Invite your fellow familiars to the crypt',
|
|
233
240
|
subtitle: '',
|
|
234
241
|
status: 'todo',
|
|
235
242
|
priority: 'low',
|
|
@@ -244,7 +251,7 @@ const allTasks: TaskRow[] = [
|
|
|
244
251
|
{
|
|
245
252
|
id: '10',
|
|
246
253
|
taskId: 'T235040478',
|
|
247
|
-
title: 'Next
|
|
254
|
+
title: 'Next rites after moonrise',
|
|
248
255
|
subtitle: '',
|
|
249
256
|
status: 'todo',
|
|
250
257
|
priority: 'none',
|
|
@@ -454,7 +461,7 @@ const allTasks: TaskRow[] = [
|
|
|
454
461
|
{
|
|
455
462
|
id: '24',
|
|
456
463
|
taskId: 'T235040492',
|
|
457
|
-
title: '
|
|
464
|
+
title: 'Audit dashboard contrast against the Dracula ramp',
|
|
458
465
|
subtitle: '',
|
|
459
466
|
status: 'backlog',
|
|
460
467
|
priority: 'low',
|
|
@@ -586,7 +593,7 @@ function groupTasks(
|
|
|
586
593
|
}
|
|
587
594
|
const map = new Map<string, TaskRow[]>();
|
|
588
595
|
for (const task of tasks) {
|
|
589
|
-
const key = String(task[groupBy]) || '
|
|
596
|
+
const key = String(task[groupBy]) || 'None';
|
|
590
597
|
let group = map.get(key);
|
|
591
598
|
if (!group) {
|
|
592
599
|
group = [];
|
|
@@ -628,7 +635,7 @@ const columns: TableColumn<TaskRow>[] = [
|
|
|
628
635
|
{
|
|
629
636
|
key: 'project',
|
|
630
637
|
header: 'Project',
|
|
631
|
-
width:
|
|
638
|
+
width: proportional(1),
|
|
632
639
|
},
|
|
633
640
|
{
|
|
634
641
|
key: 'created',
|
|
@@ -729,7 +736,7 @@ function TaskDetailPanel({
|
|
|
729
736
|
</HStack>
|
|
730
737
|
|
|
731
738
|
<VStack gap={1}>
|
|
732
|
-
<Heading level={
|
|
739
|
+
<Heading level={2}>{task.title}</Heading>
|
|
733
740
|
{task.subtitle && (
|
|
734
741
|
<Text type="body" color="secondary">
|
|
735
742
|
{task.subtitle}
|
|
@@ -739,10 +746,11 @@ function TaskDetailPanel({
|
|
|
739
746
|
|
|
740
747
|
<MetadataList label={{position: 'start'}}>
|
|
741
748
|
<MetadataListItem label="Status">
|
|
742
|
-
<HStack gap={
|
|
749
|
+
<HStack gap={1} vAlign="center">
|
|
743
750
|
<StatusDot
|
|
744
751
|
variant={STATUS_DOT_VARIANT[task.status]}
|
|
745
752
|
label={STATUS_LABEL[task.status]}
|
|
753
|
+
isPulsing={task.status === 'in_progress'}
|
|
746
754
|
/>
|
|
747
755
|
<Text type="body">{STATUS_LABEL[task.status]}</Text>
|
|
748
756
|
</HStack>
|
|
@@ -768,7 +776,7 @@ function TaskDetailPanel({
|
|
|
768
776
|
<Text type="body">{task.project}</Text>
|
|
769
777
|
) : (
|
|
770
778
|
<Text type="supporting" color="secondary">
|
|
771
|
-
|
|
779
|
+
None
|
|
772
780
|
</Text>
|
|
773
781
|
)}
|
|
774
782
|
</MetadataListItem>
|
|
@@ -787,7 +795,7 @@ function TaskDetailPanel({
|
|
|
787
795
|
<Text type="label">Labels</Text>
|
|
788
796
|
<HStack gap={2}>
|
|
789
797
|
{task.tags.map(tag => (
|
|
790
|
-
<
|
|
798
|
+
<Token key={tag} color="yellow" label={tag} />
|
|
791
799
|
))}
|
|
792
800
|
</HStack>
|
|
793
801
|
</VStack>
|
|
@@ -813,6 +821,10 @@ export default function TableGrouped() {
|
|
|
813
821
|
() => new Set(GROUP_ORDER as string[]),
|
|
814
822
|
);
|
|
815
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
|
+
|
|
816
828
|
const filtered = useMemo(() => {
|
|
817
829
|
return applyFilters(powerSearchFilters, allTasks);
|
|
818
830
|
}, [powerSearchFilters, applyFilters]);
|
|
@@ -822,11 +834,13 @@ export default function TableGrouped() {
|
|
|
822
834
|
[filtered, groupBy],
|
|
823
835
|
);
|
|
824
836
|
|
|
825
|
-
const groupKeys =
|
|
837
|
+
const groupKeys = Array.from(grouped.keys());
|
|
826
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.
|
|
827
841
|
React.useEffect(() => {
|
|
828
|
-
setExpandedGroups(new Set(
|
|
829
|
-
}, [
|
|
842
|
+
setExpandedGroups(new Set(grouped.keys()));
|
|
843
|
+
}, [grouped]);
|
|
830
844
|
|
|
831
845
|
const toggleGroup = (key: string) => {
|
|
832
846
|
setExpandedGroups(prev => {
|
|
@@ -854,16 +868,21 @@ export default function TableGrouped() {
|
|
|
854
868
|
<Layout
|
|
855
869
|
height="fill"
|
|
856
870
|
header={
|
|
857
|
-
<LayoutHeader hasDivider padding={
|
|
871
|
+
<LayoutHeader hasDivider padding={6}>
|
|
858
872
|
<VStack gap={4}>
|
|
859
873
|
<HStack gap={3} vAlign="center">
|
|
860
874
|
<StackItem size="fill">
|
|
861
|
-
|
|
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>
|
|
862
882
|
</StackItem>
|
|
863
883
|
<Button
|
|
864
884
|
label="Raise issue"
|
|
865
885
|
variant="primary"
|
|
866
|
-
size="lg"
|
|
867
886
|
onClick={() => setDialogOpen(true)}
|
|
868
887
|
/>
|
|
869
888
|
</HStack>
|
|
@@ -873,14 +892,14 @@ export default function TableGrouped() {
|
|
|
873
892
|
config={powerSearchConfig}
|
|
874
893
|
filters={powerSearchFilters}
|
|
875
894
|
onChange={newFilters => setPowerSearchFilters(newFilters)}
|
|
876
|
-
placeholder="Filter issues
|
|
895
|
+
placeholder="Filter issues…"
|
|
877
896
|
resultCount={`${filtered.length} issue${filtered.length !== 1 ? 's' : ''}`}
|
|
878
897
|
/>
|
|
879
898
|
</StackItem>
|
|
880
899
|
<Popover
|
|
881
900
|
placement="below"
|
|
882
901
|
alignment="end"
|
|
883
|
-
width=
|
|
902
|
+
width="min(20rem, calc(100vw - 2 * var(--space-viewport)))"
|
|
884
903
|
label="Grouping options"
|
|
885
904
|
content={
|
|
886
905
|
<VStack gap={4}>
|
|
@@ -898,7 +917,7 @@ export default function TableGrouped() {
|
|
|
898
917
|
</RadioList>
|
|
899
918
|
</VStack>
|
|
900
919
|
}>
|
|
901
|
-
<Button label="View
|
|
920
|
+
<Button label="View options" variant="secondary" size="md" />
|
|
902
921
|
</Popover>
|
|
903
922
|
</HStack>
|
|
904
923
|
</VStack>
|
|
@@ -949,7 +968,7 @@ export default function TableGrouped() {
|
|
|
949
968
|
size="sm"
|
|
950
969
|
color="secondary"
|
|
951
970
|
/>
|
|
952
|
-
<Text type="body" weight="
|
|
971
|
+
<Text type="body" weight="semibold">
|
|
953
972
|
{getGroupLabel(groupBy, key)}
|
|
954
973
|
</Text>
|
|
955
974
|
<Badge
|
|
@@ -966,12 +985,21 @@ export default function TableGrouped() {
|
|
|
966
985
|
key={task.id}
|
|
967
986
|
onClick={() => setSelectedTask(task)}>
|
|
968
987
|
<TableCell>
|
|
969
|
-
|
|
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">
|
|
970
994
|
<StatusDot
|
|
971
995
|
variant={STATUS_DOT_VARIANT[task.status]}
|
|
972
996
|
label={STATUS_LABEL[task.status]}
|
|
997
|
+
isPulsing={task.status === 'in_progress'}
|
|
973
998
|
/>
|
|
974
|
-
|
|
999
|
+
<Text type="supporting" color="secondary">
|
|
1000
|
+
{STATUS_LABEL[task.status]}
|
|
1001
|
+
</Text>
|
|
1002
|
+
</HStack>
|
|
975
1003
|
</TableCell>
|
|
976
1004
|
<TableCell>
|
|
977
1005
|
<HStack gap={3} vAlign="center" wrap="wrap">
|
|
@@ -1003,7 +1031,7 @@ export default function TableGrouped() {
|
|
|
1003
1031
|
</Text>
|
|
1004
1032
|
) : (
|
|
1005
1033
|
<Text type="supporting" color="secondary">
|
|
1006
|
-
|
|
1034
|
+
None
|
|
1007
1035
|
</Text>
|
|
1008
1036
|
)}
|
|
1009
1037
|
</TableCell>
|
|
@@ -1085,6 +1113,7 @@ export default function TableGrouped() {
|
|
|
1085
1113
|
</LayoutContent>
|
|
1086
1114
|
}
|
|
1087
1115
|
end={
|
|
1116
|
+
!isNarrow &&
|
|
1088
1117
|
selectedTask && (
|
|
1089
1118
|
<>
|
|
1090
1119
|
<ResizeHandle
|
|
@@ -1103,9 +1132,10 @@ export default function TableGrouped() {
|
|
|
1103
1132
|
/>
|
|
1104
1133
|
<Dialog isOpen={dialogOpen} onOpenChange={open => setDialogOpen(open)}>
|
|
1105
1134
|
<Layout
|
|
1135
|
+
height="auto"
|
|
1106
1136
|
header={
|
|
1107
1137
|
<DialogHeader
|
|
1108
|
-
title="Raise
|
|
1138
|
+
title="Raise issue"
|
|
1109
1139
|
onOpenChange={open => setDialogOpen(open)}
|
|
1110
1140
|
/>
|
|
1111
1141
|
}
|
|
@@ -1159,7 +1189,7 @@ export default function TableGrouped() {
|
|
|
1159
1189
|
onClick={() => setDialogOpen(false)}
|
|
1160
1190
|
/>
|
|
1161
1191
|
<Button
|
|
1162
|
-
label="Raise"
|
|
1192
|
+
label="Raise issue"
|
|
1163
1193
|
variant="primary"
|
|
1164
1194
|
size="md"
|
|
1165
1195
|
onClick={() => setDialogOpen(false)}
|