@gentleduck/registry-ui 0.2.1

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.
Files changed (175) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/index.css +3 -0
  3. package/package.json +59 -0
  4. package/src/_old/_table/index.ts +5 -0
  5. package/src/_old/_table/table-advanced.constants.tsx +24 -0
  6. package/src/_old/_table/table-advanced.tsx +311 -0
  7. package/src/_old/_table/table-advanced.types.ts +272 -0
  8. package/src/_old/_table/table.constants.ts +2 -0
  9. package/src/_old/_table/table.hook.tsx +115 -0
  10. package/src/_old/_table/table.lib.ts +85 -0
  11. package/src/_old/_table/table.tsx +916 -0
  12. package/src/_old/_table/table.types.ts +118 -0
  13. package/src/_old/_table/todo.md +11 -0
  14. package/src/_old/_upload/index.ts +9 -0
  15. package/src/_old/_upload/todo.md +38 -0
  16. package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
  17. package/src/_old/_upload/upload-advanced.tsx +507 -0
  18. package/src/_old/_upload/upload-sonner.tsx +58 -0
  19. package/src/_old/_upload/upload.assets.tsx +239 -0
  20. package/src/_old/_upload/upload.constants.tsx +75 -0
  21. package/src/_old/_upload/upload.dto.ts +19 -0
  22. package/src/_old/_upload/upload.lib.tsx +630 -0
  23. package/src/_old/_upload/upload.tsx +491 -0
  24. package/src/_old/_upload/upload.types.ts +436 -0
  25. package/src/accordion/accordion.tsx +247 -0
  26. package/src/accordion/index.ts +1 -0
  27. package/src/alert/alert.constants.ts +17 -0
  28. package/src/alert/alert.tsx +52 -0
  29. package/src/alert/index.ts +2 -0
  30. package/src/alert-dialog/alert-dialog.tsx +107 -0
  31. package/src/alert-dialog/index.ts +1 -0
  32. package/src/aspect-ratio/aspect-ratio.tsx +33 -0
  33. package/src/aspect-ratio/index.ts +1 -0
  34. package/src/audio/audio-record.tsx +776 -0
  35. package/src/audio/audio-visualizer.tsx +377 -0
  36. package/src/audio/audio.libs.ts +5 -0
  37. package/src/audio/audio.types.ts +50 -0
  38. package/src/audio/index.ts +2 -0
  39. package/src/avatar/avatar.tsx +78 -0
  40. package/src/avatar/index.ts +1 -0
  41. package/src/badge/badge.constants.ts +38 -0
  42. package/src/badge/badge.tsx +19 -0
  43. package/src/badge/index.ts +2 -0
  44. package/src/breadcrumb/breadcrumb.tsx +119 -0
  45. package/src/breadcrumb/index.ts +1 -0
  46. package/src/button/button.constants.ts +44 -0
  47. package/src/button/button.tsx +79 -0
  48. package/src/button/button.types.ts +38 -0
  49. package/src/button/index.ts +3 -0
  50. package/src/button-group/button-group.constants.ts +26 -0
  51. package/src/button-group/button-group.tsx +65 -0
  52. package/src/button-group/index.ts +2 -0
  53. package/src/calendar/calendar.tsx +191 -0
  54. package/src/calendar/index.ts +1 -0
  55. package/src/card/card.tsx +81 -0
  56. package/src/card/index.ts +1 -0
  57. package/src/carousel/carousel.tsx +211 -0
  58. package/src/carousel/carousel.types.ts +23 -0
  59. package/src/carousel/index.ts +2 -0
  60. package/src/chart/chart.libs.ts +27 -0
  61. package/src/chart/chart.tsx +260 -0
  62. package/src/chart/chart.types.ts +38 -0
  63. package/src/chart/index.ts +3 -0
  64. package/src/checkbox/checkbox.tsx +144 -0
  65. package/src/checkbox/checkbox.types.ts +24 -0
  66. package/src/checkbox/index.ts +2 -0
  67. package/src/collapsible/collapsible.tsx +151 -0
  68. package/src/collapsible/index.ts +1 -0
  69. package/src/combobox/combobox.tsx +132 -0
  70. package/src/combobox/index.ts +1 -0
  71. package/src/command/command.tsx +192 -0
  72. package/src/command/command.types.ts +11 -0
  73. package/src/command/index.ts +2 -0
  74. package/src/context-menu/context-menu.tsx +178 -0
  75. package/src/context-menu/index.ts +1 -0
  76. package/src/dialog/dialog-responsive.tsx +137 -0
  77. package/src/dialog/dialog.tsx +97 -0
  78. package/src/dialog/index.ts +2 -0
  79. package/src/direction/direction.tsx +13 -0
  80. package/src/direction/index.ts +1 -0
  81. package/src/drawer/drawer.tsx +185 -0
  82. package/src/drawer/index.ts +1 -0
  83. package/src/dropdown-menu/dropdown-menu.tsx +181 -0
  84. package/src/dropdown-menu/index.ts +1 -0
  85. package/src/empty/empty.constants.ts +15 -0
  86. package/src/empty/empty.tsx +73 -0
  87. package/src/empty/index.ts +2 -0
  88. package/src/field/field.constants.ts +22 -0
  89. package/src/field/field.tsx +203 -0
  90. package/src/field/index.ts +2 -0
  91. package/src/hover-card/hover-card.tsx +79 -0
  92. package/src/hover-card/index.ts +1 -0
  93. package/src/input/index.ts +1 -0
  94. package/src/input/input.tsx +45 -0
  95. package/src/input-group/index.ts +1 -0
  96. package/src/input-group/input-group.tsx +170 -0
  97. package/src/input-otp/index.ts +1 -0
  98. package/src/input-otp/input-otp.tsx +66 -0
  99. package/src/item/index.ts +2 -0
  100. package/src/item/item.constants.ts +22 -0
  101. package/src/item/item.tsx +185 -0
  102. package/src/json-editor/index.ts +4 -0
  103. package/src/json-editor/json-editor.hooks.ts +21 -0
  104. package/src/json-editor/json-editor.libs.ts +34 -0
  105. package/src/json-editor/json-editor.tsx +425 -0
  106. package/src/json-editor/json-editor.types.ts +80 -0
  107. package/src/json-editor/json-editor.view.tsx +110 -0
  108. package/src/json-editor/json-text-area.tsx +7 -0
  109. package/src/kbd/index.ts +1 -0
  110. package/src/kbd/kbd.tsx +39 -0
  111. package/src/label/index.ts +1 -0
  112. package/src/label/label.tsx +28 -0
  113. package/src/menubar/index.ts +1 -0
  114. package/src/menubar/menubar.tsx +213 -0
  115. package/src/navigation-menu/index.ts +1 -0
  116. package/src/navigation-menu/navigation-menu.tsx +152 -0
  117. package/src/pagination/index.ts +2 -0
  118. package/src/pagination/pagination.tsx +191 -0
  119. package/src/pagination/pagination.types.ts +17 -0
  120. package/src/popover/index.ts +1 -0
  121. package/src/popover/popover.tsx +35 -0
  122. package/src/preview-panel/index.ts +3 -0
  123. package/src/preview-panel/preview-panel-dialog.tsx +99 -0
  124. package/src/preview-panel/preview-panel.tsx +389 -0
  125. package/src/preview-panel/preview-panel.types.ts +49 -0
  126. package/src/progress/index.ts +1 -0
  127. package/src/progress/progress.tsx +32 -0
  128. package/src/radio-group/index.ts +1 -0
  129. package/src/radio-group/radio-group.tsx +92 -0
  130. package/src/resizable/index.ts +1 -0
  131. package/src/resizable/resizable.tsx +52 -0
  132. package/src/scroll-area/index.ts +1 -0
  133. package/src/scroll-area/scroll-area.tsx +30 -0
  134. package/src/select/index.ts +1 -0
  135. package/src/select/select.tsx +138 -0
  136. package/src/separator/index.ts +1 -0
  137. package/src/separator/separator.tsx +28 -0
  138. package/src/sheet/index.ts +2 -0
  139. package/src/sheet/sheet.constants.tsx +20 -0
  140. package/src/sheet/sheet.tsx +92 -0
  141. package/src/sidebar/index.ts +4 -0
  142. package/src/sidebar/sidebar.constants.ts +30 -0
  143. package/src/sidebar/sidebar.hooks.ts +13 -0
  144. package/src/sidebar/sidebar.tsx +676 -0
  145. package/src/sidebar/sidebar.types.ts +28 -0
  146. package/src/skeleton/index.ts +1 -0
  147. package/src/skeleton/skeleton.tsx +22 -0
  148. package/src/slider/index.ts +1 -0
  149. package/src/slider/slider.tsx +57 -0
  150. package/src/sonner/index.ts +4 -0
  151. package/src/sonner/sonner.chunks.tsx +80 -0
  152. package/src/sonner/sonner.libs.ts +13 -0
  153. package/src/sonner/sonner.tsx +31 -0
  154. package/src/sonner/sonner.types.ts +9 -0
  155. package/src/switch/index.ts +1 -0
  156. package/src/switch/switch.tsx +63 -0
  157. package/src/table/index.ts +1 -0
  158. package/src/table/table.tsx +95 -0
  159. package/src/tabs/index.ts +1 -0
  160. package/src/tabs/tabs.tsx +151 -0
  161. package/src/textarea/index.ts +1 -0
  162. package/src/textarea/textarea.tsx +24 -0
  163. package/src/toggle/index.ts +2 -0
  164. package/src/toggle/toggle.constants.ts +22 -0
  165. package/src/toggle/toggle.tsx +24 -0
  166. package/src/toggle-group/index.ts +1 -0
  167. package/src/toggle-group/toggle-group.tsx +69 -0
  168. package/src/tooltip/index.ts +1 -0
  169. package/src/tooltip/tooltip.tsx +32 -0
  170. package/src/upload/index.ts +1 -0
  171. package/src/upload/upload.constants.tsx +19 -0
  172. package/src/upload/upload.libs.ts +97 -0
  173. package/src/upload/upload.tsx +340 -0
  174. package/src/upload/upload.types.ts +44 -0
  175. package/tsconfig.json +25 -0
@@ -0,0 +1,22 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
3
+ import * as React from 'react'
4
+
5
+ const Skeleton = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, dir, ...props }, ref) => {
7
+ const direction = useDirection(dir as Direction)
8
+ return (
9
+ <div
10
+ ref={ref}
11
+ aria-hidden="true"
12
+ className={cn('animate-pulse rounded-md bg-muted motion-reduce:animate-none', className)}
13
+ dir={direction}
14
+ {...props}
15
+ data-slot="skeleton"
16
+ />
17
+ )
18
+ },
19
+ )
20
+ Skeleton.displayName = 'Skeleton'
21
+
22
+ export { Skeleton }
@@ -0,0 +1 @@
1
+ export * from './slider'
@@ -0,0 +1,57 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import * as SliderPrimitive from '@gentleduck/primitives/slider'
5
+ import * as React from 'react'
6
+
7
+ function Slider({
8
+ className,
9
+ defaultValue,
10
+ orientation = 'horizontal',
11
+ value,
12
+ min = 0,
13
+ max = 100,
14
+ ...props
15
+ }: React.ComponentProps<typeof SliderPrimitive.Root>) {
16
+ const _values = React.useMemo(
17
+ () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),
18
+ [value, defaultValue, min, max],
19
+ )
20
+
21
+ return (
22
+ <SliderPrimitive.Root
23
+ data-slot="slider"
24
+ data-orientation={orientation}
25
+ defaultValue={defaultValue}
26
+ orientation={orientation}
27
+ value={value}
28
+ min={min}
29
+ max={max}
30
+ className={cn(
31
+ "relative flex w-full touch-none select-none items-center data-[orientation='vertical']:h-full data-[orientation='vertical']:min-h-40 data-[orientation='vertical']:w-auto data-[orientation='vertical']:flex-col data-disabled:opacity-50",
32
+ className,
33
+ )}
34
+ {...props}>
35
+ <SliderPrimitive.Track
36
+ data-orientation={orientation}
37
+ data-slot="slider-track"
38
+ className="relative grow overflow-hidden rounded-full bg-muted data-[orientation='horizontal']:h-1 data-[orientation='vertical']:h-full data-[orientation='horizontal']:w-full data-[orientation='vertical']:w-1">
39
+ <SliderPrimitive.Range
40
+ data-slot="slider-range"
41
+ data-orientation={orientation}
42
+ className="absolute select-none bg-primary data-[orientation='horizontal']:h-full data-[orientation='vertical']:w-full"
43
+ />
44
+ </SliderPrimitive.Track>
45
+ {Array.from({ length: _values.length }, (_, index) => (
46
+ <SliderPrimitive.Thumb
47
+ data-orientation={orientation}
48
+ data-slot="slider-thumb"
49
+ key={index}
50
+ className="relative block size-4 shrink-0 select-none rounded-full border border-ring bg-white ring-ring/50 transition-[color,box-shadow] after:absolute after:-inset-2 hover:ring-3 focus-visible:outline-hidden focus-visible:ring-3 active:ring-3 disabled:pointer-events-none disabled:opacity-50"
51
+ />
52
+ ))}
53
+ </SliderPrimitive.Root>
54
+ )
55
+ }
56
+
57
+ export { Slider }
@@ -0,0 +1,4 @@
1
+ export * from './sonner'
2
+ export * from './sonner.chunks'
3
+ export * from './sonner.libs'
4
+ export * from './sonner.types'
@@ -0,0 +1,80 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { CircleCheck, Loader } from 'lucide-react'
3
+ import type React from 'react'
4
+ import { toast } from 'sonner'
5
+ import { Button } from '../button'
6
+ import { Progress } from '../progress'
7
+ import { formatTime } from './sonner.libs'
8
+ import type { UploadSonnerProps } from './sonner.types'
9
+
10
+ const SonnerUpload = ({
11
+ progress,
12
+ attachments,
13
+ remainingTime,
14
+ onCancel,
15
+ onComplete,
16
+ }: UploadSonnerProps): React.JSX.Element => {
17
+ return (
18
+ <div className="flex w-full gap-3" data-slot="content">
19
+ <CircleCheck
20
+ aria-hidden="true"
21
+ className={cn(
22
+ '!size-[18px] mt-2 hidden fill-primary [&_path]:stroke-primary-foreground',
23
+ progress >= 100 && 'flex',
24
+ )}
25
+ />
26
+ <Loader
27
+ aria-hidden="true"
28
+ className={cn(
29
+ '!size-[18px] mt-2 hidden animate-spin text-foreground-muted opacity-70',
30
+ progress < 100 && 'flex',
31
+ )}
32
+ />
33
+ <div className="flex w-full flex-col gap-2">
34
+ <div className="flex w-full justify-between" role="status">
35
+ <p className="text-foreground text-sm">
36
+ {progress >= 100
37
+ ? `Upload complete`
38
+ : attachments
39
+ ? `Uploading ${attachments} file${attachments > 1 ? 's' : ''}...`
40
+ : `Uploading...`}
41
+ </p>
42
+ <div className="flex items-center gap-2">
43
+ {progress <= 100 && (
44
+ <p className="font-mono text-foreground-light text-sm">{`${remainingTime && !Number.isNaN(remainingTime) && Number.isFinite(remainingTime) && remainingTime !== 0 ? `${formatTime(remainingTime)} remaining -` : ''}`}</p>
45
+ )}
46
+ <p className="font-mono text-foreground-light text-sm">{`${progress}%`}</p>
47
+ </div>
48
+ </div>
49
+ <Progress className="h-1 w-full" value={progress} />
50
+ <div className="flex w-full items-center justify-between gap-2">
51
+ <small className="w-full text-foreground-muted text-xs">
52
+ {progress < 100 ? 'Please do not close the browser until completed' : 'Upload complete'}
53
+ </small>
54
+
55
+ {progress === 100 && (
56
+ <Button
57
+ border="default"
58
+ onClick={(_) => onComplete?.(_, (id: string) => toast.dismiss(id))}
59
+ size="sm"
60
+ variant="default">
61
+ Complete
62
+ </Button>
63
+ )}
64
+
65
+ {progress < 100 && (
66
+ <Button
67
+ border="default"
68
+ onClick={(_) => onCancel?.(_, (id: string) => toast.dismiss(id))}
69
+ size="sm"
70
+ variant="default">
71
+ Cancel
72
+ </Button>
73
+ )}
74
+ </div>
75
+ </div>
76
+ </div>
77
+ )
78
+ }
79
+
80
+ export { SonnerUpload }
@@ -0,0 +1,13 @@
1
+ export function formatTime(seconds: number) {
2
+ const days = Math.floor(seconds / (24 * 3600))
3
+ seconds %= 24 * 3600
4
+ const hours = Math.floor(seconds / 3600)
5
+ seconds %= 3600
6
+ const minutes = Math.floor(seconds / 60)
7
+ seconds = Math.floor(seconds % 60)
8
+
9
+ if (days > 0) return `${days}d `
10
+ if (hours > 0) return `${hours}h `
11
+ if (minutes > 0) return `${minutes}m `
12
+ return `${seconds}s`
13
+ }
@@ -0,0 +1,31 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
5
+ import { useTheme } from 'next-themes'
6
+ import type * as React from 'react'
7
+ import { Toaster as Sonner, type ToasterProps } from 'sonner'
8
+
9
+ const Toaster = ({ dir, className, ...props }: ToasterProps) => {
10
+ const { theme = 'system' } = useTheme()
11
+ const direction = useDirection(dir as Direction)
12
+
13
+ return (
14
+ <Sonner
15
+ className={cn('toaster group [&_li>div]:w-full', className)}
16
+ dir={direction}
17
+ style={
18
+ {
19
+ '--normal-bg': 'var(--popover)',
20
+ '--normal-border': 'var(--border)',
21
+ '--normal-text': 'var(--popover-foreground)',
22
+ } as React.CSSProperties
23
+ }
24
+ theme={theme as ToasterProps['theme']}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+ Toaster.displayName = 'Toaster'
30
+
31
+ export { Toaster }
@@ -0,0 +1,9 @@
1
+ import type { Toaster as Sonner } from 'sonner'
2
+ export type ToasterProps = React.ComponentProps<typeof Sonner>
3
+ export type UploadSonnerProps = {
4
+ progress: number
5
+ attachments: number
6
+ remainingTime?: number
7
+ onCancel?: (_e: React.MouseEvent<HTMLButtonElement>, onCancel: (_id: string) => void) => void
8
+ onComplete?: (_e: React.MouseEvent<HTMLButtonElement>, onComplete: (_id: string) => void) => void
9
+ }
@@ -0,0 +1 @@
1
+ export * from './switch'
@@ -0,0 +1,63 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import { checkersStylePattern } from '@gentleduck/motion/anim'
5
+ import { useSvgIndicator } from '@gentleduck/primitives/checkers'
6
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
7
+ import * as React from 'react'
8
+
9
+ const Switch = React.forwardRef<
10
+ HTMLInputElement,
11
+ Omit<React.HTMLProps<HTMLInputElement>, 'ref'> & {
12
+ indicator?: React.ReactElement
13
+ checkedIndicator?: React.ReactElement
14
+ onCheckedChange?: (checked: boolean) => void
15
+ }
16
+ >(({ className, indicator, checkedIndicator, onChange, onCheckedChange, dir, style, ...props }, ref) => {
17
+ const direction = useDirection(dir as Direction)
18
+ const { indicatorReady, checkedIndicatorReady, inputStyle, SvgIndicator } = useSvgIndicator({
19
+ checkedIndicator,
20
+ indicator,
21
+ })
22
+
23
+ return (
24
+ <>
25
+ <input
26
+ className={cn(
27
+ checkersStylePattern({
28
+ indicatorState:
29
+ indicatorReady && checkedIndicatorReady
30
+ ? 'both'
31
+ : indicatorReady
32
+ ? 'indicatorReady'
33
+ : checkedIndicatorReady
34
+ ? 'checkedIndicatorReady'
35
+ : 'default',
36
+ type: 'switch',
37
+ }),
38
+ 'transition-all transition-discrete duration-[200ms,150ms] ease-(--duck-motion-ease)',
39
+ '[&:before,&:after]:transition-gpu [&:before,&:after]:duration-[inherit] [&:before,&:after]:ease-[inherit] [&:before,&:after]:will-change-[inherit]',
40
+ '[&[dir=ltr]:checked]:after:translate-x-full [&[dir=ltr]]:after:translate-x-0',
41
+ '[&[dir=rtl]:checked]:after:-translate-x-full [&[dir=rtl]]:after:translate-x-0',
42
+
43
+ className,
44
+ )}
45
+ onChange={(e) => {
46
+ onChange?.(e)
47
+ onCheckedChange?.(e.target.checked)
48
+ }}
49
+ ref={ref}
50
+ role="switch"
51
+ dir={direction}
52
+ style={{ ...style, ...inputStyle }}
53
+ type="checkbox"
54
+ {...props}
55
+ data-slot="switch"
56
+ />
57
+ <SvgIndicator className="sr-only" />
58
+ </>
59
+ )
60
+ })
61
+ Switch.displayName = 'Switch'
62
+
63
+ export { Switch }
@@ -0,0 +1 @@
1
+ export * from './table'
@@ -0,0 +1,95 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
3
+ import * as React from 'react'
4
+
5
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
6
+ ({ className, dir, ...props }, ref) => {
7
+ const direction = useDirection(dir as Direction)
8
+ return (
9
+ <div className="relative w-full overflow-auto" dir={direction}>
10
+ <table className={cn('w-full caption-bottom text-sm', className)} data-slot="table" ref={ref} {...props} />
11
+ </div>
12
+ )
13
+ },
14
+ )
15
+ Table.displayName = 'Table'
16
+
17
+ const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
18
+ ({ className, ...props }, ref) => (
19
+ <thead className={cn('[&_tr]:border-b', className)} data-slot="table-header" ref={ref} {...props} />
20
+ ),
21
+ )
22
+ TableHeader.displayName = 'TableHeader'
23
+
24
+ const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
25
+ ({ className, ...props }, ref) => (
26
+ <tbody className={cn('[&_tr:last-child]:border-0', className)} data-slot="table-body" ref={ref} {...props} />
27
+ ),
28
+ )
29
+ TableBody.displayName = 'TableBody'
30
+
31
+ const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
32
+ ({ className, ...props }, ref) => (
33
+ <tfoot
34
+ className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
35
+ data-slot="table-footer"
36
+ ref={ref}
37
+ {...props}
38
+ />
39
+ ),
40
+ )
41
+ TableFooter.displayName = 'TableFooter'
42
+
43
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
44
+ ({ className, ...props }, ref) => (
45
+ <tr
46
+ className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
47
+ data-slot="table-row"
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ ),
52
+ )
53
+ TableRow.displayName = 'TableRow'
54
+
55
+ const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
56
+ ({ className, scope = 'col', ...props }, ref) => (
57
+ <th
58
+ className={cn(
59
+ 'px-4 text-start align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pe-0',
60
+ className,
61
+ )}
62
+ data-slot="table-head"
63
+ ref={ref}
64
+ scope={scope}
65
+ {...props}
66
+ />
67
+ ),
68
+ )
69
+ TableHead.displayName = 'TableHead'
70
+
71
+ const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
72
+ ({ className, ...props }, ref) => (
73
+ <td
74
+ className={cn('p-4 align-middle [&:has([role=checkbox])]:pe-0', className)}
75
+ data-slot="table-cell"
76
+ ref={ref}
77
+ {...props}
78
+ />
79
+ ),
80
+ )
81
+ TableCell.displayName = 'TableCell'
82
+
83
+ const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
84
+ ({ className, ...props }, ref) => (
85
+ <caption
86
+ className={cn('mt-4 text-sm text-muted-foreground', className)}
87
+ data-slot="table-caption"
88
+ ref={ref}
89
+ {...props}
90
+ />
91
+ ),
92
+ )
93
+ TableCaption.displayName = 'TableCaption'
94
+
95
+ export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption }
@@ -0,0 +1 @@
1
+ export * from './tabs'
@@ -0,0 +1,151 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
5
+ import { MountMinimal } from '@gentleduck/primitives/mount'
6
+ import * as React from 'react'
7
+
8
+ export function useTabs() {
9
+ const context = React.useContext(TabsContext)
10
+ if (context === null) {
11
+ throw new Error('useTabs must be used within a TabsList')
12
+ }
13
+ return context
14
+ }
15
+
16
+ export interface TabsContextProps {
17
+ activeItem: string
18
+ setActiveItem: React.Dispatch<React.SetStateAction<string>>
19
+ tabsId: string
20
+ }
21
+
22
+ const TabsContext = React.createContext<TabsContextProps | null>(null)
23
+
24
+ export interface TabsProps extends Omit<React.HTMLProps<HTMLDivElement>, 'defaultValue' | 'ref'> {
25
+ value?: string
26
+ defaultValue?: string
27
+ onValueChange?: (value: string) => void
28
+ }
29
+
30
+ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
31
+ ({ value, defaultValue, onValueChange, dir, ...props }, ref) => {
32
+ const direction = useDirection(dir as Direction)
33
+ const [activeItem, setActiveItem] = React.useState<string>(defaultValue ?? value ?? '')
34
+ const tabsId = React.useId()
35
+
36
+ React.useEffect(() => {
37
+ if (onValueChange) onValueChange(activeItem)
38
+ }, [activeItem])
39
+
40
+ return (
41
+ <TabsContext.Provider value={{ activeItem, setActiveItem, tabsId }}>
42
+ <div {...props} data-slot="tabs" dir={direction} ref={ref} />
43
+ </TabsContext.Provider>
44
+ )
45
+ },
46
+ )
47
+ Tabs.displayName = 'Tabs'
48
+
49
+ export interface TabsListProps extends Omit<React.HTMLProps<HTMLDivElement>, 'ref' | 'role'> {}
50
+
51
+ const TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(({ className, ...props }, ref) => (
52
+ <div
53
+ className={cn(
54
+ 'inline-flex w-fit items-center justify-center gap-2 rounded-md bg-muted p-1 text-muted-foreground',
55
+ className,
56
+ )}
57
+ ref={ref}
58
+ role="tablist"
59
+ {...props}
60
+ data-slot="tabs-list"
61
+ />
62
+ ))
63
+ TabsList.displayName = 'TabsList'
64
+
65
+ export interface TabsTriggerProps extends Omit<React.HTMLProps<HTMLButtonElement>, 'ref' | 'value'> {
66
+ value: string
67
+ defaultChecked?: boolean
68
+ }
69
+
70
+ const TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(
71
+ ({ className, children, defaultChecked, onClick, value, disabled, ...props }, ref) => {
72
+ const { setActiveItem, activeItem, tabsId } = useTabs()
73
+ const isActive = value === activeItem
74
+
75
+ React.useEffect(() => {
76
+ if (defaultChecked) setActiveItem(value)
77
+ }, [defaultChecked])
78
+
79
+ return (
80
+ <button
81
+ aria-controls={`${tabsId}-content-${value}`}
82
+ aria-selected={isActive}
83
+ className={cn(
84
+ 'relative inline-flex h-[29.04px] items-center justify-center gap-2 whitespace-nowrap rounded-sm px-3 font-medium text-sm ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
85
+ isActive && 'bg-background text-foreground shadow-sm',
86
+ disabled && 'pointer-events-none opacity-50',
87
+ className,
88
+ )}
89
+ data-value={value}
90
+ disabled={disabled}
91
+ id={`${tabsId}-trigger-${value}`}
92
+ onClick={(e) => {
93
+ setActiveItem(value)
94
+ onClick?.(e as React.MouseEvent<HTMLButtonElement>)
95
+ }}
96
+ ref={ref}
97
+ role="tab"
98
+ tabIndex={isActive ? 0 : -1}
99
+ {...props}
100
+ type="button"
101
+ data-slot="tabs-trigger">
102
+ {children}
103
+ </button>
104
+ )
105
+ },
106
+ )
107
+ TabsTrigger.displayName = 'TabsTrigger'
108
+
109
+ const TabsContent = React.forwardRef<
110
+ HTMLDivElement,
111
+ Omit<React.HTMLProps<HTMLDivElement>, 'ref'> & {
112
+ value: string
113
+ forceMount?: boolean
114
+ }
115
+ >(({ children, forceMount = false, className, value, ...props }, ref) => {
116
+ const { activeItem, tabsId } = useTabs()
117
+ const localRef = React.useRef<HTMLDivElement>(null)
118
+
119
+ return (
120
+ <div
121
+ aria-hidden={activeItem !== value}
122
+ aria-labelledby={`${tabsId}-trigger-${value}`}
123
+ className={cn(
124
+ 'mt-2 shrink-0 list-none ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
125
+ activeItem === value ? 'h-auto opacity-100' : 'h-0 opacity-0',
126
+ className,
127
+ )}
128
+ data-value={value}
129
+ hidden={activeItem !== value}
130
+ id={`${tabsId}-content-${value}`}
131
+ ref={(node) => {
132
+ ;(localRef as React.RefObject<HTMLDivElement | null>).current = node
133
+ if (typeof ref === 'function') {
134
+ ref(node)
135
+ } else if (ref) {
136
+ ref.current = node
137
+ }
138
+ }}
139
+ role="tabpanel"
140
+ tabIndex={-1}
141
+ {...props}
142
+ data-slot="tabs-content">
143
+ <MountMinimal forceMount={forceMount} open={activeItem === value} ref={null}>
144
+ {children}
145
+ </MountMinimal>
146
+ </div>
147
+ )
148
+ })
149
+ TabsContent.displayName = 'TabsContent'
150
+
151
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1 @@
1
+ export * from './textarea'
@@ -0,0 +1,24 @@
1
+ import { cn } from '@gentleduck/libs/cn'
2
+ import { type Direction, useDirection } from '@gentleduck/primitives/direction'
3
+ import * as React from 'react'
4
+
5
+ const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
6
+ ({ className, dir, ...props }, ref) => {
7
+ const direction = useDirection(dir as Direction)
8
+ return (
9
+ <textarea
10
+ className={cn(
11
+ 'field-sizing-content flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:disabled:bg-input/80',
12
+ className,
13
+ )}
14
+ data-slot="textarea"
15
+ dir={direction}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ )
20
+ },
21
+ )
22
+ Textarea.displayName = 'Textarea'
23
+
24
+ export { Textarea }
@@ -0,0 +1,2 @@
1
+ export * from './toggle'
2
+ export * from './toggle.constants'
@@ -0,0 +1,22 @@
1
+ import { cva } from '@gentleduck/variants'
2
+
3
+ export const toggleVariants = cva(
4
+ 'inline-flex items-center justify-center gap-2 rounded-md font-medium text-sm ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 data-disabled:pointer-events-none data-[state=on]:bg-accent data-[state=on]:text-accent-foreground data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
5
+ {
6
+ defaultVariants: {
7
+ size: 'default',
8
+ variant: 'default',
9
+ },
10
+ variants: {
11
+ size: {
12
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
13
+ lg: 'h-10 min-w-11 px-5 px-6 has-[>svg]:px-4',
14
+ sm: 'h-8 min-w-9 gap-1.5 px-2.5 px-3 has-[>svg]:px-2.5',
15
+ },
16
+ variant: {
17
+ default: 'bg-transparent',
18
+ outline: 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
19
+ },
20
+ },
21
+ },
22
+ )
@@ -0,0 +1,24 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@gentleduck/libs/cn'
4
+ import * as TogglePrimitive from '@gentleduck/primitives/toggle'
5
+ import type { VariantProps } from '@gentleduck/variants'
6
+ import * as React from 'react'
7
+ import { toggleVariants } from './toggle.constants'
8
+
9
+ const Toggle = React.forwardRef<
10
+ React.ComponentRef<typeof TogglePrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
12
+ >(({ className, variant = 'default', size = 'default', ...props }, ref) => {
13
+ return (
14
+ <TogglePrimitive.Root
15
+ className={cn(toggleVariants({ className, size, variant }))}
16
+ data-slot="toggle"
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ })
22
+ Toggle.displayName = 'Toggle'
23
+
24
+ export { Toggle }
@@ -0,0 +1 @@
1
+ export * from './toggle-group'