@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,170 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@gentleduck/libs/cn'
|
|
4
|
+
import { type Direction, useDirection } from '@gentleduck/primitives/direction'
|
|
5
|
+
import { cva, type VariantProps } from '@gentleduck/variants'
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
import { Button } from '../button'
|
|
8
|
+
import { Input } from '../input'
|
|
9
|
+
import { Textarea } from '../textarea'
|
|
10
|
+
|
|
11
|
+
const InputGroup = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
12
|
+
({ className, dir, children, ...props }, ref) => {
|
|
13
|
+
const direction = useDirection(dir as Direction)
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
className={cn(
|
|
17
|
+
'group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs outline-none transition-[color,box-shadow] dark:bg-input/30',
|
|
18
|
+
'h-9 has-[>textarea]:h-auto',
|
|
19
|
+
|
|
20
|
+
// Variants based on alignment.
|
|
21
|
+
'has-[>[data-align=inline-start]]:[&>input]:ps-2',
|
|
22
|
+
'has-[>[data-align=inline-end]]:[&>input]:pe-2',
|
|
23
|
+
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
|
24
|
+
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
|
25
|
+
|
|
26
|
+
// Focus state.
|
|
27
|
+
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50',
|
|
28
|
+
|
|
29
|
+
// Error state.
|
|
30
|
+
'has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
|
|
31
|
+
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
data-slot="input-group"
|
|
35
|
+
dir={direction}
|
|
36
|
+
ref={ref}
|
|
37
|
+
role="group"
|
|
38
|
+
{...props}>
|
|
39
|
+
{children}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
InputGroup.displayName = 'InputGroup'
|
|
45
|
+
|
|
46
|
+
const inputGroupAddonVariants = cva(
|
|
47
|
+
"flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
|
|
48
|
+
{
|
|
49
|
+
defaultVariants: {
|
|
50
|
+
align: 'inline-start',
|
|
51
|
+
},
|
|
52
|
+
variants: {
|
|
53
|
+
align: {
|
|
54
|
+
'block-end': 'order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3',
|
|
55
|
+
'block-start':
|
|
56
|
+
'order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3',
|
|
57
|
+
'inline-end': 'order-last pe-3 has-[>button]:me-[-0.45rem] has-[>kbd]:me-[-0.35rem]',
|
|
58
|
+
'inline-start': 'order-first ps-3 has-[>button]:ms-[-0.45rem] has-[>kbd]:ms-[-0.35rem]',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const InputGroupAddon = React.forwardRef<
|
|
65
|
+
HTMLDivElement,
|
|
66
|
+
React.ComponentPropsWithoutRef<'div'> & VariantProps<typeof inputGroupAddonVariants>
|
|
67
|
+
>(({ className, align = 'inline-start', ...props }, ref) => {
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
71
|
+
data-align={align}
|
|
72
|
+
data-slot="input-group-addon"
|
|
73
|
+
onClick={(e) => {
|
|
74
|
+
if ((e.target as HTMLElement).closest('button')) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
e.currentTarget.parentElement?.querySelector('input')?.focus()
|
|
78
|
+
}}
|
|
79
|
+
ref={ref}
|
|
80
|
+
role="group"
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
InputGroupAddon.displayName = 'InputGroupAddon'
|
|
86
|
+
|
|
87
|
+
const inputGroupButtonVariants = cva('flex items-center gap-2 text-sm shadow-none', {
|
|
88
|
+
defaultVariants: {
|
|
89
|
+
size: 'xs',
|
|
90
|
+
},
|
|
91
|
+
variants: {
|
|
92
|
+
size: {
|
|
93
|
+
'icon-sm': 'size-8 p-0 has-[>svg]:p-0',
|
|
94
|
+
'icon-xs': 'size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0',
|
|
95
|
+
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
|
|
96
|
+
sm: 'h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const InputGroupButton = React.forwardRef<
|
|
102
|
+
HTMLButtonElement,
|
|
103
|
+
Omit<React.ComponentPropsWithoutRef<typeof Button>, 'size'> & VariantProps<typeof inputGroupButtonVariants>
|
|
104
|
+
>(({ className, type = 'button', variant = 'ghost', size = 'xs', ...props }, ref) => {
|
|
105
|
+
return (
|
|
106
|
+
<Button
|
|
107
|
+
className={cn(inputGroupButtonVariants({ size }), className)}
|
|
108
|
+
data-size={size}
|
|
109
|
+
ref={ref}
|
|
110
|
+
type={type}
|
|
111
|
+
variant={variant}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
InputGroupButton.displayName = 'InputGroupButton'
|
|
117
|
+
|
|
118
|
+
const InputGroupText = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<'span'>>(
|
|
119
|
+
({ className, ...props }, ref) => {
|
|
120
|
+
return (
|
|
121
|
+
<span
|
|
122
|
+
className={cn(
|
|
123
|
+
"flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
|
|
124
|
+
className,
|
|
125
|
+
)}
|
|
126
|
+
ref={ref}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
InputGroupText.displayName = 'InputGroupText'
|
|
133
|
+
|
|
134
|
+
const InputGroupInput = React.forwardRef<HTMLInputElement, React.ComponentPropsWithoutRef<'input'>>(
|
|
135
|
+
({ className, dir, ...props }, ref) => {
|
|
136
|
+
const direction = useDirection(dir as Direction)
|
|
137
|
+
return (
|
|
138
|
+
<Input
|
|
139
|
+
className={cn(
|
|
140
|
+
'flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent',
|
|
141
|
+
className,
|
|
142
|
+
)}
|
|
143
|
+
dir={direction}
|
|
144
|
+
data-slot="input-group-control"
|
|
145
|
+
ref={ref}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
InputGroupInput.displayName = 'InputGroupInput'
|
|
152
|
+
|
|
153
|
+
const InputGroupTextarea = React.forwardRef<HTMLTextAreaElement, React.ComponentPropsWithoutRef<'textarea'>>(
|
|
154
|
+
({ className, ...props }, ref) => {
|
|
155
|
+
return (
|
|
156
|
+
<Textarea
|
|
157
|
+
className={cn(
|
|
158
|
+
'flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent',
|
|
159
|
+
className,
|
|
160
|
+
)}
|
|
161
|
+
data-slot="input-group-control"
|
|
162
|
+
ref={ref}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
InputGroupTextarea.displayName = 'InputGroupTextarea'
|
|
169
|
+
|
|
170
|
+
export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './input-otp'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@gentleduck/libs/cn'
|
|
4
|
+
import * as InputOTPPrimitive from '@gentleduck/primitives/input-otp'
|
|
5
|
+
import { Dot } from 'lucide-react'
|
|
6
|
+
import * as React from 'react'
|
|
7
|
+
|
|
8
|
+
const InputOTP = React.forwardRef<
|
|
9
|
+
React.ComponentRef<typeof InputOTPPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof InputOTPPrimitive.Root>
|
|
11
|
+
>(({ className, ...props }, ref) => (
|
|
12
|
+
<InputOTPPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn('flex items-center gap-2 disabled:cursor-not-allowed has-disabled:opacity-50', className)}
|
|
15
|
+
data-slot="input-otp"
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
))
|
|
19
|
+
InputOTP.displayName = 'InputOTP'
|
|
20
|
+
|
|
21
|
+
const InputOTPGroup = React.forwardRef<
|
|
22
|
+
React.ComponentRef<typeof InputOTPPrimitive.Group>,
|
|
23
|
+
React.ComponentPropsWithoutRef<typeof InputOTPPrimitive.Group>
|
|
24
|
+
>(({ className, ...props }, ref) => (
|
|
25
|
+
<InputOTPPrimitive.Group
|
|
26
|
+
ref={ref}
|
|
27
|
+
className={cn('flex items-center', className)}
|
|
28
|
+
data-slot="input-otp-group"
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
))
|
|
32
|
+
InputOTPGroup.displayName = 'InputOTPGroup'
|
|
33
|
+
|
|
34
|
+
const InputOTPSlot = React.forwardRef<
|
|
35
|
+
React.ComponentRef<typeof InputOTPPrimitive.Slot>,
|
|
36
|
+
React.ComponentPropsWithoutRef<typeof InputOTPPrimitive.Slot>
|
|
37
|
+
>(({ className, ...props }, ref) => (
|
|
38
|
+
<InputOTPPrimitive.Slot
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn(
|
|
41
|
+
'relative -ms-px h-10 w-10 rounded-none border border-input border-y border-e text-center text-sm transition-all first:ms-0 first:rounded-s-md last:rounded-e-md focus:relative focus:z-10 focus:outline-none focus:ring-2 focus:ring-ring',
|
|
42
|
+
className,
|
|
43
|
+
)}
|
|
44
|
+
data-slot="input-otp-slot"
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
))
|
|
48
|
+
InputOTPSlot.displayName = 'InputOTPSlot'
|
|
49
|
+
|
|
50
|
+
const InputOTPSeparator = React.forwardRef<
|
|
51
|
+
React.ComponentRef<typeof InputOTPPrimitive.Separator>,
|
|
52
|
+
React.ComponentPropsWithoutRef<typeof InputOTPPrimitive.Separator>
|
|
53
|
+
>(({ customIndicator, ...props }, ref) => (
|
|
54
|
+
<InputOTPPrimitive.Separator
|
|
55
|
+
ref={ref}
|
|
56
|
+
customIndicator={customIndicator ?? <Dot />}
|
|
57
|
+
data-slot="input-otp-separator"
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
))
|
|
61
|
+
InputOTPSeparator.displayName = 'InputOTPSeparator'
|
|
62
|
+
|
|
63
|
+
const REGEXP_ONLY_DIGITS_AND_CHARS = InputOTPPrimitive.REGEXP_ONLY_DIGITS_AND_CHARS
|
|
64
|
+
const REGEXP_ONLY_DIGITS = InputOTPPrimitive.REGEXP_ONLY_DIGITS
|
|
65
|
+
|
|
66
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator, REGEXP_ONLY_DIGITS_AND_CHARS, REGEXP_ONLY_DIGITS }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cva } from '@gentleduck/variants'
|
|
2
|
+
|
|
3
|
+
export const itemVariants = cva(
|
|
4
|
+
'group/item flex flex-wrap items-center rounded-md border border-transparent text-sm outline-none transition-colors duration-100 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [a]:transition-colors [a]:hover:bg-accent/50',
|
|
5
|
+
{
|
|
6
|
+
defaultVariants: {
|
|
7
|
+
size: 'default',
|
|
8
|
+
variant: 'default',
|
|
9
|
+
},
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
default: 'gap-4 p-4',
|
|
13
|
+
sm: 'gap-2.5 px-4 py-3',
|
|
14
|
+
},
|
|
15
|
+
variant: {
|
|
16
|
+
default: 'bg-transparent',
|
|
17
|
+
muted: 'bg-muted/50',
|
|
18
|
+
outline: 'border-border',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { cn } from '@gentleduck/libs/cn'
|
|
2
|
+
import { type Direction, useDirection } from '@gentleduck/primitives/direction'
|
|
3
|
+
import { Slot } from '@gentleduck/primitives/slot'
|
|
4
|
+
import { cva, type VariantProps } from '@gentleduck/variants'
|
|
5
|
+
import * as React from 'react'
|
|
6
|
+
import { Separator } from '../separator'
|
|
7
|
+
import { itemVariants } from './item.constants'
|
|
8
|
+
|
|
9
|
+
const ItemGroup = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
10
|
+
({ className, dir, ...props }, ref) => {
|
|
11
|
+
const direction = useDirection(dir as Direction)
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={cn('group/item-group flex flex-col', className)}
|
|
15
|
+
dir={direction}
|
|
16
|
+
data-slot="item-group"
|
|
17
|
+
ref={ref}
|
|
18
|
+
role="list"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
ItemGroup.displayName = 'ItemGroup'
|
|
25
|
+
|
|
26
|
+
const ItemSeparator = React.forwardRef<
|
|
27
|
+
React.ComponentRef<typeof Separator>,
|
|
28
|
+
React.ComponentPropsWithoutRef<typeof Separator>
|
|
29
|
+
>(({ className, ...props }, ref) => {
|
|
30
|
+
return (
|
|
31
|
+
<Separator
|
|
32
|
+
className={cn('my-0', className)}
|
|
33
|
+
data-slot="item-separator"
|
|
34
|
+
orientation="horizontal"
|
|
35
|
+
ref={ref}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
ItemSeparator.displayName = 'ItemSeparator'
|
|
41
|
+
|
|
42
|
+
const Item = React.forwardRef<
|
|
43
|
+
HTMLDivElement,
|
|
44
|
+
React.ComponentPropsWithoutRef<'div'> & VariantProps<typeof itemVariants> & { asChild?: boolean }
|
|
45
|
+
>(({ className, variant = 'default', size = 'default', asChild = false, ...props }, ref) => {
|
|
46
|
+
const Comp = asChild ? Slot : 'div'
|
|
47
|
+
return (
|
|
48
|
+
<Comp
|
|
49
|
+
className={cn(itemVariants({ className, size, variant }))}
|
|
50
|
+
data-size={size}
|
|
51
|
+
data-slot="item"
|
|
52
|
+
data-variant={variant}
|
|
53
|
+
ref={ref}
|
|
54
|
+
role="listitem"
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
Item.displayName = 'Item'
|
|
60
|
+
|
|
61
|
+
const itemMediaVariants = cva(
|
|
62
|
+
'flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none',
|
|
63
|
+
{
|
|
64
|
+
defaultVariants: {
|
|
65
|
+
variant: 'default',
|
|
66
|
+
},
|
|
67
|
+
variants: {
|
|
68
|
+
variant: {
|
|
69
|
+
default: 'bg-transparent',
|
|
70
|
+
icon: "size-8 rounded-sm border bg-muted [&_svg:not([class*='size-'])]:size-4",
|
|
71
|
+
image: 'size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const ItemMedia = React.forwardRef<
|
|
78
|
+
HTMLDivElement,
|
|
79
|
+
React.ComponentPropsWithoutRef<'div'> & VariantProps<typeof itemMediaVariants>
|
|
80
|
+
>(({ className, variant = 'default', ...props }, ref) => {
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
className={cn(itemMediaVariants({ className, variant }))}
|
|
84
|
+
data-slot="item-media"
|
|
85
|
+
data-variant={variant}
|
|
86
|
+
ref={ref}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
ItemMedia.displayName = 'ItemMedia'
|
|
92
|
+
|
|
93
|
+
const ItemContent = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
94
|
+
({ className, ...props }, ref) => {
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
className={cn('flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none', className)}
|
|
98
|
+
data-slot="item-content"
|
|
99
|
+
ref={ref}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
ItemContent.displayName = 'ItemContent'
|
|
106
|
+
|
|
107
|
+
const ItemTitle = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
108
|
+
({ className, ...props }, ref) => {
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className={cn('flex w-fit items-center gap-2 font-medium text-sm leading-snug', className)}
|
|
112
|
+
data-slot="item-title"
|
|
113
|
+
ref={ref}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
ItemTitle.displayName = 'ItemTitle'
|
|
120
|
+
|
|
121
|
+
const ItemDescription = React.forwardRef<HTMLParagraphElement, React.ComponentPropsWithoutRef<'p'>>(
|
|
122
|
+
({ className, ...props }, ref) => {
|
|
123
|
+
return (
|
|
124
|
+
<p
|
|
125
|
+
className={cn(
|
|
126
|
+
'line-clamp-2 text-balance font-normal text-muted-foreground text-sm leading-normal',
|
|
127
|
+
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
data-slot="item-description"
|
|
131
|
+
ref={ref}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
ItemDescription.displayName = 'ItemDescription'
|
|
138
|
+
|
|
139
|
+
const ItemActions = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
140
|
+
({ className, ...props }, ref) => {
|
|
141
|
+
return <div className={cn('flex items-center gap-2', className)} data-slot="item-actions" ref={ref} {...props} />
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
ItemActions.displayName = 'ItemActions'
|
|
145
|
+
|
|
146
|
+
const ItemHeader = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
147
|
+
({ className, ...props }, ref) => {
|
|
148
|
+
return (
|
|
149
|
+
<div
|
|
150
|
+
className={cn('flex basis-full items-center justify-between gap-2', className)}
|
|
151
|
+
data-slot="item-header"
|
|
152
|
+
ref={ref}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
)
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
ItemHeader.displayName = 'ItemHeader'
|
|
159
|
+
|
|
160
|
+
const ItemFooter = React.forwardRef<HTMLDivElement, React.ComponentPropsWithoutRef<'div'>>(
|
|
161
|
+
({ className, ...props }, ref) => {
|
|
162
|
+
return (
|
|
163
|
+
<div
|
|
164
|
+
className={cn('flex basis-full items-center justify-between gap-2', className)}
|
|
165
|
+
data-slot="item-footer"
|
|
166
|
+
ref={ref}
|
|
167
|
+
{...props}
|
|
168
|
+
/>
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
ItemFooter.displayName = 'ItemFooter'
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
Item,
|
|
176
|
+
ItemMedia,
|
|
177
|
+
ItemContent,
|
|
178
|
+
ItemActions,
|
|
179
|
+
ItemGroup,
|
|
180
|
+
ItemSeparator,
|
|
181
|
+
ItemTitle,
|
|
182
|
+
ItemDescription,
|
|
183
|
+
ItemHeader,
|
|
184
|
+
ItemFooter,
|
|
185
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import type { UseJsonEditorHotkeysOptions } from './json-editor.types'
|
|
3
|
+
|
|
4
|
+
export function useJsonEditorHotkeys(options: UseJsonEditorHotkeysOptions) {
|
|
5
|
+
return React.useCallback(
|
|
6
|
+
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
7
|
+
if (!options.enabled) return
|
|
8
|
+
|
|
9
|
+
if (event.key === 'Escape') {
|
|
10
|
+
event.preventDefault()
|
|
11
|
+
options.onEscape()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
|
|
15
|
+
event.preventDefault()
|
|
16
|
+
options.onSave()
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
[options],
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { JsonParseResult } from './json-editor.types'
|
|
2
|
+
|
|
3
|
+
export function safeStringify(value: unknown): string {
|
|
4
|
+
try {
|
|
5
|
+
if (value === null || value === undefined) return ''
|
|
6
|
+
if (typeof value === 'string') return value
|
|
7
|
+
return JSON.stringify(value, null, 2)
|
|
8
|
+
} catch {
|
|
9
|
+
return ''
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function tryParseJson(text: string): JsonParseResult {
|
|
14
|
+
const raw = text.trim()
|
|
15
|
+
if (!raw) return { ok: true, value: null }
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
return { ok: true, value: JSON.parse(raw) }
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return { ok: false, message: error instanceof Error ? error.message : 'Invalid JSON' }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatJson(text: string): { ok: true; formatted: string } | { ok: false; message: string } {
|
|
29
|
+
const parsed = tryParseJson(text)
|
|
30
|
+
if (!parsed.ok) return parsed
|
|
31
|
+
if (parsed.value === null) return { ok: true, formatted: '' }
|
|
32
|
+
|
|
33
|
+
return { ok: true, formatted: JSON.stringify(parsed.value, null, 2) }
|
|
34
|
+
}
|