DataTableSheetRowAction.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import { Table } from '@tanstack/react-table'
  2. import { endOfDay, endOfHour, startOfDay, startOfHour } from 'date-fns'
  3. import {
  4. CalendarClock,
  5. CalendarDays,
  6. CalendarSearch,
  7. ChevronLeft,
  8. ChevronRight,
  9. Copy,
  10. Equal,
  11. Filter,
  12. } from 'lucide-react'
  13. import { ComponentPropsWithRef } from 'react'
  14. import {
  15. cn,
  16. DropdownMenu,
  17. DropdownMenuContent,
  18. DropdownMenuGroup,
  19. DropdownMenuItem,
  20. DropdownMenuSeparator,
  21. DropdownMenuTrigger,
  22. } from 'ui'
  23. import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
  24. import { useCopyToClipboard } from '@/hooks/ui/useCopyToClipboard'
  25. interface DataTableSheetRowActionProps<
  26. TData,
  27. TFields extends DataTableFilterField<TData>,
  28. > extends ComponentPropsWithRef<typeof DropdownMenuTrigger> {
  29. fieldValue?: TFields['value']
  30. filterFields: TFields[]
  31. value: string | number
  32. table: Table<TData>
  33. label?: string
  34. }
  35. export function DataTableSheetRowAction<TData, TFields extends DataTableFilterField<TData>>({
  36. fieldValue,
  37. filterFields,
  38. value,
  39. children,
  40. className,
  41. table,
  42. label,
  43. onKeyDown,
  44. ...props
  45. }: DataTableSheetRowActionProps<TData, TFields>) {
  46. const { copy, isCopied } = useCopyToClipboard()
  47. const field = !!fieldValue ? filterFields.find((f) => f.value === fieldValue) : undefined
  48. const column =
  49. !!fieldValue && !!field
  50. ? table.getAllColumns().find((c) => c.id === fieldValue.toString())
  51. : undefined
  52. function renderOptions() {
  53. if (!field) return null
  54. switch (field.type) {
  55. case 'checkbox':
  56. return (
  57. <DropdownMenuItem
  58. onClick={() => {
  59. const filterValue = column?.getFilterValue() as undefined | Array<unknown>
  60. const newValue = filterValue?.includes(value)
  61. ? filterValue
  62. : [...(filterValue || []), value]
  63. column?.setFilterValue(newValue)
  64. }}
  65. className="flex items-center gap-2"
  66. >
  67. <Filter size={12} />
  68. Add as filter for {column?.id}
  69. </DropdownMenuItem>
  70. )
  71. case 'input':
  72. return (
  73. <DropdownMenuItem
  74. onClick={() => column?.setFilterValue(value)}
  75. className="flex items-center gap-2"
  76. >
  77. <Filter size={12} />
  78. Add as filter for {column?.id}
  79. </DropdownMenuItem>
  80. )
  81. case 'slider':
  82. return (
  83. <DropdownMenuGroup>
  84. <DropdownMenuItem
  85. onClick={() => column?.setFilterValue([0, value])}
  86. className="flex items-center gap-2"
  87. >
  88. {/* FIXME: change icon as it is not clear */}
  89. <ChevronLeft size={12} />
  90. Less or equal than
  91. </DropdownMenuItem>
  92. <DropdownMenuItem
  93. onClick={() => column?.setFilterValue([value, 5000])}
  94. className="flex items-center gap-2"
  95. >
  96. {/* FIXME: change icon as it is not clear */}
  97. <ChevronRight size={12} />
  98. Greater or equal than
  99. </DropdownMenuItem>
  100. <DropdownMenuItem
  101. onClick={() => column?.setFilterValue([value])}
  102. className="flex items-center gap-2"
  103. >
  104. <Equal size={12} />
  105. Equal to
  106. </DropdownMenuItem>
  107. </DropdownMenuGroup>
  108. )
  109. case 'timerange':
  110. const date = new Date(value)
  111. return (
  112. <DropdownMenuGroup>
  113. <DropdownMenuItem
  114. onClick={() => column?.setFilterValue([date])}
  115. className="flex items-center gap-2"
  116. >
  117. <CalendarSearch size={12} />
  118. Exact timestamp
  119. </DropdownMenuItem>
  120. <DropdownMenuItem
  121. onClick={() => {
  122. const start = startOfHour(date)
  123. const end = endOfHour(date)
  124. column?.setFilterValue([start, end])
  125. }}
  126. className="flex items-center gap-2"
  127. >
  128. <CalendarClock size={12} />
  129. Same hour
  130. </DropdownMenuItem>
  131. <DropdownMenuItem
  132. onClick={() => {
  133. const start = startOfDay(date)
  134. const end = endOfDay(date)
  135. column?.setFilterValue([start, end])
  136. }}
  137. className="flex items-center gap-2"
  138. >
  139. <CalendarDays size={12} />
  140. Same day
  141. </DropdownMenuItem>
  142. </DropdownMenuGroup>
  143. )
  144. default:
  145. return null
  146. }
  147. }
  148. return (
  149. <DropdownMenu>
  150. <DropdownMenuTrigger
  151. className={cn(
  152. 'rounded-md ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
  153. 'relative',
  154. className
  155. )}
  156. onKeyDown={(e) => {
  157. if (e.key === 'ArrowDown') {
  158. // REMINDER: default behavior is to open the dropdown menu
  159. // But because we use it to navigate between rows, we need to prevent it
  160. // and only use "Enter" to select the option
  161. e.preventDefault()
  162. }
  163. onKeyDown?.(e)
  164. }}
  165. {...props}
  166. >
  167. {children}
  168. {isCopied ? (
  169. <div className="absolute inset-0 flex items-center justify-center rounded-md bg-surface-100/80 backdrop-blur-sm animate-in fade-in duration-150">
  170. <span className="font-mono text-xs text-foreground-light">Copied</span>
  171. </div>
  172. ) : null}
  173. </DropdownMenuTrigger>
  174. <DropdownMenuContent align="end" side="bottom" className="w-48 -translate-x-4">
  175. {!!field && !!column && (
  176. <>
  177. {renderOptions()}
  178. <DropdownMenuSeparator />
  179. </>
  180. )}
  181. <DropdownMenuItem
  182. onClick={() => copy(String(value), { timeout: 1000 })}
  183. className="flex items-center gap-2"
  184. >
  185. <Copy size={12} />
  186. Copy {label}
  187. </DropdownMenuItem>
  188. </DropdownMenuContent>
  189. </DropdownMenu>
  190. )
  191. }