astryx-dracula 0.5.0 → 0.6.2
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 +3 -3
- package/README.md +7 -4
- package/USAGE.md +49 -49
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +25 -9
- package/astryx-dracula.variants.d.ts +2 -2
- package/astryx.integration.mjs +1 -1
- package/icons.mjs +21 -17
- package/icons.tsx +69 -62
- package/package.json +125 -120
- package/shared/auth-chrome-config.ts +6 -6
- package/shared/auth-chrome.tsx +143 -144
- package/shared/auth-copy.ts +14 -14
- package/shared/chaptered-doc-config.ts +48 -51
- package/shared/chaptered-doc.tsx +267 -289
- package/shared/chart-hues.ts +25 -10
- package/shared/chart-labels.tsx +31 -30
- package/shared/chart-legend.tsx +55 -56
- package/shared/chart-panel-style.ts +3 -3
- package/shared/data-bar.tsx +97 -102
- package/shared/format-money.ts +1 -1
- package/shared/gallery-image.ts +6 -6
- package/shared/login-demo.ts +1 -1
- package/shared/metric-card.tsx +101 -105
- package/shared/metric-delta.tsx +28 -27
- package/shared/order-desk.tsx +205 -216
- package/shared/revenue-chart.tsx +145 -165
- package/shared/scene-castle.tsx +206 -291
- package/shared/scene-frame.tsx +33 -32
- package/shared/scene-hues.ts +17 -17
- package/shared/scene-tile.tsx +88 -92
- package/shared/settings-data.ts +119 -125
- package/shared/settings-rows.tsx +122 -159
- package/shared/sparkline.tsx +75 -88
- package/shared/sso-icons.tsx +10 -22
- package/shared/stars.tsx +16 -25
- package/templates/ai-chat-landing.template.mjs +5 -4
- package/templates/ai-chat-landing.tsx +473 -460
- package/templates/ai-chat.template.mjs +5 -4
- package/templates/ai-chat.tsx +543 -612
- package/templates/blank.template.mjs +5 -4
- package/templates/blank.tsx +18 -14
- package/templates/centered-hero.template.mjs +5 -4
- package/templates/centered-hero.tsx +55 -68
- package/templates/classic-gallery.template.mjs +5 -4
- package/templates/classic-gallery.tsx +97 -104
- package/templates/contact-form.template.mjs +5 -4
- package/templates/contact-form.tsx +247 -282
- package/templates/dashboard-portfolio.template.mjs +5 -4
- package/templates/dashboard-portfolio.tsx +717 -548
- package/templates/dashboard.template.mjs +5 -4
- package/templates/dashboard.tsx +596 -567
- package/templates/detail-page.template.mjs +5 -4
- package/templates/detail-page.tsx +546 -603
- package/templates/documentation-design.template.mjs +5 -4
- package/templates/documentation-design.tsx +231 -243
- package/templates/documentation-technical.template.mjs +5 -4
- package/templates/documentation-technical.tsx +236 -239
- package/templates/documentation.template.mjs +5 -4
- package/templates/documentation.tsx +267 -279
- package/templates/editor.template.mjs +5 -4
- package/templates/editor.tsx +844 -877
- package/templates/file-explorer.template.mjs +5 -4
- package/templates/file-explorer.tsx +566 -590
- package/templates/form-two-column.template.mjs +5 -4
- package/templates/form-two-column.tsx +195 -210
- package/templates/gallery-hero.template.mjs +5 -4
- package/templates/gallery-hero.tsx +93 -114
- package/templates/ide.template.mjs +5 -4
- package/templates/ide.tsx +407 -453
- package/templates/incident-console.template.mjs +5 -4
- package/templates/incident-console.tsx +593 -575
- package/templates/kanban-board.template.mjs +5 -4
- package/templates/kanban-board.tsx +665 -683
- package/templates/library.template.mjs +5 -4
- package/templates/library.tsx +409 -430
- package/templates/login-card.template.mjs +5 -4
- package/templates/login-card.tsx +63 -72
- package/templates/login-split.template.mjs +5 -4
- package/templates/login-split.tsx +192 -193
- package/templates/login-sso.template.mjs +5 -4
- package/templates/login-sso.tsx +257 -258
- package/templates/login.template.mjs +5 -4
- package/templates/login.tsx +53 -49
- package/templates/messaging-shell.template.mjs +5 -4
- package/templates/messaging-shell.tsx +556 -574
- package/templates/mixed-gallery.template.mjs +5 -4
- package/templates/mixed-gallery.tsx +60 -58
- package/templates/payment-form.template.mjs +5 -4
- package/templates/payment-form.tsx +882 -991
- package/templates/product-detail.template.mjs +5 -4
- package/templates/product-detail.tsx +261 -272
- package/templates/product-gallery.template.mjs +5 -4
- package/templates/product-gallery.tsx +126 -137
- package/templates/product-tour.template.mjs +5 -4
- package/templates/product-tour.tsx +741 -773
- package/templates/settings-dialog.template.mjs +5 -4
- package/templates/settings-dialog.tsx +688 -717
- package/templates/settings-sidebar.template.mjs +5 -4
- package/templates/settings-sidebar.tsx +762 -807
- package/templates/settings.template.mjs +5 -4
- package/templates/settings.tsx +289 -304
- package/templates/shell-nav.template.mjs +5 -4
- package/templates/shell-nav.tsx +212 -219
- package/templates/shell-side-nav.template.mjs +5 -4
- package/templates/shell-side-nav.tsx +135 -157
- package/templates/shell-top-nav.template.mjs +5 -4
- package/templates/shell-top-nav.tsx +182 -203
- package/templates/side-gallery.template.mjs +5 -4
- package/templates/side-gallery.tsx +85 -90
- package/templates/table-grouped.template.mjs +5 -4
- package/templates/table-grouped.tsx +1000 -1053
- package/templates/table-page-chart.template.mjs +5 -4
- package/templates/table-page-chart.tsx +400 -398
- package/templates/table-page-heatmap-status.template.mjs +5 -4
- package/templates/table-page-heatmap-status.tsx +483 -512
- package/templates/table-page-shoe-store-heatmap.template.mjs +5 -4
- package/templates/table-page-shoe-store-heatmap.tsx +748 -746
- package/templates/table-page.template.mjs +5 -4
- package/templates/table-page.tsx +396 -405
- package/templates/table.template.mjs +5 -4
- package/templates/table.tsx +92 -93
- package/templates/tech-report.template.mjs +5 -4
- package/templates/tech-report.tsx +1316 -1304
- package/templates/theme-showcase.template.mjs +5 -4
- package/templates/theme-showcase.tsx +871 -951
- package/theme.css +35 -77
- package/theme.css.d.ts +9 -0
- package/tokens.css +373 -380
|
@@ -1,557 +1,550 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
// XLE (canonical structure
|
|
3
|
-
//
|
|
2
|
+
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
|
+
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
|
+
// human drift check — update it with the file, never alone):
|
|
5
|
+
// L[h=fill] > (LH[divider p=6] > V[g=4] > (H[g=3 a=center wrap] > (SI[fill] > Hd"Night-shift issues"[level=1 t=display-2]) + B.primary"Raise issue"[opens=#raise-issue]) + (H[g=2 a=center] > (SI[fill] > PS"Filter issues…") + (Po > (B.secondary"View options" + (V[g=4] > RL"Group by" > RLI*5))))) + (LC[p=0] > T[hover density=balanced dv=rows] > (TR > THC*7) + ((TR > TC[colSpan=7] > (H[g=2 a=center] > Ic + Tx"In Progress"[t=body] + Bd.neutral"3")) + (TR > TC*7))*4)) + (LP[divider p=4] > V[g=4] > (H[g=2 a=center] > (SI[fill] > Tx[t=supporting]) + B.ghost"Close panel") + (V[g=1] > Hd[level=2] + Tx[t=body]) + (ML > MLI*6) + (D + (V[g=2] > Tx"Labels"[t=label] + (H[g=2] > Tk*2)))) ;; Dlg#raise-issue > L[h=auto] > (DH"Raise issue" + (LC[p=4] > V[g=4] > TI"Title" + SE{Selector}"Status" + SE{Selector}"Priority" + TI"Project") + (LF[divider] > H[g=2 j=end] > B.secondary"Cancel" + B.primary"Raise issue"))
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* 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
9
|
*/
|
|
8
10
|
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
11
|
+
import { Avatar } from "@astryxdesign/core/Avatar";
|
|
12
|
+
import { Badge } from "@astryxdesign/core/Badge";
|
|
13
|
+
import { Button } from "@astryxdesign/core/Button";
|
|
14
|
+
import { Dialog, DialogHeader } from "@astryxdesign/core/Dialog";
|
|
15
|
+
import { Divider } from "@astryxdesign/core/Divider";
|
|
16
|
+
import { DropdownMenu } from "@astryxdesign/core/DropdownMenu";
|
|
17
|
+
import { useMediaQuery } from "@astryxdesign/core/hooks";
|
|
18
|
+
import { Icon } from "@astryxdesign/core/Icon";
|
|
12
19
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
|
|
33
|
-
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
34
|
-
import {Icon} from '@astryxdesign/core/Icon';
|
|
35
|
-
import {StatusDot} from '@astryxdesign/core/StatusDot';
|
|
36
|
-
import {Divider} from '@astryxdesign/core/Divider';
|
|
37
|
-
import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
|
|
38
|
-
import {Token} from '@astryxdesign/core/Token';
|
|
39
|
-
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
20
|
+
HStack,
|
|
21
|
+
Layout,
|
|
22
|
+
LayoutContent,
|
|
23
|
+
LayoutFooter,
|
|
24
|
+
LayoutHeader,
|
|
25
|
+
LayoutPanel,
|
|
26
|
+
StackItem,
|
|
27
|
+
VStack,
|
|
28
|
+
} from "@astryxdesign/core/Layout";
|
|
29
|
+
import { MetadataList, MetadataListItem } from "@astryxdesign/core/MetadataList";
|
|
30
|
+
import { Popover } from "@astryxdesign/core/Popover";
|
|
31
|
+
import type { PowerSearchFilter } from "@astryxdesign/core/PowerSearch";
|
|
32
|
+
import { PowerSearch, usePowerSearchConfig } from "@astryxdesign/core/PowerSearch";
|
|
33
|
+
import { RadioList, RadioListItem } from "@astryxdesign/core/RadioList";
|
|
34
|
+
import type { ResizableProps } from "@astryxdesign/core/Resizable";
|
|
35
|
+
import { ResizeHandle, useResizable } from "@astryxdesign/core/Resizable";
|
|
36
|
+
import { Selector } from "@astryxdesign/core/Selector";
|
|
37
|
+
import { StatusDot } from "@astryxdesign/core/StatusDot";
|
|
38
|
+
import type { TableColumn } from "@astryxdesign/core/Table";
|
|
40
39
|
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} from
|
|
49
|
-
import
|
|
40
|
+
pixel,
|
|
41
|
+
proportional,
|
|
42
|
+
resolveColumnWidths,
|
|
43
|
+
Table,
|
|
44
|
+
TableCell,
|
|
45
|
+
TableHeaderCell,
|
|
46
|
+
TableRow,
|
|
47
|
+
} from "@astryxdesign/core/Table";
|
|
48
|
+
import { Heading, Text } from "@astryxdesign/core/Text";
|
|
49
|
+
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
50
|
+
import { Token } from "@astryxdesign/core/Token";
|
|
50
51
|
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} from
|
|
52
|
+
ArrowRight,
|
|
53
|
+
ChartBar,
|
|
54
|
+
ChevronDown,
|
|
55
|
+
ChevronRight,
|
|
56
|
+
Copy,
|
|
57
|
+
Ellipsis,
|
|
58
|
+
Pencil,
|
|
59
|
+
Tag,
|
|
60
|
+
Trash,
|
|
61
|
+
User,
|
|
62
|
+
X,
|
|
63
|
+
} from "lucide-react";
|
|
64
|
+
import React, { useMemo, useState } from "react";
|
|
63
65
|
|
|
64
66
|
// Plain inline styles using Astryx design-token CSS variables (declared at
|
|
65
67
|
// :root by `@astryxdesign/core/astryx.css`). No StyleX compiler required.
|
|
66
68
|
// The group-header background + cursor live on the colSpan TableCell (which
|
|
67
69
|
// reliably forwards `style`) so they fill the full row width.
|
|
68
70
|
const groupHeaderCell: React.CSSProperties = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
cursor: "pointer",
|
|
72
|
+
backgroundColor: "var(--color-background-muted)",
|
|
73
|
+
padding: "var(--spacing-3) var(--spacing-4)",
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
// Types
|
|
75
|
-
type TaskStatus =
|
|
76
|
-
type TaskPriority =
|
|
77
|
+
type TaskStatus = "in_progress" | "todo" | "backlog" | "done";
|
|
78
|
+
type TaskPriority = "urgent" | "high" | "medium" | "low" | "none";
|
|
77
79
|
|
|
78
80
|
interface TaskRow extends Record<string, unknown> {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
id: string;
|
|
82
|
+
taskId: string;
|
|
83
|
+
title: string;
|
|
84
|
+
subtitle: string;
|
|
85
|
+
status: TaskStatus;
|
|
86
|
+
priority: TaskPriority;
|
|
87
|
+
project: string;
|
|
88
|
+
tags: string[];
|
|
89
|
+
created: string;
|
|
90
|
+
updated: string;
|
|
91
|
+
assignee: string;
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const STATUS_DOT_VARIANT: Record<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
todo: 'warning',
|
|
98
|
-
backlog: 'neutral',
|
|
99
|
-
done: 'success',
|
|
94
|
+
const STATUS_DOT_VARIANT: Record<TaskStatus, "success" | "neutral" | "warning" | "info"> = {
|
|
95
|
+
in_progress: "info",
|
|
96
|
+
todo: "warning",
|
|
97
|
+
backlog: "neutral",
|
|
98
|
+
done: "success",
|
|
100
99
|
};
|
|
101
100
|
|
|
102
|
-
const PRIORITY_COLOR: Record<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
medium: 'secondary',
|
|
109
|
-
low: 'disabled',
|
|
110
|
-
none: 'disabled',
|
|
101
|
+
const PRIORITY_COLOR: Record<TaskPriority, "error" | "warning" | "secondary" | "disabled"> = {
|
|
102
|
+
urgent: "error",
|
|
103
|
+
high: "warning",
|
|
104
|
+
medium: "secondary",
|
|
105
|
+
low: "disabled",
|
|
106
|
+
none: "disabled",
|
|
111
107
|
};
|
|
112
108
|
|
|
113
109
|
// Mock data matching a task tracker
|
|
114
110
|
const allTasks: TaskRow[] = [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
111
|
+
{
|
|
112
|
+
id: "1",
|
|
113
|
+
taskId: "T235040469",
|
|
114
|
+
title: "Update user interface",
|
|
115
|
+
subtitle: "Update castle gate integration",
|
|
116
|
+
status: "in_progress",
|
|
117
|
+
priority: "medium",
|
|
118
|
+
project: "Castle gate integration 2.0",
|
|
119
|
+
tags: [],
|
|
120
|
+
created: "Jul 3",
|
|
121
|
+
updated: "Jul 3",
|
|
122
|
+
assignee: "Olivia Martin",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "2",
|
|
126
|
+
taskId: "T235040470",
|
|
127
|
+
title: "Chart the crypt to organize haunts for rituals or releases",
|
|
128
|
+
subtitle: "",
|
|
129
|
+
status: "in_progress",
|
|
130
|
+
priority: "medium",
|
|
131
|
+
project: "",
|
|
132
|
+
tags: [],
|
|
133
|
+
created: "Jul 1",
|
|
134
|
+
updated: "Jul 3",
|
|
135
|
+
assignee: "Jackson Lee",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "3",
|
|
139
|
+
taskId: "T235040471",
|
|
140
|
+
title: "Follow moon cycles to focus haunts over n-weeks",
|
|
141
|
+
subtitle: "",
|
|
142
|
+
status: "in_progress",
|
|
143
|
+
priority: "medium",
|
|
144
|
+
project: "",
|
|
145
|
+
tags: [],
|
|
146
|
+
created: "Jul 1",
|
|
147
|
+
updated: "Jul 3",
|
|
148
|
+
assignee: "Isabella Nguyen",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "4",
|
|
152
|
+
taskId: "T235040472",
|
|
153
|
+
title: "Test the gate wards before moonrise",
|
|
154
|
+
subtitle: "Update castle gate integration",
|
|
155
|
+
status: "todo",
|
|
156
|
+
priority: "medium",
|
|
157
|
+
project: "Castle gate integration 2.0",
|
|
158
|
+
tags: [],
|
|
159
|
+
created: "Jul 3",
|
|
160
|
+
updated: "Jul 3",
|
|
161
|
+
assignee: "William Kim",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "5",
|
|
165
|
+
taskId: "T235040473",
|
|
166
|
+
title: "Update backend code",
|
|
167
|
+
subtitle: "Update castle gate integration",
|
|
168
|
+
status: "todo",
|
|
169
|
+
priority: "medium",
|
|
170
|
+
project: "Castle gate integration 2.0",
|
|
171
|
+
tags: [],
|
|
172
|
+
created: "Jul 3",
|
|
173
|
+
updated: "Jul 3",
|
|
174
|
+
assignee: "Sofia Davis",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "6",
|
|
178
|
+
taskId: "T235040474",
|
|
179
|
+
title: "Update front end code",
|
|
180
|
+
subtitle: "Update castle gate integration",
|
|
181
|
+
status: "todo",
|
|
182
|
+
priority: "medium",
|
|
183
|
+
project: "Castle gate integration 2.0",
|
|
184
|
+
tags: [],
|
|
185
|
+
created: "Jul 3",
|
|
186
|
+
updated: "Jul 3",
|
|
187
|
+
assignee: "Mia Wilson",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "7",
|
|
191
|
+
taskId: "T235040475",
|
|
192
|
+
title: "Update castle gate integration",
|
|
193
|
+
subtitle: "",
|
|
194
|
+
status: "todo",
|
|
195
|
+
priority: "high",
|
|
196
|
+
project: "Castle gate integration 2.0",
|
|
197
|
+
tags: ["Improvement", "3rd Party"],
|
|
198
|
+
created: "Jul 3",
|
|
199
|
+
updated: "Jul 3",
|
|
200
|
+
assignee: "Lucas Brown",
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "8",
|
|
204
|
+
taskId: "T235040476",
|
|
205
|
+
title: "Update payment gateway backend code",
|
|
206
|
+
subtitle: "",
|
|
207
|
+
status: "todo",
|
|
208
|
+
priority: "medium",
|
|
209
|
+
project: "",
|
|
210
|
+
tags: [],
|
|
211
|
+
created: "Jul 3",
|
|
212
|
+
updated: "Jul 3",
|
|
213
|
+
assignee: "Ethan Jones",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "9",
|
|
217
|
+
taskId: "T235040477",
|
|
218
|
+
title: "Invite your fellow familiars to the crypt",
|
|
219
|
+
subtitle: "",
|
|
220
|
+
status: "todo",
|
|
221
|
+
priority: "low",
|
|
222
|
+
project: "",
|
|
223
|
+
tags: [],
|
|
224
|
+
created: "Jul 1",
|
|
225
|
+
updated: "Jul 1",
|
|
226
|
+
assignee: "Ava Taylor",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: "10",
|
|
230
|
+
taskId: "T235040478",
|
|
231
|
+
title: "Next rites after moonrise",
|
|
232
|
+
subtitle: "",
|
|
233
|
+
status: "todo",
|
|
234
|
+
priority: "none",
|
|
235
|
+
project: "",
|
|
236
|
+
tags: [],
|
|
237
|
+
created: "Jul 1",
|
|
238
|
+
updated: "Jul 3",
|
|
239
|
+
assignee: "Noah Garcia",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "11",
|
|
243
|
+
taskId: "T235040479",
|
|
244
|
+
title: "Welcome to the crypt",
|
|
245
|
+
subtitle: "",
|
|
246
|
+
status: "backlog",
|
|
247
|
+
priority: "none",
|
|
248
|
+
project: "",
|
|
249
|
+
tags: [],
|
|
250
|
+
created: "Jul 1",
|
|
251
|
+
updated: "Jul 3",
|
|
252
|
+
assignee: "Olivia Martin",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: "12",
|
|
256
|
+
taskId: "T235040480",
|
|
257
|
+
title: "Connect GitHub or GitLab",
|
|
258
|
+
subtitle: "",
|
|
259
|
+
status: "backlog",
|
|
260
|
+
priority: "none",
|
|
261
|
+
project: "",
|
|
262
|
+
tags: [],
|
|
263
|
+
created: "Jul 1",
|
|
264
|
+
updated: "Jul 3",
|
|
265
|
+
assignee: "Jackson Lee",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "13",
|
|
269
|
+
taskId: "T235040481",
|
|
270
|
+
title: "Customize settings",
|
|
271
|
+
subtitle: "",
|
|
272
|
+
status: "backlog",
|
|
273
|
+
priority: "none",
|
|
274
|
+
project: "",
|
|
275
|
+
tags: [],
|
|
276
|
+
created: "Jul 1",
|
|
277
|
+
updated: "Jul 3",
|
|
278
|
+
assignee: "Isabella Nguyen",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: "14",
|
|
282
|
+
taskId: "T235040482",
|
|
283
|
+
title: "Try 3 ways to navigate: Command menu, keyboard or mouse",
|
|
284
|
+
subtitle: "",
|
|
285
|
+
status: "done",
|
|
286
|
+
priority: "none",
|
|
287
|
+
project: "",
|
|
288
|
+
tags: [],
|
|
289
|
+
created: "Jul 1",
|
|
290
|
+
updated: "Jul 3",
|
|
291
|
+
assignee: "William Kim",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "15",
|
|
295
|
+
taskId: "T235040483",
|
|
296
|
+
title: "Connect to Slack",
|
|
297
|
+
subtitle: "",
|
|
298
|
+
status: "done",
|
|
299
|
+
priority: "none",
|
|
300
|
+
project: "",
|
|
301
|
+
tags: [],
|
|
302
|
+
created: "Jul 1",
|
|
303
|
+
updated: "Jul 3",
|
|
304
|
+
assignee: "Sofia Davis",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "16",
|
|
308
|
+
taskId: "T235040484",
|
|
309
|
+
title: "Migrate database schema to v2",
|
|
310
|
+
subtitle: "Castle gate integration",
|
|
311
|
+
status: "in_progress",
|
|
312
|
+
priority: "high",
|
|
313
|
+
project: "Castle gate integration 2.0",
|
|
314
|
+
tags: [],
|
|
315
|
+
created: "Jul 4",
|
|
316
|
+
updated: "Jul 5",
|
|
317
|
+
assignee: "Lucas Brown",
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: "17",
|
|
321
|
+
taskId: "T235040485",
|
|
322
|
+
title: "Write integration tests for checkout flow",
|
|
323
|
+
subtitle: "",
|
|
324
|
+
status: "in_progress",
|
|
325
|
+
priority: "medium",
|
|
326
|
+
project: "Castle gate integration 2.0",
|
|
327
|
+
tags: [],
|
|
328
|
+
created: "Jul 4",
|
|
329
|
+
updated: "Jul 5",
|
|
330
|
+
assignee: "Ethan Jones",
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: "18",
|
|
334
|
+
taskId: "T235040486",
|
|
335
|
+
title: "Set up CI/CD pipeline for staging",
|
|
336
|
+
subtitle: "",
|
|
337
|
+
status: "in_progress",
|
|
338
|
+
priority: "high",
|
|
339
|
+
project: "",
|
|
340
|
+
tags: [],
|
|
341
|
+
created: "Jul 2",
|
|
342
|
+
updated: "Jul 5",
|
|
343
|
+
assignee: "Ava Taylor",
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "19",
|
|
347
|
+
taskId: "T235040487",
|
|
348
|
+
title: "Add rate limiting to public API endpoints",
|
|
349
|
+
subtitle: "",
|
|
350
|
+
status: "todo",
|
|
351
|
+
priority: "urgent",
|
|
352
|
+
project: "",
|
|
353
|
+
tags: [],
|
|
354
|
+
created: "Jul 5",
|
|
355
|
+
updated: "Jul 5",
|
|
356
|
+
assignee: "Noah Garcia",
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: "20",
|
|
360
|
+
taskId: "T235040488",
|
|
361
|
+
title: "Refactor auth middleware to support OAuth2",
|
|
362
|
+
subtitle: "",
|
|
363
|
+
status: "todo",
|
|
364
|
+
priority: "high",
|
|
365
|
+
project: "",
|
|
366
|
+
tags: [],
|
|
367
|
+
created: "Jul 4",
|
|
368
|
+
updated: "Jul 5",
|
|
369
|
+
assignee: "Olivia Martin",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: "21",
|
|
373
|
+
taskId: "T235040489",
|
|
374
|
+
title: "Design error pages for 404 and 500",
|
|
375
|
+
subtitle: "",
|
|
376
|
+
status: "todo",
|
|
377
|
+
priority: "low",
|
|
378
|
+
project: "",
|
|
379
|
+
tags: [],
|
|
380
|
+
created: "Jul 3",
|
|
381
|
+
updated: "Jul 4",
|
|
382
|
+
assignee: "Mia Wilson",
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
id: "22",
|
|
386
|
+
taskId: "T235040490",
|
|
387
|
+
title: "Audit third-party dependencies for vulnerabilities",
|
|
388
|
+
subtitle: "",
|
|
389
|
+
status: "todo",
|
|
390
|
+
priority: "medium",
|
|
391
|
+
project: "",
|
|
392
|
+
tags: [],
|
|
393
|
+
created: "Jul 5",
|
|
394
|
+
updated: "Jul 5",
|
|
395
|
+
assignee: "Jackson Lee",
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "23",
|
|
399
|
+
taskId: "T235040491",
|
|
400
|
+
title: "Implement webhook retry logic with exponential backoff",
|
|
401
|
+
subtitle: "Castle gate integration",
|
|
402
|
+
status: "todo",
|
|
403
|
+
priority: "medium",
|
|
404
|
+
project: "Castle gate integration 2.0",
|
|
405
|
+
tags: [],
|
|
406
|
+
created: "Jul 4",
|
|
407
|
+
updated: "Jul 5",
|
|
408
|
+
assignee: "William Kim",
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
id: "24",
|
|
412
|
+
taskId: "T235040492",
|
|
413
|
+
title: "Audit dashboard contrast against the Dracula ramp",
|
|
414
|
+
subtitle: "",
|
|
415
|
+
status: "backlog",
|
|
416
|
+
priority: "low",
|
|
417
|
+
project: "",
|
|
418
|
+
tags: [],
|
|
419
|
+
created: "Jul 2",
|
|
420
|
+
updated: "Jul 3",
|
|
421
|
+
assignee: "Sofia Davis",
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: "25",
|
|
425
|
+
taskId: "T235040493",
|
|
426
|
+
title: "Create onboarding flow for new team members",
|
|
427
|
+
subtitle: "",
|
|
428
|
+
status: "backlog",
|
|
429
|
+
priority: "none",
|
|
430
|
+
project: "",
|
|
431
|
+
tags: [],
|
|
432
|
+
created: "Jul 1",
|
|
433
|
+
updated: "Jul 2",
|
|
434
|
+
assignee: "Isabella Nguyen",
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: "26",
|
|
438
|
+
taskId: "T235040494",
|
|
439
|
+
title: "Set up error tracking with Sentry",
|
|
440
|
+
subtitle: "",
|
|
441
|
+
status: "backlog",
|
|
442
|
+
priority: "medium",
|
|
443
|
+
project: "",
|
|
444
|
+
tags: [],
|
|
445
|
+
created: "Jul 3",
|
|
446
|
+
updated: "Jul 4",
|
|
447
|
+
assignee: "Ethan Jones",
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
id: "27",
|
|
451
|
+
taskId: "T235040495",
|
|
452
|
+
title: "Improve search performance with indexing",
|
|
453
|
+
subtitle: "",
|
|
454
|
+
status: "backlog",
|
|
455
|
+
priority: "low",
|
|
456
|
+
project: "",
|
|
457
|
+
tags: [],
|
|
458
|
+
created: "Jul 2",
|
|
459
|
+
updated: "Jul 3",
|
|
460
|
+
assignee: "Ava Taylor",
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
id: "28",
|
|
464
|
+
taskId: "T235040496",
|
|
465
|
+
title: "Write API documentation for v2 endpoints",
|
|
466
|
+
subtitle: "",
|
|
467
|
+
status: "done",
|
|
468
|
+
priority: "medium",
|
|
469
|
+
project: "",
|
|
470
|
+
tags: [],
|
|
471
|
+
created: "Jun 28",
|
|
472
|
+
updated: "Jul 3",
|
|
473
|
+
assignee: "Noah Garcia",
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: "29",
|
|
477
|
+
taskId: "T235040497",
|
|
478
|
+
title: "Set up staging environment",
|
|
479
|
+
subtitle: "",
|
|
480
|
+
status: "done",
|
|
481
|
+
priority: "high",
|
|
482
|
+
project: "",
|
|
483
|
+
tags: [],
|
|
484
|
+
created: "Jun 25",
|
|
485
|
+
updated: "Jul 1",
|
|
486
|
+
assignee: "Lucas Brown",
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
id: "30",
|
|
490
|
+
taskId: "T235040498",
|
|
491
|
+
title: "Fix flaky end-to-end tests in CI",
|
|
492
|
+
subtitle: "",
|
|
493
|
+
status: "done",
|
|
494
|
+
priority: "medium",
|
|
495
|
+
project: "",
|
|
496
|
+
tags: [],
|
|
497
|
+
created: "Jun 30",
|
|
498
|
+
updated: "Jul 2",
|
|
499
|
+
assignee: "Mia Wilson",
|
|
500
|
+
},
|
|
505
501
|
];
|
|
506
502
|
|
|
507
503
|
const STATUS_LABEL: Record<TaskStatus, string> = {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
504
|
+
in_progress: "In Progress",
|
|
505
|
+
todo: "Todo",
|
|
506
|
+
backlog: "Backlog",
|
|
507
|
+
done: "Done",
|
|
512
508
|
};
|
|
513
509
|
|
|
514
|
-
type GroupByField =
|
|
510
|
+
type GroupByField = "status" | "priority" | "project" | "assignee" | "none";
|
|
515
511
|
|
|
516
|
-
const GROUP_BY_OPTIONS: {value: GroupByField; label: string}[] = [
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
512
|
+
const GROUP_BY_OPTIONS: { value: GroupByField; label: string }[] = [
|
|
513
|
+
{ value: "none", label: "None" },
|
|
514
|
+
{ value: "status", label: "Status" },
|
|
515
|
+
{ value: "priority", label: "Priority" },
|
|
516
|
+
{ value: "project", label: "Project" },
|
|
517
|
+
{ value: "assignee", label: "Assignee" },
|
|
522
518
|
];
|
|
523
519
|
|
|
524
|
-
function groupTasks(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
group.push(task);
|
|
540
|
-
}
|
|
541
|
-
return map;
|
|
520
|
+
function groupTasks(tasks: TaskRow[], groupBy: GroupByField): Map<string, TaskRow[]> {
|
|
521
|
+
if (groupBy === "none") {
|
|
522
|
+
return new Map([["All", tasks]]);
|
|
523
|
+
}
|
|
524
|
+
const map = new Map<string, TaskRow[]>();
|
|
525
|
+
for (const task of tasks) {
|
|
526
|
+
const key = String(task[groupBy]) || "None";
|
|
527
|
+
let group = map.get(key);
|
|
528
|
+
if (!group) {
|
|
529
|
+
group = [];
|
|
530
|
+
map.set(key, group);
|
|
531
|
+
}
|
|
532
|
+
group.push(task);
|
|
533
|
+
}
|
|
534
|
+
return map;
|
|
542
535
|
}
|
|
543
536
|
|
|
544
537
|
function getGroupLabel(groupBy: GroupByField, key: string): string {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
538
|
+
if (groupBy === "status") {
|
|
539
|
+
return STATUS_LABEL[key as TaskStatus] ?? key;
|
|
540
|
+
}
|
|
541
|
+
if (groupBy === "priority") {
|
|
542
|
+
// Group headers say "No priority" for the unprioritized bucket while the
|
|
543
|
+
// inspector keeps the "None" filter vocabulary — same key, different
|
|
544
|
+
// surface, so the shared label map is overridden only here.
|
|
545
|
+
return { ...PRIORITY_LABEL, none: "No priority" }[key as TaskPriority] ?? key;
|
|
546
|
+
}
|
|
547
|
+
return key;
|
|
555
548
|
}
|
|
556
549
|
|
|
557
550
|
// Real widths, because `resolveColumnWidths` derives the table's aggregate
|
|
@@ -568,587 +561,541 @@ function getGroupLabel(groupBy: GroupByField, key: string): string {
|
|
|
568
561
|
// Issue column both land on screen, and the remaining columns are one swipe
|
|
569
562
|
// away.
|
|
570
563
|
const columns: TableColumn<TaskRow>[] = [
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
564
|
+
{
|
|
565
|
+
key: "status",
|
|
566
|
+
header: "",
|
|
567
|
+
// StatusDot + the status word, which WCAG 1.4.1 requires as a channel
|
|
568
|
+
// beside the hue.
|
|
569
|
+
width: pixel(116),
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
key: "title",
|
|
573
|
+
header: "Issue",
|
|
574
|
+
// 2fr of the flexible pair: the issue id and title are what the reader
|
|
575
|
+
// scans the list for.
|
|
576
|
+
width: proportional(2, { minWidth: 280 }),
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
key: "project",
|
|
580
|
+
header: "Project",
|
|
581
|
+
width: proportional(1, { minWidth: 150 }),
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
key: "created",
|
|
585
|
+
header: "Created",
|
|
586
|
+
// 112, not 80: the header label measures 83px at label-semibold, and a
|
|
587
|
+
// header cell always truncates, so the floor has to clear the label and
|
|
588
|
+
// not just the "Jul 3" value underneath it.
|
|
589
|
+
width: pixel(112),
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
key: "updated",
|
|
593
|
+
header: "Updated",
|
|
594
|
+
width: pixel(112),
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
key: "assignee",
|
|
598
|
+
header: "Assignee",
|
|
599
|
+
// Avatar AND name. At 52px the column showed the avatar alone, which
|
|
600
|
+
// left the name reachable only in the inspector — and the inspector is
|
|
601
|
+
// gone below 1024. 168 clears the widest name in the data
|
|
602
|
+
// ("Isabella Nguyen", 108px of text) beside the 24px avatar, so it never
|
|
603
|
+
// breaks across two lines.
|
|
604
|
+
width: pixel(168),
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
key: "actions",
|
|
608
|
+
header: "",
|
|
609
|
+
width: pixel(56),
|
|
610
|
+
},
|
|
618
611
|
];
|
|
619
612
|
|
|
620
613
|
const fieldDefs = [
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
614
|
+
{
|
|
615
|
+
key: "status",
|
|
616
|
+
type: "enum",
|
|
617
|
+
label: "Status",
|
|
618
|
+
enumValues: [
|
|
619
|
+
{ value: "in_progress", label: "In Progress" },
|
|
620
|
+
{ value: "todo", label: "Todo" },
|
|
621
|
+
{ value: "backlog", label: "Backlog" },
|
|
622
|
+
{ value: "done", label: "Done" },
|
|
623
|
+
],
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
key: "priority",
|
|
627
|
+
type: "enum",
|
|
628
|
+
label: "Priority",
|
|
629
|
+
enumValues: [
|
|
630
|
+
{ value: "urgent", label: "Urgent" },
|
|
631
|
+
{ value: "high", label: "High" },
|
|
632
|
+
{ value: "medium", label: "Medium" },
|
|
633
|
+
{ value: "low", label: "Low" },
|
|
634
|
+
{ value: "none", label: "None" },
|
|
635
|
+
],
|
|
636
|
+
},
|
|
637
|
+
{ key: "title", type: "string", label: "Title" },
|
|
638
|
+
{ key: "assignee", type: "string", label: "Assignee" },
|
|
639
|
+
{ key: "project", type: "string", label: "Project" },
|
|
647
640
|
] as const;
|
|
648
641
|
|
|
649
642
|
const PRIORITY_LABEL: Record<TaskPriority, string> = {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
643
|
+
urgent: "Urgent",
|
|
644
|
+
high: "High",
|
|
645
|
+
medium: "Medium",
|
|
646
|
+
low: "Low",
|
|
647
|
+
none: "None",
|
|
655
648
|
};
|
|
656
649
|
|
|
657
650
|
function TaskDetailPanel({
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
651
|
+
task,
|
|
652
|
+
onClose,
|
|
653
|
+
resizable,
|
|
661
654
|
}: {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
655
|
+
task: TaskRow | null;
|
|
656
|
+
onClose: () => void;
|
|
657
|
+
resizable: ResizableProps;
|
|
665
658
|
}) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
659
|
+
if (!task) {
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
return (
|
|
663
|
+
// Panel owns the separator (its full-height left border). The adjacent
|
|
664
|
+
// ResizeHandle is kept divider-less + isAlwaysVisible={false} so its
|
|
665
|
+
// always-on pill doesn't float above the panel as a stray stub.
|
|
666
|
+
<LayoutPanel
|
|
667
|
+
hasDivider
|
|
668
|
+
resizable={resizable}
|
|
669
|
+
padding={4}
|
|
670
|
+
role="complementary"
|
|
671
|
+
label="Task details"
|
|
672
|
+
>
|
|
673
|
+
<VStack gap={4}>
|
|
674
|
+
<HStack gap={2} vAlign="center">
|
|
675
|
+
<StackItem size="fill">
|
|
676
|
+
<Text type="supporting" color="secondary">
|
|
677
|
+
{task.taskId}
|
|
678
|
+
</Text>
|
|
679
|
+
</StackItem>
|
|
680
|
+
<Button
|
|
681
|
+
label="Close panel"
|
|
682
|
+
variant="ghost"
|
|
683
|
+
size="sm"
|
|
684
|
+
icon={<Icon icon={X} size="sm" />}
|
|
685
|
+
isIconOnly
|
|
686
|
+
onClick={onClose}
|
|
687
|
+
/>
|
|
688
|
+
</HStack>
|
|
695
689
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
690
|
+
<VStack gap={1}>
|
|
691
|
+
<Heading level={2}>{task.title}</Heading>
|
|
692
|
+
{task.subtitle && (
|
|
693
|
+
<Text type="body" color="secondary">
|
|
694
|
+
{task.subtitle}
|
|
695
|
+
</Text>
|
|
696
|
+
)}
|
|
697
|
+
</VStack>
|
|
704
698
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
<MetadataListItem label="Updated">
|
|
745
|
-
<Text type="body">{task.updated}</Text>
|
|
746
|
-
</MetadataListItem>
|
|
747
|
-
</MetadataList>
|
|
699
|
+
<MetadataList label={{ position: "start" }}>
|
|
700
|
+
<MetadataListItem label="Status">
|
|
701
|
+
<HStack gap={1} vAlign="center">
|
|
702
|
+
<StatusDot
|
|
703
|
+
variant={STATUS_DOT_VARIANT[task.status]}
|
|
704
|
+
label={STATUS_LABEL[task.status]}
|
|
705
|
+
isPulsing={task.status === "in_progress"}
|
|
706
|
+
/>
|
|
707
|
+
<Text type="body">{STATUS_LABEL[task.status]}</Text>
|
|
708
|
+
</HStack>
|
|
709
|
+
</MetadataListItem>
|
|
710
|
+
<MetadataListItem label="Priority">
|
|
711
|
+
<HStack gap={2} vAlign="center">
|
|
712
|
+
<Icon icon={ChartBar} size="sm" color={PRIORITY_COLOR[task.priority]} />
|
|
713
|
+
<Text type="body">{PRIORITY_LABEL[task.priority]}</Text>
|
|
714
|
+
</HStack>
|
|
715
|
+
</MetadataListItem>
|
|
716
|
+
<MetadataListItem label="Assignee">
|
|
717
|
+
<HStack gap={2} vAlign="center">
|
|
718
|
+
<Avatar name={task.assignee} size="sm" />
|
|
719
|
+
<Text type="body">{task.assignee}</Text>
|
|
720
|
+
</HStack>
|
|
721
|
+
</MetadataListItem>
|
|
722
|
+
<MetadataListItem label="Project">
|
|
723
|
+
{task.project ? (
|
|
724
|
+
<Text type="body">{task.project}</Text>
|
|
725
|
+
) : (
|
|
726
|
+
<Text type="supporting" color="secondary">
|
|
727
|
+
None
|
|
728
|
+
</Text>
|
|
729
|
+
)}
|
|
730
|
+
</MetadataListItem>
|
|
731
|
+
<MetadataListItem label="Created">
|
|
732
|
+
<Text type="body">{task.created}</Text>
|
|
733
|
+
</MetadataListItem>
|
|
734
|
+
<MetadataListItem label="Updated">
|
|
735
|
+
<Text type="body">{task.updated}</Text>
|
|
736
|
+
</MetadataListItem>
|
|
737
|
+
</MetadataList>
|
|
748
738
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
739
|
+
{task.tags.length > 0 && (
|
|
740
|
+
<>
|
|
741
|
+
<Divider />
|
|
742
|
+
<VStack gap={2}>
|
|
743
|
+
<Text type="label">Labels</Text>
|
|
744
|
+
<HStack gap={2}>
|
|
745
|
+
{task.tags.map((tag) => (
|
|
746
|
+
<Token key={tag} color="yellow" label={tag} />
|
|
747
|
+
))}
|
|
748
|
+
</HStack>
|
|
749
|
+
</VStack>
|
|
750
|
+
</>
|
|
751
|
+
)}
|
|
752
|
+
</VStack>
|
|
753
|
+
</LayoutPanel>
|
|
754
|
+
);
|
|
765
755
|
}
|
|
766
756
|
|
|
767
757
|
export default function TableGrouped() {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
758
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
759
|
+
const [selectedTask, setSelectedTask] = useState<TaskRow | null>(null);
|
|
760
|
+
const [powerSearchFilters, setPowerSearchFilters] = useState<ReadonlyArray<PowerSearchFilter>>(
|
|
761
|
+
[],
|
|
762
|
+
);
|
|
763
|
+
const { config: powerSearchConfig, applyFilters } = usePowerSearchConfig(
|
|
764
|
+
fieldDefs,
|
|
765
|
+
"IssueSearch",
|
|
766
|
+
);
|
|
767
|
+
const [groupBy, setGroupBy] = useState<GroupByField>("status");
|
|
768
|
+
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
|
|
769
|
+
() => new Set(["in_progress", "todo", "backlog", "done"]),
|
|
770
|
+
);
|
|
781
771
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
772
|
+
// Responsive contract (see file header): below 1024px the inspector would
|
|
773
|
+
// squeeze the grouped table, so it steps aside entirely.
|
|
774
|
+
const isNarrow = useMediaQuery("(max-width: 1024px)");
|
|
785
775
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
776
|
+
const filtered = useMemo(() => {
|
|
777
|
+
return applyFilters(powerSearchFilters, allTasks);
|
|
778
|
+
}, [powerSearchFilters, applyFilters]);
|
|
789
779
|
|
|
790
|
-
|
|
791
|
-
() => groupTasks(filtered, groupBy),
|
|
792
|
-
[filtered, groupBy],
|
|
793
|
-
);
|
|
780
|
+
const grouped = useMemo(() => groupTasks(filtered, groupBy), [filtered, groupBy]);
|
|
794
781
|
|
|
795
|
-
|
|
782
|
+
const groupKeys = Array.from(grouped.keys());
|
|
796
783
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
784
|
+
// Expand every group whenever the grouping itself changes. Keyed on the
|
|
785
|
+
// memoized Map, not on groupKeys, which is a fresh array each render.
|
|
786
|
+
React.useEffect(() => {
|
|
787
|
+
setExpandedGroups(new Set(grouped.keys()));
|
|
788
|
+
}, [grouped]);
|
|
802
789
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
790
|
+
const toggleGroup = (key: string) => {
|
|
791
|
+
setExpandedGroups((prev) => {
|
|
792
|
+
const next = new Set(prev);
|
|
793
|
+
if (next.has(key)) {
|
|
794
|
+
next.delete(key);
|
|
795
|
+
} else {
|
|
796
|
+
next.add(key);
|
|
797
|
+
}
|
|
798
|
+
return next;
|
|
799
|
+
});
|
|
800
|
+
};
|
|
814
801
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
802
|
+
const detailPanel = useResizable({
|
|
803
|
+
defaultSize: 360,
|
|
804
|
+
minSize: 280,
|
|
805
|
+
maxSize: 500,
|
|
806
|
+
});
|
|
820
807
|
|
|
821
|
-
|
|
808
|
+
const resolvedWidths = resolveColumnWidths(columns);
|
|
822
809
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
810
|
+
return (
|
|
811
|
+
<>
|
|
812
|
+
<Layout
|
|
813
|
+
height="fill"
|
|
814
|
+
header={
|
|
815
|
+
<LayoutHeader hasDivider padding={6}>
|
|
816
|
+
<VStack gap={4}>
|
|
817
|
+
{/* wrap: below ~460px the Raise issue button drops to its own
|
|
831
818
|
line instead of starving the heading, so the display-2 title
|
|
832
819
|
keeps its full measure. The earlier maxLines={1} clipped it
|
|
833
820
|
to "Night-shift i…" at 390, and its reveal path was a
|
|
834
821
|
Tooltip — a hover affordance a phone never fires. Wrapping
|
|
835
822
|
breaks on the space between the two words, so no value is
|
|
836
823
|
lost and none is sliced mid-word (SC 1.4.10). */}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
key={col.key}
|
|
896
|
-
style={resolvedWidths.columns.get(col.key)?.style}
|
|
897
|
-
/>
|
|
898
|
-
))}
|
|
899
|
-
</colgroup>
|
|
900
|
-
{/* A scroller needs its columns named: once the reader has
|
|
824
|
+
<HStack gap={3} vAlign="center" wrap="wrap">
|
|
825
|
+
<StackItem size="fill">
|
|
826
|
+
<Heading level={1} type="display-2">
|
|
827
|
+
Night-shift issues
|
|
828
|
+
</Heading>
|
|
829
|
+
</StackItem>
|
|
830
|
+
<Button label="Raise issue" variant="primary" onClick={() => setDialogOpen(true)} />
|
|
831
|
+
</HStack>
|
|
832
|
+
<HStack gap={2} vAlign="center">
|
|
833
|
+
<StackItem size="fill">
|
|
834
|
+
<PowerSearch
|
|
835
|
+
config={powerSearchConfig}
|
|
836
|
+
filters={powerSearchFilters}
|
|
837
|
+
onChange={(newFilters) => setPowerSearchFilters(newFilters)}
|
|
838
|
+
placeholder="Filter issues…"
|
|
839
|
+
resultCount={`${filtered.length} issue${filtered.length !== 1 ? "s" : ""}`}
|
|
840
|
+
/>
|
|
841
|
+
</StackItem>
|
|
842
|
+
<Popover
|
|
843
|
+
placement="below"
|
|
844
|
+
alignment="end"
|
|
845
|
+
width="min(20rem, calc(100vw - 2 * var(--space-viewport)))"
|
|
846
|
+
label="Grouping options"
|
|
847
|
+
content={
|
|
848
|
+
<VStack gap={4}>
|
|
849
|
+
<RadioList
|
|
850
|
+
label="Group by"
|
|
851
|
+
value={groupBy}
|
|
852
|
+
onChange={(v) => setGroupBy(v as GroupByField)}
|
|
853
|
+
>
|
|
854
|
+
{GROUP_BY_OPTIONS.map((opt) => (
|
|
855
|
+
<RadioListItem key={opt.value} value={opt.value} label={opt.label} />
|
|
856
|
+
))}
|
|
857
|
+
</RadioList>
|
|
858
|
+
</VStack>
|
|
859
|
+
}
|
|
860
|
+
>
|
|
861
|
+
<Button label="View options" variant="secondary" size="md" />
|
|
862
|
+
</Popover>
|
|
863
|
+
</HStack>
|
|
864
|
+
</VStack>
|
|
865
|
+
</LayoutHeader>
|
|
866
|
+
}
|
|
867
|
+
content={
|
|
868
|
+
<LayoutContent role="main" padding={0}>
|
|
869
|
+
<Table
|
|
870
|
+
columns={columns}
|
|
871
|
+
density="balanced"
|
|
872
|
+
dividers="rows"
|
|
873
|
+
textOverflow="wrap"
|
|
874
|
+
hasHover
|
|
875
|
+
>
|
|
876
|
+
<colgroup>
|
|
877
|
+
{columns.map((col) => (
|
|
878
|
+
<col key={col.key} style={resolvedWidths.columns.get(col.key)?.style} />
|
|
879
|
+
))}
|
|
880
|
+
</colgroup>
|
|
881
|
+
{/* A scroller needs its columns named: once the reader has
|
|
901
882
|
scrolled sideways at 390, this row is the only thing that
|
|
902
883
|
says which value they are looking at. Rendered from
|
|
903
884
|
`columns` so the labels and the widths cannot drift, and as
|
|
904
885
|
real <th scope="col"> cells so the association reaches
|
|
905
886
|
assistive tech. */}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
887
|
+
<TableRow>
|
|
888
|
+
{columns.map((col) => (
|
|
889
|
+
<TableHeaderCell key={col.key} scope="col">
|
|
890
|
+
{col.header}
|
|
891
|
+
</TableHeaderCell>
|
|
892
|
+
))}
|
|
893
|
+
</TableRow>
|
|
894
|
+
{groupKeys.map((key) => {
|
|
895
|
+
const tasks = grouped.get(key);
|
|
896
|
+
if (!tasks || tasks.length === 0) {
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
const isExpanded = expandedGroups.has(key);
|
|
919
900
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
tasks.map(task => (
|
|
955
|
-
<TableRow
|
|
956
|
-
key={task.id}
|
|
957
|
-
onClick={() => setSelectedTask(task)}>
|
|
958
|
-
<TableCell>
|
|
959
|
-
{/* StatusDot paints nothing but an 8px dot — its
|
|
901
|
+
return (
|
|
902
|
+
<React.Fragment key={key}>
|
|
903
|
+
{groupBy !== "none" && (
|
|
904
|
+
<TableRow
|
|
905
|
+
role="button"
|
|
906
|
+
tabIndex={0}
|
|
907
|
+
onClick={() => toggleGroup(key)}
|
|
908
|
+
onKeyDown={(e) => {
|
|
909
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
910
|
+
e.preventDefault();
|
|
911
|
+
toggleGroup(key);
|
|
912
|
+
}
|
|
913
|
+
}}
|
|
914
|
+
>
|
|
915
|
+
<TableCell colSpan={columns.length} style={groupHeaderCell}>
|
|
916
|
+
<HStack gap={2} vAlign="center">
|
|
917
|
+
<Icon
|
|
918
|
+
icon={isExpanded ? ChevronDown : ChevronRight}
|
|
919
|
+
size="sm"
|
|
920
|
+
color="secondary"
|
|
921
|
+
/>
|
|
922
|
+
<Text type="body" weight="semibold">
|
|
923
|
+
{getGroupLabel(groupBy, key)}
|
|
924
|
+
</Text>
|
|
925
|
+
<Badge variant="neutral" label={String(tasks.length)} />
|
|
926
|
+
</HStack>
|
|
927
|
+
</TableCell>
|
|
928
|
+
</TableRow>
|
|
929
|
+
)}
|
|
930
|
+
{(groupBy === "none" || isExpanded) &&
|
|
931
|
+
tasks.map((task) => (
|
|
932
|
+
<TableRow key={task.id} onClick={() => setSelectedTask(task)}>
|
|
933
|
+
<TableCell>
|
|
934
|
+
{/* StatusDot paints nothing but an 8px dot — its
|
|
960
935
|
label is aria-label on a role="img" span — so
|
|
961
936
|
four states here read as hue alone. Same
|
|
962
937
|
dot-plus-word pairing as the Status entry in
|
|
963
938
|
TaskDetailPanel's MetadataList. */}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
color="secondary">
|
|
1020
|
-
{task.updated}
|
|
1021
|
-
</Text>
|
|
1022
|
-
</TableCell>
|
|
1023
|
-
<TableCell>
|
|
1024
|
-
{/* Name beside the avatar: below 1024 the
|
|
939
|
+
<HStack gap={1} vAlign="center">
|
|
940
|
+
<StatusDot
|
|
941
|
+
variant={STATUS_DOT_VARIANT[task.status]}
|
|
942
|
+
label={STATUS_LABEL[task.status]}
|
|
943
|
+
isPulsing={task.status === "in_progress"}
|
|
944
|
+
/>
|
|
945
|
+
<Text type="supporting" color="secondary">
|
|
946
|
+
{STATUS_LABEL[task.status]}
|
|
947
|
+
</Text>
|
|
948
|
+
</HStack>
|
|
949
|
+
</TableCell>
|
|
950
|
+
<TableCell>
|
|
951
|
+
<HStack gap={3} vAlign="center" wrap="wrap">
|
|
952
|
+
<Icon
|
|
953
|
+
icon={ChartBar}
|
|
954
|
+
size="sm"
|
|
955
|
+
color={PRIORITY_COLOR[task.priority]}
|
|
956
|
+
/>
|
|
957
|
+
<Text type="supporting" color="secondary">
|
|
958
|
+
{task.taskId}
|
|
959
|
+
</Text>
|
|
960
|
+
<StackItem size="fill">
|
|
961
|
+
<Text type="body">
|
|
962
|
+
{task.title}
|
|
963
|
+
{task.subtitle && (
|
|
964
|
+
<Text type="inherit" color="secondary">
|
|
965
|
+
{" › "}
|
|
966
|
+
{task.subtitle}
|
|
967
|
+
</Text>
|
|
968
|
+
)}
|
|
969
|
+
</Text>
|
|
970
|
+
</StackItem>
|
|
971
|
+
</HStack>
|
|
972
|
+
</TableCell>
|
|
973
|
+
<TableCell>
|
|
974
|
+
{task.project ? (
|
|
975
|
+
<Text type="body">{task.project}</Text>
|
|
976
|
+
) : (
|
|
977
|
+
<Text type="supporting" color="secondary">
|
|
978
|
+
None
|
|
979
|
+
</Text>
|
|
980
|
+
)}
|
|
981
|
+
</TableCell>
|
|
982
|
+
<TableCell>
|
|
983
|
+
<Text type="supporting" color="secondary">
|
|
984
|
+
{task.created}
|
|
985
|
+
</Text>
|
|
986
|
+
</TableCell>
|
|
987
|
+
<TableCell>
|
|
988
|
+
<Text type="supporting" color="secondary">
|
|
989
|
+
{task.updated}
|
|
990
|
+
</Text>
|
|
991
|
+
</TableCell>
|
|
992
|
+
<TableCell>
|
|
993
|
+
{/* Name beside the avatar: below 1024 the
|
|
1025
994
|
inspector is gone, so the avatar alone would
|
|
1026
995
|
leave the assignee readable by screen reader
|
|
1027
996
|
but not by eye. */}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
footer={
|
|
1133
|
-
<LayoutFooter hasDivider>
|
|
1134
|
-
<HStack gap={2} hAlign="end">
|
|
1135
|
-
<Button
|
|
1136
|
-
label="Cancel"
|
|
1137
|
-
variant="secondary"
|
|
1138
|
-
size="md"
|
|
1139
|
-
onClick={() => setDialogOpen(false)}
|
|
1140
|
-
/>
|
|
1141
|
-
<Button
|
|
1142
|
-
label="Raise issue"
|
|
1143
|
-
variant="primary"
|
|
1144
|
-
size="md"
|
|
1145
|
-
onClick={() => setDialogOpen(false)}
|
|
1146
|
-
/>
|
|
1147
|
-
</HStack>
|
|
1148
|
-
</LayoutFooter>
|
|
1149
|
-
}
|
|
1150
|
-
/>
|
|
1151
|
-
</Dialog>
|
|
1152
|
-
</>
|
|
1153
|
-
);
|
|
997
|
+
<HStack gap={2} vAlign="center">
|
|
998
|
+
<Avatar name={task.assignee} size="sm" />
|
|
999
|
+
<Text type="supporting" color="secondary">
|
|
1000
|
+
{task.assignee}
|
|
1001
|
+
</Text>
|
|
1002
|
+
</HStack>
|
|
1003
|
+
</TableCell>
|
|
1004
|
+
<TableCell>
|
|
1005
|
+
<DropdownMenu
|
|
1006
|
+
button={{
|
|
1007
|
+
label: "Actions",
|
|
1008
|
+
variant: "ghost",
|
|
1009
|
+
size: "sm",
|
|
1010
|
+
icon: <Icon icon={Ellipsis} size="sm" />,
|
|
1011
|
+
isIconOnly: true,
|
|
1012
|
+
}}
|
|
1013
|
+
hasChevron={false}
|
|
1014
|
+
items={[
|
|
1015
|
+
{ label: "Edit issue", icon: Pencil },
|
|
1016
|
+
{ label: "Assign to...", icon: User },
|
|
1017
|
+
{ label: "Add label", icon: Tag },
|
|
1018
|
+
{ label: "Duplicate", icon: Copy },
|
|
1019
|
+
{ label: "Move to project", icon: ArrowRight },
|
|
1020
|
+
{ type: "divider" as const },
|
|
1021
|
+
{ label: "Delete issue", icon: Trash },
|
|
1022
|
+
]}
|
|
1023
|
+
/>
|
|
1024
|
+
</TableCell>
|
|
1025
|
+
</TableRow>
|
|
1026
|
+
))}
|
|
1027
|
+
</React.Fragment>
|
|
1028
|
+
);
|
|
1029
|
+
})}
|
|
1030
|
+
</Table>
|
|
1031
|
+
</LayoutContent>
|
|
1032
|
+
}
|
|
1033
|
+
end={
|
|
1034
|
+
!isNarrow &&
|
|
1035
|
+
selectedTask && (
|
|
1036
|
+
<>
|
|
1037
|
+
<ResizeHandle resizable={detailPanel.props} isReversed isAlwaysVisible={false} />
|
|
1038
|
+
<TaskDetailPanel
|
|
1039
|
+
task={selectedTask}
|
|
1040
|
+
onClose={() => setSelectedTask(null)}
|
|
1041
|
+
resizable={detailPanel.props}
|
|
1042
|
+
/>
|
|
1043
|
+
</>
|
|
1044
|
+
)
|
|
1045
|
+
}
|
|
1046
|
+
/>
|
|
1047
|
+
<Dialog isOpen={dialogOpen} onOpenChange={(open) => setDialogOpen(open)}>
|
|
1048
|
+
<Layout
|
|
1049
|
+
height="auto"
|
|
1050
|
+
header={<DialogHeader title="Raise issue" onOpenChange={(open) => setDialogOpen(open)} />}
|
|
1051
|
+
content={
|
|
1052
|
+
<LayoutContent padding={4}>
|
|
1053
|
+
<VStack gap={4}>
|
|
1054
|
+
<TextInput label="Title" placeholder="Issue title" value="" onChange={() => {}} />
|
|
1055
|
+
<Selector
|
|
1056
|
+
label="Status"
|
|
1057
|
+
value="todo"
|
|
1058
|
+
options={[
|
|
1059
|
+
{ value: "in_progress", label: "In Progress" },
|
|
1060
|
+
{ value: "todo", label: "Todo" },
|
|
1061
|
+
{ value: "backlog", label: "Backlog" },
|
|
1062
|
+
]}
|
|
1063
|
+
/>
|
|
1064
|
+
<Selector
|
|
1065
|
+
label="Priority"
|
|
1066
|
+
value="none"
|
|
1067
|
+
options={[
|
|
1068
|
+
{ value: "urgent", label: "Urgent" },
|
|
1069
|
+
{ value: "high", label: "High" },
|
|
1070
|
+
{ value: "medium", label: "Medium" },
|
|
1071
|
+
{ value: "low", label: "Low" },
|
|
1072
|
+
{ value: "none", label: "No priority" },
|
|
1073
|
+
]}
|
|
1074
|
+
/>
|
|
1075
|
+
<TextInput label="Project" placeholder="Project name" value="" />
|
|
1076
|
+
</VStack>
|
|
1077
|
+
</LayoutContent>
|
|
1078
|
+
}
|
|
1079
|
+
footer={
|
|
1080
|
+
<LayoutFooter hasDivider>
|
|
1081
|
+
<HStack gap={2} hAlign="end">
|
|
1082
|
+
<Button
|
|
1083
|
+
label="Cancel"
|
|
1084
|
+
variant="secondary"
|
|
1085
|
+
size="md"
|
|
1086
|
+
onClick={() => setDialogOpen(false)}
|
|
1087
|
+
/>
|
|
1088
|
+
<Button
|
|
1089
|
+
label="Raise issue"
|
|
1090
|
+
variant="primary"
|
|
1091
|
+
size="md"
|
|
1092
|
+
onClick={() => setDialogOpen(false)}
|
|
1093
|
+
/>
|
|
1094
|
+
</HStack>
|
|
1095
|
+
</LayoutFooter>
|
|
1096
|
+
}
|
|
1097
|
+
/>
|
|
1098
|
+
</Dialog>
|
|
1099
|
+
</>
|
|
1100
|
+
);
|
|
1154
1101
|
}
|