DataTableSheetDetails.tsx123 lines · main
1import { ChevronDown, ChevronUp, X } from 'lucide-react'
2import { ReactNode, useCallback, useEffect, useMemo } from 'react'
3import { Button, cn, Separator, Skeleton, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
4
5import { Kbd } from './primitives/Kbd'
6import { useDataTable } from './providers/DataTableProvider'
7
8export interface DataTableSheetDetailsProps {
9 title?: ReactNode
10 titleClassName?: string
11 children?: ReactNode
12}
13
14export function DataTableSheetDetails({
15 title,
16 titleClassName,
17 children,
18}: DataTableSheetDetailsProps) {
19 const { table, rowSelection, isLoading } = useDataTable()
20
21 const selectedRowKey = Object.keys(rowSelection)?.[0]
22
23 const selectedRow = useMemo(() => {
24 if (isLoading && !selectedRowKey) return
25 return table.getRowModel().rows.find((row) => row.id === selectedRowKey)
26 }, [selectedRowKey, isLoading, table])
27
28 const index = table.getRowModel().rows.findIndex((row) => row.id === selectedRow?.id)
29
30 const nextId = useMemo(() => table.getRowModel().rows[index + 1]?.id, [index, table])
31
32 const prevId = useMemo(() => table.getRowModel().rows[index - 1]?.id, [index, table])
33
34 const onPrev = useCallback(() => {
35 if (prevId) table.setRowSelection({ [prevId]: true })
36 }, [prevId, table])
37
38 const onNext = useCallback(() => {
39 if (nextId) table.setRowSelection({ [nextId]: true })
40 }, [nextId, table])
41
42 useEffect(() => {
43 const down = (e: KeyboardEvent) => {
44 if (!selectedRowKey) return
45
46 // REMINDER: prevent dropdown navigation inside of sheet to change row selection
47 const activeElement = document.activeElement
48 const isMenuActive = activeElement?.closest('[role="menu"]')
49
50 if (isMenuActive) return
51
52 if (e.key === 'ArrowUp') {
53 e.preventDefault()
54 onPrev()
55 }
56 if (e.key === 'ArrowDown') {
57 e.preventDefault()
58 onNext()
59 }
60 }
61
62 document.addEventListener('keydown', down)
63 return () => document.removeEventListener('keydown', down)
64 }, [selectedRowKey, onNext, onPrev])
65
66 return (
67 <div className="relative bg-sidebar">
68 <div className="flex items-center justify-between gap-2 pl-5 pr-2 py-1">
69 <h5 className={cn(titleClassName, 'truncate text-left')}>
70 {isLoading && !selectedRowKey ? <Skeleton className="h-7 w-36" /> : title}
71 </h5>
72 <div className="flex h-7 items-center gap-1">
73 <Tooltip>
74 <TooltipTrigger asChild>
75 <Button
76 size="tiny"
77 type="text"
78 disabled={!prevId}
79 onClick={onPrev}
80 className="px-1"
81 icon={<ChevronUp />}
82 />
83 </TooltipTrigger>
84 <TooltipContent>
85 <p>
86 Navigate <Kbd>↑</Kbd>
87 </p>
88 </TooltipContent>
89 </Tooltip>
90
91 <Tooltip>
92 <TooltipTrigger asChild>
93 <Button
94 size="tiny"
95 type="text"
96 disabled={!nextId}
97 onClick={onNext}
98 className="px-1"
99 icon={<ChevronDown />}
100 />
101 </TooltipTrigger>
102 <TooltipContent>
103 <p>
104 Navigate <Kbd>↓</Kbd>
105 </p>
106 </TooltipContent>
107 </Tooltip>
108 <Separator orientation="vertical" className="mx-1" />
109
110 <Button
111 size="tiny"
112 type="text"
113 onClick={() => table.resetRowSelection()}
114 className="px-1"
115 icon={<X />}
116 />
117 </div>
118 </div>
119 <Separator />
120 {children}
121 </div>
122 )
123}