@djangocfg/ui-core 1.0.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 (88) hide show
  1. package/README.md +135 -0
  2. package/package.json +111 -0
  3. package/src/components/accordion.tsx +56 -0
  4. package/src/components/alert-dialog.tsx +142 -0
  5. package/src/components/alert.tsx +59 -0
  6. package/src/components/aspect-ratio.tsx +7 -0
  7. package/src/components/avatar.tsx +50 -0
  8. package/src/components/badge.tsx +36 -0
  9. package/src/components/button-group.tsx +85 -0
  10. package/src/components/button.tsx +111 -0
  11. package/src/components/calendar.tsx +213 -0
  12. package/src/components/card.tsx +76 -0
  13. package/src/components/carousel.tsx +261 -0
  14. package/src/components/chart.tsx +369 -0
  15. package/src/components/checkbox.tsx +29 -0
  16. package/src/components/collapsible.tsx +11 -0
  17. package/src/components/combobox.tsx +182 -0
  18. package/src/components/command.tsx +170 -0
  19. package/src/components/context-menu.tsx +200 -0
  20. package/src/components/copy.tsx +144 -0
  21. package/src/components/dialog.tsx +122 -0
  22. package/src/components/drawer.tsx +137 -0
  23. package/src/components/empty.tsx +104 -0
  24. package/src/components/field.tsx +244 -0
  25. package/src/components/form.tsx +178 -0
  26. package/src/components/hover-card.tsx +29 -0
  27. package/src/components/image-with-fallback.tsx +170 -0
  28. package/src/components/index.ts +86 -0
  29. package/src/components/input-group.tsx +170 -0
  30. package/src/components/input-otp.tsx +81 -0
  31. package/src/components/input.tsx +22 -0
  32. package/src/components/item.tsx +195 -0
  33. package/src/components/kbd.tsx +28 -0
  34. package/src/components/label.tsx +26 -0
  35. package/src/components/multi-select.tsx +222 -0
  36. package/src/components/og-image.tsx +47 -0
  37. package/src/components/popover.tsx +33 -0
  38. package/src/components/portal.tsx +106 -0
  39. package/src/components/preloader.tsx +250 -0
  40. package/src/components/progress.tsx +28 -0
  41. package/src/components/radio-group.tsx +43 -0
  42. package/src/components/resizable.tsx +111 -0
  43. package/src/components/scroll-area.tsx +102 -0
  44. package/src/components/section.tsx +58 -0
  45. package/src/components/select.tsx +158 -0
  46. package/src/components/separator.tsx +31 -0
  47. package/src/components/sheet.tsx +140 -0
  48. package/src/components/skeleton.tsx +15 -0
  49. package/src/components/slider.tsx +28 -0
  50. package/src/components/spinner.tsx +16 -0
  51. package/src/components/sticky.tsx +117 -0
  52. package/src/components/switch.tsx +29 -0
  53. package/src/components/table.tsx +120 -0
  54. package/src/components/tabs.tsx +238 -0
  55. package/src/components/textarea.tsx +22 -0
  56. package/src/components/toast.tsx +129 -0
  57. package/src/components/toaster.tsx +41 -0
  58. package/src/components/toggle-group.tsx +61 -0
  59. package/src/components/toggle.tsx +45 -0
  60. package/src/components/token-icon.tsx +156 -0
  61. package/src/components/tooltip-provider-safe.tsx +43 -0
  62. package/src/components/tooltip.tsx +32 -0
  63. package/src/hooks/index.ts +15 -0
  64. package/src/hooks/useCopy.ts +41 -0
  65. package/src/hooks/useCountdown.ts +73 -0
  66. package/src/hooks/useDebounce.ts +25 -0
  67. package/src/hooks/useDebouncedCallback.ts +58 -0
  68. package/src/hooks/useDebugTools.ts +52 -0
  69. package/src/hooks/useEventsBus.ts +53 -0
  70. package/src/hooks/useImageLoader.ts +95 -0
  71. package/src/hooks/useMediaQuery.ts +40 -0
  72. package/src/hooks/useMobile.tsx +22 -0
  73. package/src/hooks/useToast.ts +194 -0
  74. package/src/index.ts +14 -0
  75. package/src/lib/index.ts +2 -0
  76. package/src/lib/og-image.ts +151 -0
  77. package/src/lib/utils.ts +6 -0
  78. package/src/styles/base.css +20 -0
  79. package/src/styles/globals.css +12 -0
  80. package/src/styles/index.css +25 -0
  81. package/src/styles/sources.css +11 -0
  82. package/src/styles/theme/animations.css +65 -0
  83. package/src/styles/theme/dark.css +49 -0
  84. package/src/styles/theme/light.css +50 -0
  85. package/src/styles/theme/tokens.css +134 -0
  86. package/src/styles/theme.css +22 -0
  87. package/src/styles/utilities.css +187 -0
  88. package/src/types/index.ts +0 -0
@@ -0,0 +1,170 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { type DialogProps } from "@radix-ui/react-dialog"
5
+ import { Command as CommandPrimitive } from "cmdk"
6
+ import { cn } from "../lib/utils"
7
+ import { Dialog, DialogContent } from "./dialog"
8
+ import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
9
+
10
+ const Command = React.forwardRef<
11
+ React.ElementRef<typeof CommandPrimitive>,
12
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
13
+ >(({ className, ...props }, ref) => (
14
+ <CommandPrimitive
15
+ ref={ref}
16
+ className={cn(
17
+ "flex h-full w-full flex-col rounded-md bg-popover text-popover-foreground",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ))
23
+ Command.displayName = CommandPrimitive.displayName
24
+
25
+ const CommandDialog = ({ children, ...props }: DialogProps) => {
26
+ return (
27
+ <Dialog {...props}>
28
+ <DialogContent className="overflow-hidden p-0">
29
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
30
+ {children}
31
+ </Command>
32
+ </DialogContent>
33
+ </Dialog>
34
+ )
35
+ }
36
+
37
+ const CommandInput = React.forwardRef<
38
+ React.ElementRef<typeof CommandPrimitive.Input>,
39
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
40
+ >(({ className, ...props }, ref) => (
41
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
42
+ <MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
43
+ <CommandPrimitive.Input
44
+ ref={ref}
45
+ className={cn(
46
+ "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ </div>
52
+ ))
53
+
54
+ CommandInput.displayName = CommandPrimitive.Input.displayName
55
+
56
+ const CommandList = React.forwardRef<
57
+ React.ElementRef<typeof CommandPrimitive.List>,
58
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
59
+ >(({ className, style, ...props }, ref) => {
60
+ const listRef = React.useRef<HTMLDivElement>(null)
61
+
62
+ React.useImperativeHandle(ref, () => listRef.current as HTMLDivElement)
63
+
64
+ React.useEffect(() => {
65
+ if (listRef.current) {
66
+ listRef.current.style.cssText += 'max-height: 300px !important; overflow-y: auto !important; overflow-x: hidden !important;'
67
+ }
68
+ }, [])
69
+
70
+ return (
71
+ <CommandPrimitive.List
72
+ ref={listRef as any}
73
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
74
+ style={{
75
+ maxHeight: '300px',
76
+ overflowY: 'auto',
77
+ overflowX: 'hidden',
78
+ ...style
79
+ }}
80
+ {...props}
81
+ />
82
+ )
83
+ })
84
+
85
+ CommandList.displayName = CommandPrimitive.List.displayName
86
+
87
+ const CommandEmpty = React.forwardRef<
88
+ React.ElementRef<typeof CommandPrimitive.Empty>,
89
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
90
+ >((props, ref) => (
91
+ <CommandPrimitive.Empty
92
+ ref={ref}
93
+ className="py-6 text-center text-sm"
94
+ {...props}
95
+ />
96
+ ))
97
+
98
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
99
+
100
+ const CommandGroup = React.forwardRef<
101
+ React.ElementRef<typeof CommandPrimitive.Group>,
102
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
103
+ >(({ className, ...props }, ref) => (
104
+ <CommandPrimitive.Group
105
+ ref={ref}
106
+ className={cn(
107
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
108
+ className
109
+ )}
110
+ {...props}
111
+ />
112
+ ))
113
+
114
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
115
+
116
+ const CommandSeparator = React.forwardRef<
117
+ React.ElementRef<typeof CommandPrimitive.Separator>,
118
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
119
+ >(({ className, ...props }, ref) => (
120
+ <CommandPrimitive.Separator
121
+ ref={ref}
122
+ className={cn("-mx-1 h-px bg-border", className)}
123
+ {...props}
124
+ />
125
+ ))
126
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
127
+
128
+ const CommandItem = React.forwardRef<
129
+ React.ElementRef<typeof CommandPrimitive.Item>,
130
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
131
+ >(({ className, ...props }, ref) => (
132
+ <CommandPrimitive.Item
133
+ ref={ref}
134
+ className={cn(
135
+ "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
136
+ className
137
+ )}
138
+ {...props}
139
+ />
140
+ ))
141
+
142
+ CommandItem.displayName = CommandPrimitive.Item.displayName
143
+
144
+ const CommandShortcut = ({
145
+ className,
146
+ ...props
147
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
148
+ return (
149
+ <span
150
+ className={cn(
151
+ "ml-auto text-xs tracking-widest text-muted-foreground",
152
+ className
153
+ )}
154
+ {...props}
155
+ />
156
+ )
157
+ }
158
+ CommandShortcut.displayName = "CommandShortcut"
159
+
160
+ export {
161
+ Command,
162
+ CommandDialog,
163
+ CommandInput,
164
+ CommandList,
165
+ CommandEmpty,
166
+ CommandGroup,
167
+ CommandItem,
168
+ CommandShortcut,
169
+ CommandSeparator,
170
+ }
@@ -0,0 +1,200 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
5
+ import { cn } from "../lib/utils"
6
+ import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
7
+
8
+ const ContextMenu = ContextMenuPrimitive.Root
9
+
10
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
11
+
12
+ const ContextMenuGroup = ContextMenuPrimitive.Group
13
+
14
+ const ContextMenuPortal = ContextMenuPrimitive.Portal
15
+
16
+ const ContextMenuSub = ContextMenuPrimitive.Sub
17
+
18
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
19
+
20
+ const ContextMenuSubTrigger = React.forwardRef<
21
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
22
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
23
+ inset?: boolean
24
+ }
25
+ >(({ className, inset, children, ...props }, ref) => (
26
+ <ContextMenuPrimitive.SubTrigger
27
+ ref={ref}
28
+ className={cn(
29
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
30
+ inset && "pl-8",
31
+ className
32
+ )}
33
+ {...props}
34
+ >
35
+ {children}
36
+ <ChevronRightIcon className="ml-auto h-4 w-4" />
37
+ </ContextMenuPrimitive.SubTrigger>
38
+ ))
39
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
40
+
41
+ const ContextMenuSubContent = React.forwardRef<
42
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
43
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
44
+ >(({ className, ...props }, ref) => (
45
+ <ContextMenuPrimitive.SubContent
46
+ ref={ref}
47
+ className={cn(
48
+ "z-150 min-w-32 overflow-hidden rounded-sm border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ ))
54
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
55
+
56
+ const ContextMenuContent = React.forwardRef<
57
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
58
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
59
+ >(({ className, ...props }, ref) => (
60
+ <ContextMenuPrimitive.Portal>
61
+ <ContextMenuPrimitive.Content
62
+ ref={ref}
63
+ className={cn(
64
+ "z-150 max-h-[--radix-context-menu-content-available-height] min-w-32 overflow-y-auto overflow-x-hidden rounded-sm border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
65
+ className
66
+ )}
67
+ {...props}
68
+ />
69
+ </ContextMenuPrimitive.Portal>
70
+ ))
71
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
72
+
73
+ const ContextMenuItem = React.forwardRef<
74
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
75
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
76
+ inset?: boolean
77
+ key?: React.Key
78
+ }
79
+ >(({ className, inset, ...props }, ref) => (
80
+ <ContextMenuPrimitive.Item
81
+ ref={ref}
82
+ className={cn(
83
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
84
+ inset && "pl-8",
85
+ className
86
+ )}
87
+ {...props}
88
+ />
89
+ ))
90
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
91
+
92
+ const ContextMenuCheckboxItem = React.forwardRef<
93
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
94
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem> & { key?: React.Key }
95
+ >(({ className, children, checked, ...props }, ref) => (
96
+ <ContextMenuPrimitive.CheckboxItem
97
+ ref={ref}
98
+ className={cn(
99
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
100
+ className
101
+ )}
102
+ checked={checked}
103
+ {...props}
104
+ >
105
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
106
+ <ContextMenuPrimitive.ItemIndicator>
107
+ <CheckIcon className="h-4 w-4" />
108
+ </ContextMenuPrimitive.ItemIndicator>
109
+ </span>
110
+ {children}
111
+ </ContextMenuPrimitive.CheckboxItem>
112
+ ))
113
+ ContextMenuCheckboxItem.displayName =
114
+ ContextMenuPrimitive.CheckboxItem.displayName
115
+
116
+ const ContextMenuRadioItem = React.forwardRef<
117
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
118
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem> & { key?: React.Key }
119
+ >(({ className, children, ...props }, ref) => (
120
+ <ContextMenuPrimitive.RadioItem
121
+ ref={ref}
122
+ className={cn(
123
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
124
+ className
125
+ )}
126
+ {...props}
127
+ >
128
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
129
+ <ContextMenuPrimitive.ItemIndicator>
130
+ <DotFilledIcon className="h-4 w-4 fill-current" />
131
+ </ContextMenuPrimitive.ItemIndicator>
132
+ </span>
133
+ {children}
134
+ </ContextMenuPrimitive.RadioItem>
135
+ ))
136
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
137
+
138
+ const ContextMenuLabel = React.forwardRef<
139
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
140
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
141
+ inset?: boolean
142
+ }
143
+ >(({ className, inset, ...props }, ref) => (
144
+ <ContextMenuPrimitive.Label
145
+ ref={ref}
146
+ className={cn(
147
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
148
+ inset && "pl-8",
149
+ className
150
+ )}
151
+ {...props}
152
+ />
153
+ ))
154
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
155
+
156
+ const ContextMenuSeparator = React.forwardRef<
157
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
158
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
159
+ >(({ className, ...props }, ref) => (
160
+ <ContextMenuPrimitive.Separator
161
+ ref={ref}
162
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
163
+ {...props}
164
+ />
165
+ ))
166
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
167
+
168
+ const ContextMenuShortcut = ({
169
+ className,
170
+ ...props
171
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
172
+ return (
173
+ <span
174
+ className={cn(
175
+ "ml-auto text-xs tracking-widest text-muted-foreground",
176
+ className
177
+ )}
178
+ {...props}
179
+ />
180
+ )
181
+ }
182
+ ContextMenuShortcut.displayName = "ContextMenuShortcut"
183
+
184
+ export {
185
+ ContextMenu,
186
+ ContextMenuTrigger,
187
+ ContextMenuContent,
188
+ ContextMenuItem,
189
+ ContextMenuCheckboxItem,
190
+ ContextMenuRadioItem,
191
+ ContextMenuLabel,
192
+ ContextMenuSeparator,
193
+ ContextMenuShortcut,
194
+ ContextMenuGroup,
195
+ ContextMenuPortal,
196
+ ContextMenuSub,
197
+ ContextMenuSubContent,
198
+ ContextMenuSubTrigger,
199
+ ContextMenuRadioGroup,
200
+ }
@@ -0,0 +1,144 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Check, Copy } from 'lucide-react'
5
+ import { cn } from '../lib/utils'
6
+ import { Button } from './button'
7
+
8
+ // =============================================================================
9
+ // CopyButton - Copy button with icon feedback
10
+ // =============================================================================
11
+
12
+ export interface CopyButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'onCopy'> {
13
+ /** Text to copy to clipboard */
14
+ value: string
15
+ /** Duration in ms to show success state (default: 2000) */
16
+ successDuration?: number
17
+ /** Callback after successful copy */
18
+ onCopy?: (value: string) => void
19
+ /** Button size variant */
20
+ size?: 'default' | 'sm' | 'icon'
21
+ /** Button style variant */
22
+ variant?: 'default' | 'ghost' | 'outline'
23
+ /** Optional label text (if provided, shows text next to icon) */
24
+ children?: React.ReactNode
25
+ /** Icon size class (default: "h-4 w-4") */
26
+ iconClassName?: string
27
+ }
28
+
29
+ const CopyButton = React.forwardRef<HTMLButtonElement, CopyButtonProps>(
30
+ ({
31
+ value,
32
+ successDuration = 2000,
33
+ onCopy,
34
+ className,
35
+ size = 'icon',
36
+ variant = 'ghost',
37
+ children,
38
+ iconClassName = 'h-4 w-4',
39
+ ...props
40
+ }, ref) => {
41
+ const [copied, setCopied] = React.useState(false)
42
+
43
+ const handleCopy = async () => {
44
+ try {
45
+ await navigator.clipboard.writeText(value)
46
+ setCopied(true)
47
+ onCopy?.(value)
48
+ setTimeout(() => setCopied(false), successDuration)
49
+ } catch (err) {
50
+ console.error('Failed to copy:', err)
51
+ }
52
+ }
53
+
54
+ const hasLabel = !!children
55
+
56
+ return (
57
+ <Button
58
+ ref={ref}
59
+ type="button"
60
+ size={hasLabel ? size : 'icon'}
61
+ variant={variant}
62
+ onClick={handleCopy}
63
+ className={cn('shrink-0', className)}
64
+ aria-label={copied ? 'Copied' : 'Copy to clipboard'}
65
+ {...props}
66
+ >
67
+ {copied ? (
68
+ <Check className={cn(iconClassName, 'text-green-500')} />
69
+ ) : (
70
+ <Copy className={iconClassName} />
71
+ )}
72
+ {hasLabel && <span className={copied ? 'text-green-500' : undefined}>{copied ? 'Copied' : children}</span>}
73
+ </Button>
74
+ )
75
+ }
76
+ )
77
+ CopyButton.displayName = 'CopyButton'
78
+
79
+ // =============================================================================
80
+ // CopyField - Field with label, text display, and copy button
81
+ // =============================================================================
82
+
83
+ export interface CopyFieldProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children' | 'onCopy'> {
84
+ /** Label text (optional) */
85
+ label?: string
86
+ /** Text to display and copy */
87
+ value: string
88
+ /** Duration in ms to show success state */
89
+ successDuration?: number
90
+ /** Callback after successful copy */
91
+ onCopy?: (value: string) => void
92
+ /** Show as monospace font */
93
+ mono?: boolean
94
+ /** Truncate long text */
95
+ truncate?: boolean
96
+ }
97
+
98
+ const CopyField = React.forwardRef<HTMLDivElement, CopyFieldProps>(
99
+ ({
100
+ label,
101
+ value,
102
+ successDuration = 2000,
103
+ onCopy,
104
+ className,
105
+ mono = true,
106
+ truncate = true,
107
+ ...props
108
+ }, ref) => {
109
+ return (
110
+ <div
111
+ ref={ref}
112
+ className={cn(
113
+ 'flex items-center justify-between gap-2 p-3 rounded-lg bg-muted',
114
+ className
115
+ )}
116
+ {...props}
117
+ >
118
+ <div className="min-w-0 flex-1">
119
+ {label && (
120
+ <div className="text-xs text-muted-foreground mb-0.5">{label}</div>
121
+ )}
122
+ <div
123
+ className={cn(
124
+ 'text-sm',
125
+ mono && 'font-mono',
126
+ truncate && 'truncate'
127
+ )}
128
+ title={value}
129
+ >
130
+ {value}
131
+ </div>
132
+ </div>
133
+ <CopyButton
134
+ value={value}
135
+ successDuration={successDuration}
136
+ onCopy={onCopy}
137
+ />
138
+ </div>
139
+ )
140
+ }
141
+ )
142
+ CopyField.displayName = 'CopyField'
143
+
144
+ export { CopyButton, CopyField }
@@ -0,0 +1,122 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { cn } from "../lib/utils"
6
+ import { Cross2Icon } from "@radix-ui/react-icons"
7
+
8
+ const Dialog = DialogPrimitive.Root
9
+
10
+ const DialogTrigger = DialogPrimitive.Trigger
11
+
12
+ const DialogPortal = DialogPrimitive.Portal
13
+
14
+ const DialogClose = DialogPrimitive.Close
15
+
16
+ const DialogOverlay = React.forwardRef<
17
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
18
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
19
+ >(({ className, style, ...props }, ref) => (
20
+ <DialogPrimitive.Overlay
21
+ ref={ref}
22
+ className={cn(
23
+ "fixed inset-0 z-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
24
+ className
25
+ )}
26
+ style={{ backgroundColor: 'rgb(0 0 0 / 0.8)', ...style }}
27
+ {...props}
28
+ />
29
+ ))
30
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31
+
32
+ const DialogContent = React.forwardRef<
33
+ React.ElementRef<typeof DialogPrimitive.Content>,
34
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
35
+ >(({ className, children, ...props }, ref) => (
36
+ <DialogPortal>
37
+ <DialogOverlay />
38
+ <DialogPrimitive.Content
39
+ ref={ref}
40
+ className={cn(
41
+ "fixed left-1/2 top-1/2 z-200 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg",
42
+ className
43
+ )}
44
+ {...props}
45
+ >
46
+ {children}
47
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
48
+ <Cross2Icon className="h-4 w-4" />
49
+ <span className="sr-only">Close</span>
50
+ </DialogPrimitive.Close>
51
+ </DialogPrimitive.Content>
52
+ </DialogPortal>
53
+ ))
54
+ DialogContent.displayName = DialogPrimitive.Content.displayName
55
+
56
+ const DialogHeader = ({
57
+ className,
58
+ ...props
59
+ }: React.HTMLAttributes<HTMLDivElement>) => (
60
+ <div
61
+ className={cn(
62
+ "flex flex-col space-y-1.5 text-center sm:text-left",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ )
68
+ DialogHeader.displayName = "DialogHeader"
69
+
70
+ const DialogFooter = ({
71
+ className,
72
+ ...props
73
+ }: React.HTMLAttributes<HTMLDivElement>) => (
74
+ <div
75
+ className={cn(
76
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ )
82
+ DialogFooter.displayName = "DialogFooter"
83
+
84
+ const DialogTitle = React.forwardRef<
85
+ React.ElementRef<typeof DialogPrimitive.Title>,
86
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
87
+ >(({ className, ...props }, ref) => (
88
+ <DialogPrimitive.Title
89
+ ref={ref}
90
+ className={cn(
91
+ "text-lg font-semibold leading-none tracking-tight",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ ))
97
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
98
+
99
+ const DialogDescription = React.forwardRef<
100
+ React.ElementRef<typeof DialogPrimitive.Description>,
101
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
102
+ >(({ className, ...props }, ref) => (
103
+ <DialogPrimitive.Description
104
+ ref={ref}
105
+ className={cn("text-sm text-muted-foreground", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
110
+
111
+ export {
112
+ Dialog,
113
+ DialogPortal,
114
+ DialogOverlay,
115
+ DialogTrigger,
116
+ DialogClose,
117
+ DialogContent,
118
+ DialogHeader,
119
+ DialogFooter,
120
+ DialogTitle,
121
+ DialogDescription,
122
+ }