@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 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.14",
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 data-slot="tooltip-trigger" suppressHydrationWarning {...props} />
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
+ }
@@ -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({ row }: { row: ComplianceItem }) {
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({ row }: { row: ComplianceItem }) {
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({ rows }: { rows: ComplianceItem[] }) {
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 rows={tableState.rows as ComplianceItem[]} />
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={onRowClick ? () => onRowClick(row) : undefined}
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
- onRowClick && "cursor-pointer",
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 className={cn(QUESTION_BANK_FAVORITE_HOVER_GROUP, "w-full")}>
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 => <QuestionBankBoardCard row={row} onToggleFavorite={onToggleFavorite} />}
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 key={row.id} row={row} onToggleFavorite={onToggleFavorite} />
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 rows={tableState.rows as QuestionBankItem[]} onToggleFavorite={toggleFavorite} />
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({ member }: { member: TeamMember }) {
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({ member }: { member: TeamMember }) {
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({ members }: { members: TeamMember[] }) {
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 members={tableState.rows} groupByColumnKey={teamBoardGroupKey} />
598
+ <TeamBoardView
599
+ members={tableState.rows}
600
+ groupByColumnKey={teamBoardGroupKey}
601
+ onRowActivate={m => tableState.toggleRow(m.id)}
602
+ />
599
603
  </div>
600
604
  )
601
605
  }