1
1
import { ChevronRightIcon } from "@heroicons/react/24/solid" ;
2
2
import { Link } from "@remix-run/react" ;
3
- import { ReactNode , forwardRef , useState } from "react" ;
3
+ import React , { ReactNode , forwardRef , useState , useContext , createContext } from "react" ;
4
4
import { cn } from "~/utils/cn" ;
5
5
import { Popover , PopoverContent , PopoverVerticalEllipseTrigger } from "./Popover" ;
6
6
import { InfoIconTooltip } from "./Tooltip" ;
7
7
8
+ const variants = {
9
+ bright : {
10
+ header : "bg-background-bright" ,
11
+ cell : "group-hover/table-row:bg-charcoal-750 group-has-[[tabindex='0']:focus]/table-row:bg-charcoal-750" ,
12
+ stickyCell : "bg-background-bright group-hover/table-row:bg-charcoal-750" ,
13
+ menuButton :
14
+ "bg-background-bright group-hover/table-row:bg-charcoal-750 group-hover/table-row:ring-charcoal-600/70 group-has-[[tabindex='0']:focus]/table-row:bg-charcoal-750" ,
15
+ menuButtonDivider : "group-hover/table-row:border-charcoal-600/70" ,
16
+ rowSelected : "bg-charcoal-750 group-hover/table-row:bg-charcoal-750" ,
17
+ } ,
18
+ dimmed : {
19
+ header : "bg-background-dimmed" ,
20
+ cell : "group-hover/table-row:bg-charcoal-800 group-has-[[tabindex='0']:focus]/table-row:bg-background-bright" ,
21
+ stickyCell : "group-hover/table-row:bg-charcoal-800" ,
22
+ menuButton :
23
+ "bg-background-dimmed group-hover/table-row:bg-charcoal-800 group-hover/table-row:ring-grid-bright group-has-[[tabindex='0']:focus]/table-row:bg-background-bright" ,
24
+ menuButtonDivider : "group-hover/table-row:border-grid-dimmed" ,
25
+ rowSelected : "bg-charcoal-750 group-hover/table-row:bg-charcoal-750" ,
26
+ } ,
27
+ } as const ;
28
+
29
+ export type TableVariant = keyof typeof variants ;
30
+
8
31
type TableProps = {
9
32
containerClassName ?: string ;
10
33
className ?: string ;
11
34
children : ReactNode ;
12
35
fullWidth ?: boolean ;
13
36
} ;
14
37
15
- export const Table = forwardRef < HTMLTableElement , TableProps > (
16
- ( { className, containerClassName, children, fullWidth } , ref ) => {
38
+ // Add TableContext
39
+ const TableContext = createContext < { variant : TableVariant } > ( { variant : "dimmed" } ) ;
40
+
41
+ export const Table = forwardRef < HTMLTableElement , TableProps & { variant ?: TableVariant } > (
42
+ ( { className, containerClassName, children, fullWidth, variant = "dimmed" } , ref ) => {
17
43
return (
18
- < div
19
- className = { cn (
20
- "overflow-x-auto whitespace-nowrap border-t border-grid-bright scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" ,
21
- containerClassName ,
22
- fullWidth && "w-full"
23
- ) }
24
- >
25
- < table ref = { ref } className = { cn ( "w-full" , className ) } >
26
- { children }
27
- </ table >
28
- </ div >
44
+ < TableContext . Provider value = { { variant } } >
45
+ < div
46
+ className = { cn (
47
+ "overflow-x-auto whitespace-nowrap border-t scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600" ,
48
+ containerClassName ,
49
+ fullWidth && "w-full"
50
+ ) }
51
+ >
52
+ < table ref = { ref } className = { cn ( "w-full" , className ) } >
53
+ { children }
54
+ </ table >
55
+ </ div >
56
+ </ TableContext . Provider >
29
57
) ;
30
58
}
31
59
) ;
@@ -37,11 +65,13 @@ type TableHeaderProps = {
37
65
38
66
export const TableHeader = forwardRef < HTMLTableSectionElement , TableHeaderProps > (
39
67
( { className, children } , ref ) => {
68
+ const { variant } = useContext ( TableContext ) ;
40
69
return (
41
70
< thead
42
71
ref = { ref }
43
72
className = { cn (
44
- "sticky top-0 z-10 bg-background-dimmed after:absolute after:bottom-0 after:left-0 after:right-0 after:h-px after:bg-grid-bright" ,
73
+ "sticky top-0 z-10 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-px after:bg-grid-bright" ,
74
+ variants [ variant ] . header ,
45
75
className
46
76
) }
47
77
>
@@ -75,13 +105,14 @@ type TableRowProps = {
75
105
76
106
export const TableRow = forwardRef < HTMLTableRowElement , TableRowProps > (
77
107
( { className, disabled, isSelected, children } , ref ) => {
108
+ const { variant } = useContext ( TableContext ) ;
78
109
return (
79
110
< tr
80
111
ref = { ref }
81
112
className = { cn (
82
- "group/table-row relative w-full after:absolute after:bottom-0 after:left-3 after:right-0 after:h-px after:bg-grid-dimmed" ,
113
+ "group/table-row relative w-full outline-none after:absolute after:bottom-0 after:left-3 after:right-0 after:h-px after:bg-grid-dimmed" ,
114
+ isSelected && variants [ variant ] . rowSelected ,
83
115
disabled && "opacity-50" ,
84
- isSelected && isSelectedStyle ,
85
116
className
86
117
) }
87
118
>
@@ -94,7 +125,7 @@ export const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(
94
125
type TableCellBasicProps = {
95
126
className ?: string ;
96
127
alignment ?: "left" | "center" | "right" ;
97
- children : ReactNode ;
128
+ children ? : ReactNode ;
98
129
colSpan ?: number ;
99
130
} ;
100
131
@@ -125,6 +156,7 @@ export const TableHeaderCell = forwardRef<HTMLTableCellElement, TableHeaderCellP
125
156
className
126
157
) }
127
158
colSpan = { colSpan }
159
+ tabIndex = { - 1 }
128
160
>
129
161
{ hiddenLabel ? (
130
162
< span className = "sr-only" > { children } </ span >
@@ -147,24 +179,12 @@ type TableCellProps = TableCellBasicProps & {
147
179
hasAction ?: boolean ;
148
180
isSticky ?: boolean ;
149
181
actionClassName ?: string ;
150
- rowHoverStyle ?: keyof typeof rowHoverStyles ;
182
+ rowHoverStyle ?: string ;
151
183
isSelected ?: boolean ;
184
+ isTabbableCell ?: boolean ;
185
+ children ?: ReactNode ;
152
186
} ;
153
187
154
- const rowHoverStyles = {
155
- default :
156
- "group-hover/table-row:bg-charcoal-800 group-hover/table-row:before:absolute group-hover/table-row:before:bg-charcoal-750 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:left-0 group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:after:absolute group-hover/table-row:after:bg-charcoal-750 group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3" ,
157
- dimmed :
158
- "group-hover/table-row:bg-charcoal-850 group-hover/table-row:before:absolute group-hover/table-row:before:bg-charcoal-800 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:left-0 group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:after:absolute group-hover/table-row:after:bg-charcoal-800 group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3" ,
159
- bright :
160
- "group-hover/table-row:bg-charcoal-750 group-hover/table-row:before:absolute group-hover/table-row:before:bg-charcoal-700 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:left-0 group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:after:absolute group-hover/table-row:after:bg-charcoal-700 group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3" ,
161
- } ;
162
-
163
- const stickyStyles =
164
- "sticky right-0 bg-background-dimmed group-hover/table-row:bg-charcoal-750 w-[--sticky-width] [&:has(.group-hover\\/table-row\\:block)]:w-auto" ;
165
-
166
- const isSelectedStyle = "bg-charcoal-750 group-hover:bg-charcoal-750" ;
167
-
168
188
export const TableCell = forwardRef < HTMLTableCellElement , TableCellProps > (
169
189
(
170
190
{
@@ -177,8 +197,8 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
177
197
onClick,
178
198
hasAction = false ,
179
199
isSticky = false ,
180
- rowHoverStyle = "default" ,
181
200
isSelected,
201
+ isTabbableCell = false ,
182
202
} ,
183
203
ref
184
204
) => {
@@ -193,34 +213,47 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
193
213
}
194
214
195
215
const flexClasses = cn (
196
- "flex w-full whitespace-nowrap px-3 py-3 text-xs text-text-dimmed" ,
216
+ "flex w-full whitespace-nowrap px-3 py-3 items-center text-xs text-text-dimmed" ,
197
217
alignment === "left"
198
218
? "justify-start text-left"
199
219
: alignment === "center"
200
220
? "justify-center text-center"
201
221
: "justify-end text-right"
202
222
) ;
223
+ const { variant } = useContext ( TableContext ) ;
203
224
204
225
return (
205
226
< td
206
227
ref = { ref }
207
228
className = { cn (
208
- "text-xs text-charcoal-400" ,
209
- to || onClick || hasAction ? "cursor-pointer" : "px-3 py-3 align-middle" ,
229
+ "text-xs text-charcoal-400 has-[[tabindex='0']:focus]:before:absolute has-[[tabindex='0']:focus]:before:-top-px has-[[tabindex='0']:focus]:before:left-0 has-[[tabindex='0']:focus]:before:h-px has-[[tabindex='0']:focus]:before:w-3 has-[[tabindex='0']:focus]:before:bg-grid-dimmed has-[[tabindex='0']:focus]:after:absolute has-[[tabindex='0']:focus]:after:bottom-0 has-[[tabindex='0']:focus]:after:left-0 has-[[tabindex='0']:focus]:after:right-0 has-[[tabindex='0']:focus]:after:h-px has-[[tabindex='0']:focus]:after:bg-grid-dimmed" ,
230
+ variants [ variant ] . cell ,
231
+ to || onClick || hasAction ? "cursor-pointer" : "cursor-default px-3 py-3 align-middle" ,
210
232
! to && ! onClick && alignmentClassName ,
211
- isSticky && stickyStyles ,
212
- isSelected && isSelectedStyle ,
213
- ! isSelected && rowHoverStyles [ rowHoverStyle ] ,
233
+ isSticky &&
234
+ "[&:has(.group-hover/table-row:block)]:w-auto sticky right-0 bg-background-dimmed" ,
235
+ isSticky && variants [ variant ] . stickyCell ,
236
+ isSelected && variants [ variant ] . rowSelected ,
237
+ ! isSelected &&
238
+ "group-hover/table-row:before:absolute group-hover/table-row:before:left-0 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:before:bg-charcoal-750 group-hover/table-row:after:absolute group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3 group-hover/table-row:after:bg-charcoal-750 group-focus-visible/table-row:bg-background-bright" ,
214
239
className
215
240
) }
216
241
colSpan = { colSpan }
217
242
>
218
243
{ to ? (
219
- < Link to = { to } className = { cn ( "focus-custom" , flexClasses , actionClassName ) } >
244
+ < Link
245
+ to = { to }
246
+ className = { cn ( "cursor-pointer focus:outline-none" , flexClasses , actionClassName ) }
247
+ tabIndex = { isTabbableCell ? 0 : - 1 }
248
+ >
220
249
{ children }
221
250
</ Link >
222
251
) : onClick ? (
223
- < button onClick = { onClick } className = { cn ( "focus-custom" , flexClasses , actionClassName ) } >
252
+ < button
253
+ onClick = { onClick }
254
+ className = { cn ( "cursor-pointer focus:outline-none" , flexClasses , actionClassName ) }
255
+ tabIndex = { isTabbableCell ? 0 : - 1 }
256
+ >
224
257
{ children }
225
258
</ button >
226
259
) : (
@@ -258,7 +291,7 @@ export const TableCellChevron = forwardRef<
258
291
259
292
export const TableCellMenu = forwardRef <
260
293
HTMLTableCellElement ,
261
- {
294
+ TableCellProps & {
262
295
className ?: string ;
263
296
isSticky ?: boolean ;
264
297
onClick ?: ( event : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => void ;
@@ -283,6 +316,7 @@ export const TableCellMenu = forwardRef<
283
316
ref
284
317
) => {
285
318
const [ isOpen , setIsOpen ] = useState ( false ) ;
319
+ const { variant } = useContext ( TableContext ) ;
286
320
287
321
return (
288
322
< TableCell
@@ -297,15 +331,18 @@ export const TableCellMenu = forwardRef<
297
331
< div className = "relative h-full p-1" >
298
332
< div
299
333
className = { cn (
300
- "absolute right-0 top-1/2 mr-1 flex -translate-y-1/2 items-center justify-end gap-0.5 rounded-[0.25rem] bg-background-dimmed p-0.5 group-hover/table-row:bg-background-bright group-hover/table-row:ring-1 group-hover/table-row:ring-grid-bright" ,
301
- isSelected && isSelectedStyle ,
302
- isSelected &&
303
- "group-hover/table-row:bg-charcoal-750 group-hover/table-row:ring-charcoal-600/50"
334
+ "absolute right-0 top-1/2 mr-1 flex -translate-y-1/2 items-center justify-end gap-0.5 rounded-[0.25rem] p-0.5 group-hover/table-row:ring-1" ,
335
+ variants [ variant ] . menuButton
304
336
) }
305
337
>
306
338
{ /* Hidden buttons that show on hover */ }
307
339
{ hiddenButtons && (
308
- < div className = "hidden pr-0.5 group-hover/table-row:block group-hover/table-row:border-r group-hover/table-row:border-grid-dimmed" >
340
+ < div
341
+ className = { cn (
342
+ "hidden pr-0.5 group-hover/table-row:block group-hover/table-row:border-r" ,
343
+ variants [ variant ] . menuButtonDivider
344
+ ) }
345
+ >
309
346
{ hiddenButtons }
310
347
</ div >
311
348
) }
0 commit comments