@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.
- package/CHANGELOG.md +62 -0
- package/index.css +3 -0
- package/package.json +59 -0
- package/src/_old/_table/index.ts +5 -0
- package/src/_old/_table/table-advanced.constants.tsx +24 -0
- package/src/_old/_table/table-advanced.tsx +311 -0
- package/src/_old/_table/table-advanced.types.ts +272 -0
- package/src/_old/_table/table.constants.ts +2 -0
- package/src/_old/_table/table.hook.tsx +115 -0
- package/src/_old/_table/table.lib.ts +85 -0
- package/src/_old/_table/table.tsx +916 -0
- package/src/_old/_table/table.types.ts +118 -0
- package/src/_old/_table/todo.md +11 -0
- package/src/_old/_upload/index.ts +9 -0
- package/src/_old/_upload/todo.md +38 -0
- package/src/_old/_upload/upload-advanced-chunks.tsx +1624 -0
- package/src/_old/_upload/upload-advanced.tsx +507 -0
- package/src/_old/_upload/upload-sonner.tsx +58 -0
- package/src/_old/_upload/upload.assets.tsx +239 -0
- package/src/_old/_upload/upload.constants.tsx +75 -0
- package/src/_old/_upload/upload.dto.ts +19 -0
- package/src/_old/_upload/upload.lib.tsx +630 -0
- package/src/_old/_upload/upload.tsx +491 -0
- package/src/_old/_upload/upload.types.ts +436 -0
- package/src/accordion/accordion.tsx +247 -0
- package/src/accordion/index.ts +1 -0
- package/src/alert/alert.constants.ts +17 -0
- package/src/alert/alert.tsx +52 -0
- package/src/alert/index.ts +2 -0
- package/src/alert-dialog/alert-dialog.tsx +107 -0
- package/src/alert-dialog/index.ts +1 -0
- package/src/aspect-ratio/aspect-ratio.tsx +33 -0
- package/src/aspect-ratio/index.ts +1 -0
- package/src/audio/audio-record.tsx +776 -0
- package/src/audio/audio-visualizer.tsx +377 -0
- package/src/audio/audio.libs.ts +5 -0
- package/src/audio/audio.types.ts +50 -0
- package/src/audio/index.ts +2 -0
- package/src/avatar/avatar.tsx +78 -0
- package/src/avatar/index.ts +1 -0
- package/src/badge/badge.constants.ts +38 -0
- package/src/badge/badge.tsx +19 -0
- package/src/badge/index.ts +2 -0
- package/src/breadcrumb/breadcrumb.tsx +119 -0
- package/src/breadcrumb/index.ts +1 -0
- package/src/button/button.constants.ts +44 -0
- package/src/button/button.tsx +79 -0
- package/src/button/button.types.ts +38 -0
- package/src/button/index.ts +3 -0
- package/src/button-group/button-group.constants.ts +26 -0
- package/src/button-group/button-group.tsx +65 -0
- package/src/button-group/index.ts +2 -0
- package/src/calendar/calendar.tsx +191 -0
- package/src/calendar/index.ts +1 -0
- package/src/card/card.tsx +81 -0
- package/src/card/index.ts +1 -0
- package/src/carousel/carousel.tsx +211 -0
- package/src/carousel/carousel.types.ts +23 -0
- package/src/carousel/index.ts +2 -0
- package/src/chart/chart.libs.ts +27 -0
- package/src/chart/chart.tsx +260 -0
- package/src/chart/chart.types.ts +38 -0
- package/src/chart/index.ts +3 -0
- package/src/checkbox/checkbox.tsx +144 -0
- package/src/checkbox/checkbox.types.ts +24 -0
- package/src/checkbox/index.ts +2 -0
- package/src/collapsible/collapsible.tsx +151 -0
- package/src/collapsible/index.ts +1 -0
- package/src/combobox/combobox.tsx +132 -0
- package/src/combobox/index.ts +1 -0
- package/src/command/command.tsx +192 -0
- package/src/command/command.types.ts +11 -0
- package/src/command/index.ts +2 -0
- package/src/context-menu/context-menu.tsx +178 -0
- package/src/context-menu/index.ts +1 -0
- package/src/dialog/dialog-responsive.tsx +137 -0
- package/src/dialog/dialog.tsx +97 -0
- package/src/dialog/index.ts +2 -0
- package/src/direction/direction.tsx +13 -0
- package/src/direction/index.ts +1 -0
- package/src/drawer/drawer.tsx +185 -0
- package/src/drawer/index.ts +1 -0
- package/src/dropdown-menu/dropdown-menu.tsx +181 -0
- package/src/dropdown-menu/index.ts +1 -0
- package/src/empty/empty.constants.ts +15 -0
- package/src/empty/empty.tsx +73 -0
- package/src/empty/index.ts +2 -0
- package/src/field/field.constants.ts +22 -0
- package/src/field/field.tsx +203 -0
- package/src/field/index.ts +2 -0
- package/src/hover-card/hover-card.tsx +79 -0
- package/src/hover-card/index.ts +1 -0
- package/src/input/index.ts +1 -0
- package/src/input/input.tsx +45 -0
- package/src/input-group/index.ts +1 -0
- package/src/input-group/input-group.tsx +170 -0
- package/src/input-otp/index.ts +1 -0
- package/src/input-otp/input-otp.tsx +66 -0
- package/src/item/index.ts +2 -0
- package/src/item/item.constants.ts +22 -0
- package/src/item/item.tsx +185 -0
- package/src/json-editor/index.ts +4 -0
- package/src/json-editor/json-editor.hooks.ts +21 -0
- package/src/json-editor/json-editor.libs.ts +34 -0
- package/src/json-editor/json-editor.tsx +425 -0
- package/src/json-editor/json-editor.types.ts +80 -0
- package/src/json-editor/json-editor.view.tsx +110 -0
- package/src/json-editor/json-text-area.tsx +7 -0
- package/src/kbd/index.ts +1 -0
- package/src/kbd/kbd.tsx +39 -0
- package/src/label/index.ts +1 -0
- package/src/label/label.tsx +28 -0
- package/src/menubar/index.ts +1 -0
- package/src/menubar/menubar.tsx +213 -0
- package/src/navigation-menu/index.ts +1 -0
- package/src/navigation-menu/navigation-menu.tsx +152 -0
- package/src/pagination/index.ts +2 -0
- package/src/pagination/pagination.tsx +191 -0
- package/src/pagination/pagination.types.ts +17 -0
- package/src/popover/index.ts +1 -0
- package/src/popover/popover.tsx +35 -0
- package/src/preview-panel/index.ts +3 -0
- package/src/preview-panel/preview-panel-dialog.tsx +99 -0
- package/src/preview-panel/preview-panel.tsx +389 -0
- package/src/preview-panel/preview-panel.types.ts +49 -0
- package/src/progress/index.ts +1 -0
- package/src/progress/progress.tsx +32 -0
- package/src/radio-group/index.ts +1 -0
- package/src/radio-group/radio-group.tsx +92 -0
- package/src/resizable/index.ts +1 -0
- package/src/resizable/resizable.tsx +52 -0
- package/src/scroll-area/index.ts +1 -0
- package/src/scroll-area/scroll-area.tsx +30 -0
- package/src/select/index.ts +1 -0
- package/src/select/select.tsx +138 -0
- package/src/separator/index.ts +1 -0
- package/src/separator/separator.tsx +28 -0
- package/src/sheet/index.ts +2 -0
- package/src/sheet/sheet.constants.tsx +20 -0
- package/src/sheet/sheet.tsx +92 -0
- package/src/sidebar/index.ts +4 -0
- package/src/sidebar/sidebar.constants.ts +30 -0
- package/src/sidebar/sidebar.hooks.ts +13 -0
- package/src/sidebar/sidebar.tsx +676 -0
- package/src/sidebar/sidebar.types.ts +28 -0
- package/src/skeleton/index.ts +1 -0
- package/src/skeleton/skeleton.tsx +22 -0
- package/src/slider/index.ts +1 -0
- package/src/slider/slider.tsx +57 -0
- package/src/sonner/index.ts +4 -0
- package/src/sonner/sonner.chunks.tsx +80 -0
- package/src/sonner/sonner.libs.ts +13 -0
- package/src/sonner/sonner.tsx +31 -0
- package/src/sonner/sonner.types.ts +9 -0
- package/src/switch/index.ts +1 -0
- package/src/switch/switch.tsx +63 -0
- package/src/table/index.ts +1 -0
- package/src/table/table.tsx +95 -0
- package/src/tabs/index.ts +1 -0
- package/src/tabs/tabs.tsx +151 -0
- package/src/textarea/index.ts +1 -0
- package/src/textarea/textarea.tsx +24 -0
- package/src/toggle/index.ts +2 -0
- package/src/toggle/toggle.constants.ts +22 -0
- package/src/toggle/toggle.tsx +24 -0
- package/src/toggle-group/index.ts +1 -0
- package/src/toggle-group/toggle-group.tsx +69 -0
- package/src/tooltip/index.ts +1 -0
- package/src/tooltip/tooltip.tsx +32 -0
- package/src/upload/index.ts +1 -0
- package/src/upload/upload.constants.tsx +19 -0
- package/src/upload/upload.libs.ts +97 -0
- package/src/upload/upload.tsx +340 -0
- package/src/upload/upload.types.ts +44 -0
- 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,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,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'
|