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