@exxatdesignux/ui 0.2.14 → 0.2.15
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/components/ui/dropdown-menu.tsx +2 -0
- package/src/components/ui/popover.tsx +2 -2
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tooltip.tsx +7 -1
- package/src/globals.css +6 -0
- package/template/app/globals.css +5 -0
- package/template/components/compliance-board-view.tsx +11 -3
- package/template/components/compliance-list-view.tsx +16 -3
- package/template/components/compliance-table.tsx +5 -1
- package/template/components/data-table/index.tsx +25 -11
- package/template/components/question-bank-board-view.tsx +11 -2
- package/template/components/question-bank-list-view.tsx +12 -1
- package/template/components/question-bank-table.tsx +6 -1
- package/template/components/team-board-view.tsx +11 -3
- package/template/components/team-list-view.tsx +16 -3
- package/template/components/team-table.tsx +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,14 @@ After the user bumps `@exxatdesignux/ui`, do this in order:
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
## [0.2.15] - 2026-05-13
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Globals**: `button` / `[role="button"]` **`cursor: pointer`** is applied **outside** `@layer base` (after utilities) so it wins over Tailwind preflight; filter bar and native controls show a hand cursor consistently.
|
|
23
|
+
- **Primitives**: **`DropdownMenuTrigger`**, **`PopoverTrigger`**, and **`TooltipTrigger`** merge **`cursor-pointer`** into **`className`**; **`SelectTrigger`** includes **`cursor-pointer`** (with **`disabled:cursor-not-allowed`**).
|
|
24
|
+
- **Starter / app parity**: `sync-template` brings **`DataTable`** filter bar **`cursor-pointer`** and list-hub table/list/board affordance updates into **`template/`**.
|
|
25
|
+
|
|
18
26
|
## [0.2.14] - 2026-05-14
|
|
19
27
|
|
|
20
28
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exxatdesignux/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "Exxat shared design system (components, hooks, tokens). Monorepo setup: clone repo then pnpm bootstrap at workspace root — see github.com/ExxatDesign/Exxat-DS-Workspace README.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -21,12 +21,14 @@ function DropdownMenuPortal({
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function DropdownMenuTrigger({
|
|
24
|
+
className,
|
|
24
25
|
...props
|
|
25
26
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
|
26
27
|
return (
|
|
27
28
|
<DropdownMenuPrimitive.Trigger
|
|
28
29
|
data-slot="dropdown-menu-trigger"
|
|
29
30
|
suppressHydrationWarning
|
|
31
|
+
className={cn("cursor-pointer", className)}
|
|
30
32
|
{...props}
|
|
31
33
|
/>
|
|
32
34
|
)
|
|
@@ -8,8 +8,8 @@ function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root
|
|
|
8
8
|
return <PopoverPrimitive.Root {...props} />
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
12
|
-
return <PopoverPrimitive.Trigger {...props} />
|
|
11
|
+
function PopoverTrigger({ className, ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
|
12
|
+
return <PopoverPrimitive.Trigger className={cn("cursor-pointer", className)} {...props} />
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
|
@@ -43,7 +43,7 @@ function SelectTrigger({
|
|
|
43
43
|
data-slot="select-trigger"
|
|
44
44
|
data-size={size}
|
|
45
45
|
className={cn(
|
|
46
|
-
"flex w-fit items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pe-2 ps-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/15 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
46
|
+
"flex w-fit cursor-pointer items-center justify-between gap-1.5 rounded-md border border-input bg-transparent py-2 pe-2 ps-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/15 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
47
47
|
className
|
|
48
48
|
)}
|
|
49
49
|
{...props}
|
|
@@ -25,10 +25,16 @@ function Tooltip({
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function TooltipTrigger({
|
|
28
|
+
className,
|
|
28
29
|
...props
|
|
29
30
|
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
31
|
return (
|
|
31
|
-
<TooltipPrimitive.Trigger
|
|
32
|
+
<TooltipPrimitive.Trigger
|
|
33
|
+
data-slot="tooltip-trigger"
|
|
34
|
+
suppressHydrationWarning
|
|
35
|
+
className={cn("cursor-pointer", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
32
38
|
)
|
|
33
39
|
}
|
|
34
40
|
|
package/src/globals.css
CHANGED
|
@@ -1792,3 +1792,9 @@ html:is([data-contrast="high"], [data-contrast="windows"]) {
|
|
|
1792
1792
|
background-color: var(--banner-prism-bg);
|
|
1793
1793
|
}
|
|
1794
1794
|
}
|
|
1795
|
+
|
|
1796
|
+
/* After all @layer rules: win over Tailwind preflight / layer merge so real controls show a pointer. */
|
|
1797
|
+
button:not(:disabled):not([disabled]),
|
|
1798
|
+
[role="button"]:not([aria-disabled="true"]):not([data-disabled]) {
|
|
1799
|
+
cursor: pointer;
|
|
1800
|
+
}
|
package/template/app/globals.css
CHANGED
|
@@ -1808,3 +1808,8 @@ html:is([data-contrast="high"], [data-contrast="windows"]) [data-slot="coach-mar
|
|
|
1808
1808
|
}
|
|
1809
1809
|
}
|
|
1810
1810
|
|
|
1811
|
+
/* After all @layer rules: win over Tailwind preflight / layer merge so real controls show a pointer. */
|
|
1812
|
+
button:not(:disabled):not([disabled]),
|
|
1813
|
+
[role="button"]:not([aria-disabled="true"]):not([data-disabled]) {
|
|
1814
|
+
cursor: pointer;
|
|
1815
|
+
}
|
|
@@ -79,10 +79,16 @@ function useComplianceBoardModel(rows: ComplianceItem[], groupByColumnKey: strin
|
|
|
79
79
|
}, [rows, groupByColumnKey])
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function ComplianceBoardCard({
|
|
82
|
+
function ComplianceBoardCard({
|
|
83
|
+
row,
|
|
84
|
+
onRowActivate,
|
|
85
|
+
}: {
|
|
86
|
+
row: ComplianceItem
|
|
87
|
+
onRowActivate?: (row: ComplianceItem) => void
|
|
88
|
+
}) {
|
|
83
89
|
const ownerInitials = initialsFromDisplayName(row.owner)
|
|
84
90
|
return (
|
|
85
|
-
<ListPageBoardCard className="w-full">
|
|
91
|
+
<ListPageBoardCard className="w-full" onClick={onRowActivate ? () => onRowActivate(row) : undefined}>
|
|
86
92
|
<ListPageBoardCardHeader>
|
|
87
93
|
<ListPageBoardCardTitleRow
|
|
88
94
|
title={row.title}
|
|
@@ -114,9 +120,11 @@ export const COMPLIANCE_BOARD_GROUP_OPTIONS = [
|
|
|
114
120
|
export function ComplianceBoardView({
|
|
115
121
|
rows,
|
|
116
122
|
groupByColumnKey,
|
|
123
|
+
onRowActivate,
|
|
117
124
|
}: {
|
|
118
125
|
rows: ComplianceItem[]
|
|
119
126
|
groupByColumnKey: string
|
|
127
|
+
onRowActivate?: (row: ComplianceItem) => void
|
|
120
128
|
}) {
|
|
121
129
|
const key = groupByColumnKey === "category" ? "category" : "status"
|
|
122
130
|
const { columns, badgeMap } = useComplianceBoardModel(rows, key)
|
|
@@ -128,7 +136,7 @@ export function ComplianceBoardView({
|
|
|
128
136
|
getRowKey={r => r.id}
|
|
129
137
|
columnCountBadgeClassName={badgeMap}
|
|
130
138
|
emptyColumnLabel="No items"
|
|
131
|
-
renderCard={row => <ComplianceBoardCard row={row} />}
|
|
139
|
+
renderCard={row => <ComplianceBoardCard row={row} onRowActivate={onRowActivate} />}
|
|
132
140
|
/>
|
|
133
141
|
)
|
|
134
142
|
}
|
|
@@ -10,12 +10,19 @@ import {
|
|
|
10
10
|
} from "@/lib/list-status-badges"
|
|
11
11
|
import type { ComplianceItem } from "@/lib/mock/compliance"
|
|
12
12
|
|
|
13
|
-
function ComplianceListRow({
|
|
13
|
+
function ComplianceListRow({
|
|
14
|
+
row,
|
|
15
|
+
onRowActivate,
|
|
16
|
+
}: {
|
|
17
|
+
row: ComplianceItem
|
|
18
|
+
onRowActivate?: (row: ComplianceItem) => void
|
|
19
|
+
}) {
|
|
14
20
|
return (
|
|
15
21
|
<li>
|
|
16
22
|
<ListPageBoardCard
|
|
17
23
|
layout="row"
|
|
18
24
|
rowContainerClassName="flex w-full flex-col gap-1 sm:flex-row sm:items-center sm:gap-4"
|
|
25
|
+
onClick={onRowActivate ? () => onRowActivate(row) : undefined}
|
|
19
26
|
rowEnd={
|
|
20
27
|
<div className="flex shrink-0 items-center gap-2">
|
|
21
28
|
<ListHubStatusBadge
|
|
@@ -40,7 +47,13 @@ function ComplianceListRow({ row }: { row: ComplianceItem }) {
|
|
|
40
47
|
)
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
export function ComplianceListView({
|
|
50
|
+
export function ComplianceListView({
|
|
51
|
+
rows,
|
|
52
|
+
onRowActivate,
|
|
53
|
+
}: {
|
|
54
|
+
rows: ComplianceItem[]
|
|
55
|
+
onRowActivate?: (row: ComplianceItem) => void
|
|
56
|
+
}) {
|
|
44
57
|
if (rows.length === 0) {
|
|
45
58
|
return (
|
|
46
59
|
<div className="px-4 py-16 text-center lg:px-6">
|
|
@@ -52,7 +65,7 @@ export function ComplianceListView({ rows }: { rows: ComplianceItem[] }) {
|
|
|
52
65
|
return (
|
|
53
66
|
<ul className="flex list-none flex-col gap-2 px-4 pb-8 pt-2 lg:px-6">
|
|
54
67
|
{rows.map(row => (
|
|
55
|
-
<ComplianceListRow key={row.id} row={row} />
|
|
68
|
+
<ComplianceListRow key={row.id} row={row} onRowActivate={onRowActivate} />
|
|
56
69
|
))}
|
|
57
70
|
</ul>
|
|
58
71
|
)
|
|
@@ -509,7 +509,10 @@ export const ComplianceTable = React.forwardRef<
|
|
|
509
509
|
return (
|
|
510
510
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
511
511
|
{sharedToolbar}
|
|
512
|
-
<ComplianceListView
|
|
512
|
+
<ComplianceListView
|
|
513
|
+
rows={tableState.rows as ComplianceItem[]}
|
|
514
|
+
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
515
|
+
/>
|
|
513
516
|
</div>
|
|
514
517
|
)
|
|
515
518
|
}
|
|
@@ -521,6 +524,7 @@ export const ComplianceTable = React.forwardRef<
|
|
|
521
524
|
<ComplianceBoardView
|
|
522
525
|
rows={tableState.rows as ComplianceItem[]}
|
|
523
526
|
groupByColumnKey={complianceBoardGroupKey}
|
|
527
|
+
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
524
528
|
/>
|
|
525
529
|
</div>
|
|
526
530
|
)
|
|
@@ -214,7 +214,7 @@ function FilterPillBase<TData>({
|
|
|
214
214
|
<PopoverAnchor asChild>
|
|
215
215
|
<div
|
|
216
216
|
className={cn(
|
|
217
|
-
"inline-flex items-center rounded border text-xs transition-colors",
|
|
217
|
+
"inline-flex cursor-pointer items-center rounded border text-xs transition-colors",
|
|
218
218
|
isActive ? "border-brand/45 bg-brand/10" : "border-input bg-background"
|
|
219
219
|
)}
|
|
220
220
|
>
|
|
@@ -222,7 +222,7 @@ function FilterPillBase<TData>({
|
|
|
222
222
|
<button
|
|
223
223
|
type="button"
|
|
224
224
|
className={cn(
|
|
225
|
-
"inline-flex items-center gap-1 h-6 pl-2 pr-1.5 rounded-l transition-colors",
|
|
225
|
+
"inline-flex cursor-pointer items-center gap-1 h-6 pl-2 pr-1.5 rounded-l transition-colors",
|
|
226
226
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
227
227
|
isActive ? "hover:bg-brand/15" : "hover:bg-interactive-hover",
|
|
228
228
|
)}
|
|
@@ -240,7 +240,7 @@ function FilterPillBase<TData>({
|
|
|
240
240
|
aria-label={`Remove ${col.label} filter`}
|
|
241
241
|
onClick={() => onRemove(filter.id)}
|
|
242
242
|
className={cn(
|
|
243
|
-
"inline-flex items-center justify-center h-6 w-5 rounded-r transition-colors",
|
|
243
|
+
"inline-flex cursor-pointer items-center justify-center h-6 w-5 rounded-r transition-colors",
|
|
244
244
|
"text-muted-foreground hover:text-destructive",
|
|
245
245
|
isActive ? "hover:bg-brand/15" : "hover:bg-interactive-hover",
|
|
246
246
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
|
|
@@ -496,7 +496,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
496
496
|
<DropdownMenu>
|
|
497
497
|
<DropdownMenuTrigger asChild>
|
|
498
498
|
<button type="button"
|
|
499
|
-
className="inline-flex items-center gap-1 h-6 px-2 rounded text-xs text-muted-foreground hover:text-interactive-hover-foreground border border-dashed border-input/70 hover:border-input hover:bg-interactive-hover-subtle transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
499
|
+
className="inline-flex cursor-pointer items-center gap-1 h-6 px-2 rounded text-xs text-muted-foreground hover:text-interactive-hover-foreground border border-dashed border-input/70 hover:border-input hover:bg-interactive-hover-subtle transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
500
500
|
>
|
|
501
501
|
<i className="fa-light fa-plus text-xs" aria-hidden="true" />
|
|
502
502
|
Add filter
|
|
@@ -518,7 +518,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
518
518
|
<button
|
|
519
519
|
type="button"
|
|
520
520
|
onClick={() => setActiveFilters([])}
|
|
521
|
-
className="text-xs text-muted-foreground hover:text-interactive-hover-foreground transition-colors px-1"
|
|
521
|
+
className="cursor-pointer text-xs text-muted-foreground hover:text-interactive-hover-foreground transition-colors px-1"
|
|
522
522
|
>
|
|
523
523
|
Clear all
|
|
524
524
|
</button>
|
|
@@ -556,7 +556,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
556
556
|
type="button"
|
|
557
557
|
aria-label="Clear search"
|
|
558
558
|
onClick={() => setSearch("")}
|
|
559
|
-
className="absolute right-1.5 top-1/2 -translate-y-1/2 inline-flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:text-interactive-hover-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
559
|
+
className="absolute right-1.5 top-1/2 -translate-y-1/2 inline-flex cursor-pointer size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:text-interactive-hover-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
560
560
|
>
|
|
561
561
|
<i className="fa-light fa-xmark text-xs" aria-hidden="true" />
|
|
562
562
|
</button>
|
|
@@ -568,7 +568,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
568
568
|
<TooltipTrigger asChild>
|
|
569
569
|
<button type="button" aria-label="Search"
|
|
570
570
|
onClick={() => { setSearchOpen(true); setTimeout(() => searchRef.current?.focus(), 10) }}
|
|
571
|
-
className="inline-flex shrink-0 items-center justify-center size-8 rounded-md text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
571
|
+
className="inline-flex shrink-0 cursor-pointer items-center justify-center size-8 rounded-md text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
572
572
|
>
|
|
573
573
|
<i className="fa-light fa-magnifying-glass text-[13px]" aria-hidden="true" />
|
|
574
574
|
</button>
|
|
@@ -596,7 +596,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
596
596
|
aria-label={filterBarVisible ? "Hide filters" : "Show filters"}
|
|
597
597
|
onClick={() => setFilterBarVisible(v => !v)}
|
|
598
598
|
className={cn(
|
|
599
|
-
"inline-flex shrink-0 items-center gap-1 size-8 justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
599
|
+
"inline-flex shrink-0 cursor-pointer items-center gap-1 size-8 justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
600
600
|
filterBarVisible
|
|
601
601
|
? "bg-accent text-accent-foreground hover:bg-accent/90"
|
|
602
602
|
: "text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover",
|
|
@@ -610,7 +610,7 @@ export function DataTableToolbar<TData extends Record<string, unknown>>({
|
|
|
610
610
|
<DropdownMenuTrigger asChild>
|
|
611
611
|
<button type="button" aria-label="Add filter"
|
|
612
612
|
onClick={() => setFilterBarVisible(true)}
|
|
613
|
-
className="inline-flex shrink-0 items-center justify-center size-8 rounded-md text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
613
|
+
className="inline-flex shrink-0 cursor-pointer items-center justify-center size-8 rounded-md text-muted-foreground hover:text-interactive-hover-foreground hover:bg-interactive-hover transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
614
614
|
>
|
|
615
615
|
<i className="fa-light fa-filter text-[13px]" aria-hidden="true" />
|
|
616
616
|
</button>
|
|
@@ -1325,19 +1325,33 @@ function DataTableInner<TData extends Record<string, unknown>>({
|
|
|
1325
1325
|
const rowId = getRowId(row, rowIndex, getRowIdProp)
|
|
1326
1326
|
const isSelected = selected.has(rowId)
|
|
1327
1327
|
const isHovered = hoveredRow === rowId
|
|
1328
|
+
const rowClickable = Boolean(onRowClick) || selectable
|
|
1329
|
+
function handleRowClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
|
1330
|
+
if (!rowClickable) return
|
|
1331
|
+
const el = e.target as HTMLElement | null
|
|
1332
|
+
if (!el) return
|
|
1333
|
+
if (el.closest("button, a, input, textarea, select, label, [role='checkbox']")) return
|
|
1334
|
+
if (onRowClick) {
|
|
1335
|
+
onRowClick(row)
|
|
1336
|
+
return
|
|
1337
|
+
}
|
|
1338
|
+
if (selectable) {
|
|
1339
|
+
toggleRow(rowId)
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1328
1342
|
return (
|
|
1329
1343
|
<tr
|
|
1330
1344
|
key={String(rowId)}
|
|
1331
1345
|
data-state={isSelected ? "selected" : undefined}
|
|
1332
1346
|
onMouseEnter={() => setHoveredRow(rowId)}
|
|
1333
1347
|
onMouseLeave={() => setHoveredRow(null)}
|
|
1334
|
-
onClick={
|
|
1348
|
+
onClick={rowClickable ? handleRowClick : undefined}
|
|
1335
1349
|
data-new={Boolean((row as Record<string, unknown>).isNew) || undefined}
|
|
1336
1350
|
className={cn(
|
|
1337
1351
|
"group/row transition-colors",
|
|
1338
1352
|
"hover:bg-dt-row-hover",
|
|
1339
1353
|
isSelected && "bg-dt-row-selected text-dt-row-selected-fg",
|
|
1340
|
-
|
|
1354
|
+
rowClickable && "cursor-pointer",
|
|
1341
1355
|
Boolean((row as Record<string, unknown>).isNew) && "bg-dt-new-row-bg border-l-2 border-l-dt-new-row-border"
|
|
1342
1356
|
)}
|
|
1343
1357
|
>
|
|
@@ -124,13 +124,18 @@ function useQuestionBankBoardModel(rows: QuestionBankItem[], groupByColumnKey: s
|
|
|
124
124
|
function QuestionBankBoardCard({
|
|
125
125
|
row,
|
|
126
126
|
onToggleFavorite,
|
|
127
|
+
onRowActivate,
|
|
127
128
|
}: {
|
|
128
129
|
row: QuestionBankItem
|
|
129
130
|
onToggleFavorite: (row: QuestionBankItem) => void
|
|
131
|
+
onRowActivate?: (row: QuestionBankItem) => void
|
|
130
132
|
}) {
|
|
131
133
|
const initials = initialsFromDisplayName(row.author)
|
|
132
134
|
return (
|
|
133
|
-
<ListPageBoardCard
|
|
135
|
+
<ListPageBoardCard
|
|
136
|
+
className={cn(QUESTION_BANK_FAVORITE_HOVER_GROUP, "w-full")}
|
|
137
|
+
onClick={onRowActivate ? () => onRowActivate(row) : undefined}
|
|
138
|
+
>
|
|
134
139
|
<ListPageBoardCardHeader>
|
|
135
140
|
<ListPageBoardCardTitleRow
|
|
136
141
|
title={(
|
|
@@ -171,10 +176,12 @@ export function QuestionBankBoardView({
|
|
|
171
176
|
rows,
|
|
172
177
|
groupByColumnKey,
|
|
173
178
|
onToggleFavorite,
|
|
179
|
+
onRowActivate,
|
|
174
180
|
}: {
|
|
175
181
|
rows: QuestionBankItem[]
|
|
176
182
|
groupByColumnKey: string
|
|
177
183
|
onToggleFavorite: (row: QuestionBankItem) => void
|
|
184
|
+
onRowActivate?: (row: QuestionBankItem) => void
|
|
178
185
|
}) {
|
|
179
186
|
const allowed = QUESTION_BANK_BOARD_GROUP_OPTIONS.some(o => o.key === groupByColumnKey)
|
|
180
187
|
const key = allowed ? groupByColumnKey : "topic"
|
|
@@ -187,7 +194,9 @@ export function QuestionBankBoardView({
|
|
|
187
194
|
getRowKey={r => r.id}
|
|
188
195
|
columnCountBadgeClassName={badgeMap}
|
|
189
196
|
emptyColumnLabel="No questions"
|
|
190
|
-
renderCard={row =>
|
|
197
|
+
renderCard={row => (
|
|
198
|
+
<QuestionBankBoardCard row={row} onToggleFavorite={onToggleFavorite} onRowActivate={onRowActivate} />
|
|
199
|
+
)}
|
|
191
200
|
/>
|
|
192
201
|
)
|
|
193
202
|
}
|
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
function QuestionBankListRow({
|
|
12
12
|
row,
|
|
13
13
|
onToggleFavorite,
|
|
14
|
+
onRowActivate,
|
|
14
15
|
}: {
|
|
15
16
|
row: QuestionBankItem
|
|
16
17
|
onToggleFavorite: (row: QuestionBankItem) => void
|
|
18
|
+
onRowActivate?: (row: QuestionBankItem) => void
|
|
17
19
|
}) {
|
|
18
20
|
return (
|
|
19
21
|
<li>
|
|
@@ -21,6 +23,7 @@ function QuestionBankListRow({
|
|
|
21
23
|
className={QUESTION_BANK_FAVORITE_HOVER_GROUP}
|
|
22
24
|
layout="row"
|
|
23
25
|
rowContainerClassName="flex w-full flex-col gap-1 sm:flex-row sm:items-center sm:gap-4"
|
|
26
|
+
onClick={onRowActivate ? () => onRowActivate(row) : undefined}
|
|
24
27
|
rowEnd={(
|
|
25
28
|
<div className="flex shrink-0 items-center gap-1">
|
|
26
29
|
<QuestionBankFavoriteButton row={row} onToggleFavorite={onToggleFavorite} />
|
|
@@ -44,9 +47,12 @@ function QuestionBankListRow({
|
|
|
44
47
|
export function QuestionBankListView({
|
|
45
48
|
rows,
|
|
46
49
|
onToggleFavorite,
|
|
50
|
+
onRowActivate,
|
|
47
51
|
}: {
|
|
48
52
|
rows: QuestionBankItem[]
|
|
49
53
|
onToggleFavorite: (row: QuestionBankItem) => void
|
|
54
|
+
/** When set (e.g. table selection), clicking a row toggles the same selection as the grid. */
|
|
55
|
+
onRowActivate?: (row: QuestionBankItem) => void
|
|
50
56
|
}) {
|
|
51
57
|
if (rows.length === 0) {
|
|
52
58
|
return (
|
|
@@ -59,7 +65,12 @@ export function QuestionBankListView({
|
|
|
59
65
|
return (
|
|
60
66
|
<ul className="flex list-none flex-col gap-2 px-4 pb-8 pt-2 lg:px-6">
|
|
61
67
|
{rows.map(row => (
|
|
62
|
-
<QuestionBankListRow
|
|
68
|
+
<QuestionBankListRow
|
|
69
|
+
key={row.id}
|
|
70
|
+
row={row}
|
|
71
|
+
onToggleFavorite={onToggleFavorite}
|
|
72
|
+
onRowActivate={onRowActivate}
|
|
73
|
+
/>
|
|
63
74
|
))}
|
|
64
75
|
</ul>
|
|
65
76
|
)
|
|
@@ -898,7 +898,11 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
898
898
|
return (
|
|
899
899
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
900
900
|
{sharedToolbar}
|
|
901
|
-
<QuestionBankListView
|
|
901
|
+
<QuestionBankListView
|
|
902
|
+
rows={tableState.rows as QuestionBankItem[]}
|
|
903
|
+
onToggleFavorite={toggleFavorite}
|
|
904
|
+
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
905
|
+
/>
|
|
902
906
|
</div>
|
|
903
907
|
)
|
|
904
908
|
}
|
|
@@ -911,6 +915,7 @@ export const QuestionBankTable = React.forwardRef<
|
|
|
911
915
|
rows={tableState.rows as QuestionBankItem[]}
|
|
912
916
|
groupByColumnKey={questionBankBoardGroupKey}
|
|
913
917
|
onToggleFavorite={toggleFavorite}
|
|
918
|
+
onRowActivate={row => tableState.toggleRow(row.id)}
|
|
914
919
|
/>
|
|
915
920
|
</div>
|
|
916
921
|
)
|
|
@@ -61,9 +61,15 @@ function useTeamBoardModel(members: TeamMember[], groupByColumnKey: string) {
|
|
|
61
61
|
}, [members, groupByColumnKey])
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function TeamBoardCard({
|
|
64
|
+
function TeamBoardCard({
|
|
65
|
+
member,
|
|
66
|
+
onRowActivate,
|
|
67
|
+
}: {
|
|
68
|
+
member: TeamMember
|
|
69
|
+
onRowActivate?: (member: TeamMember) => void
|
|
70
|
+
}) {
|
|
65
71
|
return (
|
|
66
|
-
<ListPageBoardCard className="w-full">
|
|
72
|
+
<ListPageBoardCard className="w-full" onClick={onRowActivate ? () => onRowActivate(member) : undefined}>
|
|
67
73
|
<ListPageBoardCardHeader>
|
|
68
74
|
<ListPageBoardCardTitleRow
|
|
69
75
|
title={member.name}
|
|
@@ -94,9 +100,11 @@ export const TEAM_BOARD_GROUP_OPTIONS = [
|
|
|
94
100
|
export function TeamBoardView({
|
|
95
101
|
members,
|
|
96
102
|
groupByColumnKey,
|
|
103
|
+
onRowActivate,
|
|
97
104
|
}: {
|
|
98
105
|
members: TeamMember[]
|
|
99
106
|
groupByColumnKey: string
|
|
107
|
+
onRowActivate?: (member: TeamMember) => void
|
|
100
108
|
}) {
|
|
101
109
|
const key = groupByColumnKey === "role" ? "role" : "status"
|
|
102
110
|
const { columns, badgeMap } = useTeamBoardModel(members, key)
|
|
@@ -108,7 +116,7 @@ export function TeamBoardView({
|
|
|
108
116
|
getRowKey={m => m.id}
|
|
109
117
|
columnCountBadgeClassName={badgeMap}
|
|
110
118
|
emptyColumnLabel="No members"
|
|
111
|
-
renderCard={member => <TeamBoardCard member={member} />}
|
|
119
|
+
renderCard={member => <TeamBoardCard member={member} onRowActivate={onRowActivate} />}
|
|
112
120
|
/>
|
|
113
121
|
)
|
|
114
122
|
}
|
|
@@ -14,12 +14,19 @@ import {
|
|
|
14
14
|
} from "@/lib/list-status-badges"
|
|
15
15
|
import type { TeamMember } from "@/lib/mock/team"
|
|
16
16
|
|
|
17
|
-
function TeamListRow({
|
|
17
|
+
function TeamListRow({
|
|
18
|
+
member,
|
|
19
|
+
onRowActivate,
|
|
20
|
+
}: {
|
|
21
|
+
member: TeamMember
|
|
22
|
+
onRowActivate?: (member: TeamMember) => void
|
|
23
|
+
}) {
|
|
18
24
|
return (
|
|
19
25
|
<li>
|
|
20
26
|
<ListPageBoardCard
|
|
21
27
|
layout="row"
|
|
22
28
|
rowContainerClassName="flex flex-row items-center gap-3"
|
|
29
|
+
onClick={onRowActivate ? () => onRowActivate(member) : undefined}
|
|
23
30
|
leading={<ListPageBoardCardAvatar initials={member.initials} className="size-9" />}
|
|
24
31
|
rowEnd={
|
|
25
32
|
<div className="flex shrink-0 items-center gap-2">
|
|
@@ -43,7 +50,13 @@ function TeamListRow({ member }: { member: TeamMember }) {
|
|
|
43
50
|
)
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
export function TeamListView({
|
|
53
|
+
export function TeamListView({
|
|
54
|
+
members,
|
|
55
|
+
onRowActivate,
|
|
56
|
+
}: {
|
|
57
|
+
members: TeamMember[]
|
|
58
|
+
onRowActivate?: (member: TeamMember) => void
|
|
59
|
+
}) {
|
|
47
60
|
if (members.length === 0) {
|
|
48
61
|
return (
|
|
49
62
|
<div className="px-4 py-16 text-center lg:px-6">
|
|
@@ -55,7 +68,7 @@ export function TeamListView({ members }: { members: TeamMember[] }) {
|
|
|
55
68
|
return (
|
|
56
69
|
<ul className="flex list-none flex-col gap-2 px-4 pb-8 pt-2 lg:px-6">
|
|
57
70
|
{members.map(m => (
|
|
58
|
-
<TeamListRow key={m.id} member={m} />
|
|
71
|
+
<TeamListRow key={m.id} member={m} onRowActivate={onRowActivate} />
|
|
59
72
|
))}
|
|
60
73
|
</ul>
|
|
61
74
|
)
|
|
@@ -586,7 +586,7 @@ export const TeamTable = React.forwardRef<
|
|
|
586
586
|
return (
|
|
587
587
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
588
588
|
{sharedToolbar}
|
|
589
|
-
<TeamListView members={tableState.rows} />
|
|
589
|
+
<TeamListView members={tableState.rows} onRowActivate={m => tableState.toggleRow(m.id)} />
|
|
590
590
|
</div>
|
|
591
591
|
)
|
|
592
592
|
}
|
|
@@ -595,7 +595,11 @@ export const TeamTable = React.forwardRef<
|
|
|
595
595
|
return (
|
|
596
596
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
597
597
|
{sharedToolbar}
|
|
598
|
-
<TeamBoardView
|
|
598
|
+
<TeamBoardView
|
|
599
|
+
members={tableState.rows}
|
|
600
|
+
groupByColumnKey={teamBoardGroupKey}
|
|
601
|
+
onRowActivate={m => tableState.toggleRow(m.id)}
|
|
602
|
+
/>
|
|
599
603
|
</div>
|
|
600
604
|
)
|
|
601
605
|
}
|