@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,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,2 @@
1
+ export * from './item'
2
+ export * from './item.constants'
@@ -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,4 @@
1
+ export * from './json-editor'
2
+ export * from './json-editor.hooks'
3
+ export * from './json-editor.libs'
4
+ export * from './json-editor.types'
@@ -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
+ }