@gunjo/ui 0.0.1-alpha.0 → 0.0.1-alpha.2

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 (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +90 -0
  3. package/README.md +52 -91
  4. package/package.json +47 -6
  5. package/src/components/display/Accordion.tsx +185 -0
  6. package/src/components/display/AccordionGroup.tsx +155 -0
  7. package/src/components/display/ActionDataTable.tsx +413 -0
  8. package/src/components/display/ActivityTimelineCard.tsx +483 -0
  9. package/src/components/display/AnalyticsCard.tsx +167 -0
  10. package/src/components/display/AssetCard.tsx +242 -0
  11. package/src/components/display/AssetGrid.tsx +164 -0
  12. package/src/components/display/Avatar.tsx +127 -0
  13. package/src/components/display/AvatarGroup.tsx +131 -0
  14. package/src/components/{atoms → display}/Badge.tsx +3 -3
  15. package/src/components/display/BarChart.tsx +247 -0
  16. package/src/components/{molecules → display}/Card.tsx +1 -1
  17. package/src/components/display/Carousel.tsx +593 -0
  18. package/src/components/display/ChartLegend.tsx +124 -0
  19. package/src/components/display/ChatMessage.tsx +382 -0
  20. package/src/components/display/ChoroplethMap.tsx +613 -0
  21. package/src/components/display/Code.tsx +42 -0
  22. package/src/components/display/CodeBlock.tsx +338 -0
  23. package/src/components/display/ColorSwatch.tsx +71 -0
  24. package/src/components/display/ConcentricProgressCard.tsx +545 -0
  25. package/src/components/display/DataTable.tsx +522 -0
  26. package/src/components/display/DistributionBar.tsx +102 -0
  27. package/src/components/display/DocNote.tsx +36 -0
  28. package/src/components/display/DonutChart.tsx +257 -0
  29. package/src/components/display/EmptyState.tsx +44 -0
  30. package/src/components/display/FileTree.tsx +180 -0
  31. package/src/components/display/GaugeChart.tsx +219 -0
  32. package/src/components/display/HeatmapChart.tsx +266 -0
  33. package/src/components/display/Icon.tsx +66 -0
  34. package/src/components/display/ImagePreview.tsx +140 -0
  35. package/src/components/{atoms → display}/Img.tsx +46 -12
  36. package/src/components/display/LabeledDonutCard.tsx +475 -0
  37. package/src/components/display/LineChart.tsx +464 -0
  38. package/src/components/{molecules → display}/List.tsx +20 -13
  39. package/src/components/display/MarkdownRenderer.tsx +157 -0
  40. package/src/components/display/MetadataList.tsx +81 -0
  41. package/src/components/display/MiniDistributionBarCard.tsx +314 -0
  42. package/src/components/display/PieChart.tsx +234 -0
  43. package/src/components/display/QuadrantMatrix.tsx +330 -0
  44. package/src/components/display/RadarChart.tsx +335 -0
  45. package/src/components/display/RadialBarChart.tsx +264 -0
  46. package/src/components/display/RetentionCohortCard.tsx +350 -0
  47. package/src/components/display/RibbonChart.tsx +618 -0
  48. package/src/components/display/SearchableAccordion.tsx +270 -0
  49. package/src/components/display/SegmentTimelineCard.tsx +452 -0
  50. package/src/components/display/SegmentedGaugeCard.tsx +607 -0
  51. package/src/components/display/Spacer.tsx +51 -0
  52. package/src/components/display/SparklineChart.tsx +394 -0
  53. package/src/components/display/StackedBarChart.tsx +393 -0
  54. package/src/components/display/Statistic.tsx +70 -0
  55. package/src/components/{molecules → display}/Table.tsx +22 -7
  56. package/src/components/display/Tag.tsx +80 -0
  57. package/src/components/display/TagEditor.tsx +141 -0
  58. package/src/components/display/Timeline.tsx +121 -0
  59. package/src/components/{atoms → display}/ToolPill.tsx +42 -18
  60. package/src/components/display/TreeView.tsx +226 -0
  61. package/src/components/display/chart-tooltip.tsx +423 -0
  62. package/src/components/display/chart-utils.ts +71 -0
  63. package/src/components/display/circular-chart-utils.ts +147 -0
  64. package/src/components/display/generated/default-variant-keys.ts +90 -0
  65. package/src/components/display/generated/variant-keys.ts +169 -0
  66. package/src/components/{atoms → feedback}/Alert.tsx +12 -5
  67. package/src/components/feedback/Banner.tsx +90 -0
  68. package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
  69. package/src/components/feedback/ProgressWidget.tsx +44 -0
  70. package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
  71. package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
  72. package/src/components/feedback/StatusScreen.tsx +148 -0
  73. package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
  74. package/src/components/feedback/Toast.tsx +108 -0
  75. package/src/components/feedback/ToastProvider.tsx +78 -0
  76. package/src/components/feedback/generated/default-variant-keys.ts +16 -0
  77. package/src/components/feedback/generated/variant-keys.ts +21 -0
  78. package/src/components/generated/component-manifest.ts +1568 -454
  79. package/src/components/generated/component-style-hints.ts +1958 -718
  80. package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
  81. package/src/components/inputs/Calendar.tsx +212 -0
  82. package/src/components/inputs/ChatComposer.tsx +75 -0
  83. package/src/components/inputs/ChatInput.tsx +528 -0
  84. package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
  85. package/src/components/inputs/Combobox.tsx +175 -0
  86. package/src/components/inputs/CopyButton.tsx +187 -0
  87. package/src/components/inputs/DatePicker.tsx +519 -0
  88. package/src/components/inputs/DateRangePicker.tsx +878 -0
  89. package/src/components/inputs/EditableField.tsx +182 -0
  90. package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
  91. package/src/components/inputs/FilterButton.tsx +163 -0
  92. package/src/components/{molecules → inputs}/Form.tsx +20 -3
  93. package/src/components/{atoms → inputs}/Input.tsx +2 -0
  94. package/src/components/inputs/InputOTP.tsx +75 -0
  95. package/src/components/inputs/Mention.tsx +279 -0
  96. package/src/components/inputs/NumberInput.tsx +109 -0
  97. package/src/components/inputs/PasswordGroup.tsx +138 -0
  98. package/src/components/inputs/PasswordInput.tsx +74 -0
  99. package/src/components/inputs/PasswordRequirementList.tsx +96 -0
  100. package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
  101. package/src/components/inputs/PhoneInput.tsx +99 -0
  102. package/src/components/inputs/PostalCodeInput.tsx +98 -0
  103. package/src/components/inputs/RangeSlider.tsx +129 -0
  104. package/src/components/inputs/SearchInput.tsx +76 -0
  105. package/src/components/inputs/Select.tsx +39 -0
  106. package/src/components/{atoms → inputs}/Slider.tsx +18 -5
  107. package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
  108. package/src/components/{atoms → inputs}/Switch.tsx +15 -4
  109. package/src/components/inputs/TagInput.tsx +114 -0
  110. package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
  111. package/src/components/inputs/TimePicker.tsx +150 -0
  112. package/src/components/inputs/Toggle.tsx +48 -0
  113. package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
  114. package/src/components/inputs/TooltipButton.tsx +148 -0
  115. package/src/components/inputs/VoiceInputButton.tsx +317 -0
  116. package/src/components/inputs/calendar-holidays.ts +56 -0
  117. package/src/components/inputs/generated/default-variant-keys.ts +32 -0
  118. package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
  119. package/src/components/layout/AspectRatio.tsx +12 -0
  120. package/src/components/layout/AssetInspectorPanel.tsx +416 -0
  121. package/src/components/layout/Cluster.tsx +56 -0
  122. package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
  123. package/src/components/layout/Container.tsx +43 -0
  124. package/src/components/layout/DeviceFrame.tsx +227 -0
  125. package/src/components/layout/Grid.tsx +65 -0
  126. package/src/components/layout/HStack.tsx +73 -0
  127. package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
  128. package/src/components/layout/MarqueeFrame.tsx +158 -0
  129. package/src/components/layout/Resizable.tsx +94 -0
  130. package/src/components/layout/ScrollArea.tsx +71 -0
  131. package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
  132. package/src/components/layout/VStack.tsx +69 -0
  133. package/src/components/layout/generated/default-variant-keys.ts +16 -0
  134. package/src/components/layout/generated/variant-keys.ts +21 -0
  135. package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
  136. package/src/components/navigation/Command.tsx +266 -0
  137. package/src/components/navigation/CommandPalette.tsx +83 -0
  138. package/src/components/navigation/DocumentPager.tsx +171 -0
  139. package/src/components/navigation/Footer.tsx +88 -0
  140. package/src/components/navigation/Header.tsx +80 -0
  141. package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
  142. package/src/components/navigation/NavigationMenu.tsx +128 -0
  143. package/src/components/navigation/PageAside.tsx +84 -0
  144. package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
  145. package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
  146. package/src/components/navigation/Sidebar.tsx +223 -0
  147. package/src/components/navigation/SidebarItem.tsx +160 -0
  148. package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
  149. package/src/components/navigation/TextLink.tsx +71 -0
  150. package/src/components/navigation/generated/default-variant-keys.ts +12 -0
  151. package/src/components/navigation/generated/variant-keys.ts +13 -0
  152. package/src/components/overlay/AIChatInput.tsx +5 -0
  153. package/src/components/overlay/AIChatMessage.tsx +6 -0
  154. package/src/components/overlay/AlertDialog.tsx +145 -0
  155. package/src/components/overlay/ChatPanel.tsx +180 -0
  156. package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
  157. package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
  158. package/src/components/overlay/Drawer.tsx +131 -0
  159. package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
  160. package/src/components/overlay/FloatingPanel.tsx +90 -0
  161. package/src/components/overlay/HoverCard.tsx +36 -0
  162. package/src/components/overlay/MediaLightbox.tsx +403 -0
  163. package/src/components/overlay/MediaPickerDialog.tsx +198 -0
  164. package/src/components/overlay/Modal.tsx +103 -0
  165. package/src/components/overlay/OnboardingFlow.tsx +172 -0
  166. package/src/components/overlay/Popover.tsx +36 -0
  167. package/src/components/overlay/ShareModal.tsx +324 -0
  168. package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
  169. package/src/components/overlay/Tooltip.tsx +130 -0
  170. package/src/components/overlay/generated/default-variant-keys.ts +14 -0
  171. package/src/components/overlay/generated/variant-keys.ts +17 -0
  172. package/src/components/patterns/BlogTemplate.tsx +46 -0
  173. package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
  174. package/src/components/patterns/DocsTemplate.tsx +41 -0
  175. package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
  176. package/src/components/patterns/OnboardingTemplate.tsx +32 -0
  177. package/src/components/patterns/PricingTemplate.tsx +106 -0
  178. package/src/globals.css +173 -22
  179. package/src/index.ts +177 -76
  180. package/tailwind-theme-extend.cjs +48 -3
  181. package/design/atoms-metadata.json +0 -82
  182. package/design/molecules-metadata.json +0 -130
  183. package/design/organisms-metadata.json +0 -38
  184. package/design/templates-metadata.json +0 -38
  185. package/src/components/atoms/Avatar.tsx +0 -57
  186. package/src/components/atoms/Select.tsx +0 -28
  187. package/src/components/atoms/generated/default-variant-keys.ts +0 -36
  188. package/src/components/molecules/AIChatInput.tsx +0 -140
  189. package/src/components/molecules/AIChatMessage.tsx +0 -109
  190. package/src/components/molecules/Accordion.tsx +0 -99
  191. package/src/components/molecules/Calendar.tsx +0 -60
  192. package/src/components/molecules/Carousel.tsx +0 -261
  193. package/src/components/molecules/Command.tsx +0 -152
  194. package/src/components/molecules/FilterButton.tsx +0 -133
  195. package/src/components/molecules/HoverCard.tsx +0 -29
  196. package/src/components/molecules/Modal.tsx +0 -66
  197. package/src/components/molecules/Popover.tsx +0 -31
  198. package/src/components/molecules/ProgressWidget.tsx +0 -40
  199. package/src/components/molecules/Resizable.tsx +0 -47
  200. package/src/components/molecules/ScrollArea.tsx +0 -48
  201. package/src/components/molecules/SidebarItem.tsx +0 -134
  202. package/src/components/molecules/Toast.tsx +0 -57
  203. package/src/components/molecules/Tooltip.tsx +0 -30
  204. package/src/components/molecules/generated/default-variant-keys.ts +0 -22
  205. package/src/components/molecules/generated/variant-keys.ts +0 -33
  206. package/src/components/organisms/CommandPalette.tsx +0 -58
  207. package/src/components/organisms/FloatingPanel.tsx +0 -46
  208. package/src/components/organisms/ShareModal.tsx +0 -182
  209. package/src/components/organisms/ToastProvider.tsx +0 -49
  210. /package/src/components/{atoms → display}/Kbd.tsx +0 -0
  211. /package/src/components/{atoms → display}/Separator.tsx +0 -0
  212. /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
  213. /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
  214. /package/src/components/{atoms → inputs}/Button.tsx +0 -0
  215. /package/src/components/{atoms → inputs}/Label.tsx +0 -0
  216. /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
  217. /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
  218. /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
  219. /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
  220. /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
  221. /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
  222. /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
  223. /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
  224. /package/src/components/{templates → patterns}/SettingsTemplate.tsx +0 -0
@@ -0,0 +1,175 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconCheck as Check, IconSelector as ChevronsUpDown, IconX as X } from "@tabler/icons-react";
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Button } from "../inputs/Button"
8
+ import {
9
+ Command,
10
+ CommandEmpty,
11
+ CommandGroup,
12
+ CommandInput,
13
+ CommandItem,
14
+ CommandList,
15
+ } from "../navigation/Command"
16
+ import { Popover, PopoverContent, PopoverTrigger } from "../overlay/Popover"
17
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
18
+
19
+ export interface ComboboxOption {
20
+ value: string
21
+ label: string
22
+ disabled?: boolean
23
+ disabledReason?: string
24
+ }
25
+
26
+ export interface ComboboxProps {
27
+ id?: string
28
+ options: ComboboxOption[]
29
+ value?: string
30
+ onValueChange?: (value: string) => void
31
+ placeholder?: string
32
+ searchPlaceholder?: string
33
+ searchClearLabel?: string
34
+ emptyMessage?: string
35
+ className?: string
36
+ triggerClassName?: string
37
+ disabled?: boolean
38
+ clearable?: boolean
39
+ clearLabel?: string
40
+ }
41
+
42
+ const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
43
+ (
44
+ {
45
+ id,
46
+ options,
47
+ value,
48
+ onValueChange,
49
+ placeholder = "Select option...",
50
+ searchPlaceholder = "Search...",
51
+ searchClearLabel = "Clear search",
52
+ emptyMessage = "No option found.",
53
+ className,
54
+ triggerClassName,
55
+ disabled,
56
+ clearable = true,
57
+ clearLabel = "Clear selection",
58
+ },
59
+ ref
60
+ ) => {
61
+ const [open, setOpen] = React.useState(false)
62
+ const selected = options.find((option) => option.value === value)
63
+ const canClear = clearable && Boolean(selected) && !disabled
64
+
65
+ return (
66
+ <Popover open={open} onOpenChange={setOpen}>
67
+ <div className="relative">
68
+ <PopoverTrigger asChild>
69
+ <Button
70
+ id={id}
71
+ ref={ref}
72
+ variant="outline"
73
+ role="combobox"
74
+ aria-expanded={open}
75
+ disabled={disabled}
76
+ data-slot="combobox"
77
+ className={cn(
78
+ "w-full justify-between",
79
+ canClear ? "pr-16" : "pr-10",
80
+ !selected && "text-muted-foreground",
81
+ triggerClassName
82
+ )}
83
+ >
84
+ <span className="min-w-0 flex-1 truncate text-left">
85
+ {selected ? selected.label : placeholder}
86
+ </span>
87
+ <ChevronsUpDown className="absolute right-3 h-4 w-4 shrink-0 opacity-50" />
88
+ </Button>
89
+ </PopoverTrigger>
90
+ {canClear ? (
91
+ <Tooltip>
92
+ <TooltipTrigger asChild>
93
+ <button
94
+ type="button"
95
+ className="absolute right-9 top-1/2 z-10 flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
96
+ aria-label={clearLabel}
97
+ onClick={(event) => {
98
+ event.preventDefault()
99
+ event.stopPropagation()
100
+ onValueChange?.("")
101
+ setOpen(false)
102
+ }}
103
+ >
104
+ <X className="h-3.5 w-3.5" />
105
+ </button>
106
+ </TooltipTrigger>
107
+ <TooltipContent>{clearLabel}</TooltipContent>
108
+ </Tooltip>
109
+ ) : null}
110
+ </div>
111
+ <PopoverContent className={cn("w-[var(--radix-popover-trigger-width)] p-0", className)}>
112
+ <Command>
113
+ <CommandInput
114
+ placeholder={searchPlaceholder}
115
+ clearable
116
+ clearLabel={searchClearLabel}
117
+ />
118
+ <CommandList>
119
+ <CommandEmpty>{emptyMessage}</CommandEmpty>
120
+ <CommandGroup>
121
+ {options.map((option) => {
122
+ const item = (
123
+ <CommandItem
124
+ key={option.value}
125
+ value={option.value}
126
+ disabled={option.disabled}
127
+ aria-disabled={option.disabled}
128
+ className={cn(
129
+ option.disabled &&
130
+ "cursor-not-allowed opacity-50 aria-selected:bg-transparent aria-selected:text-foreground"
131
+ )}
132
+ onSelect={() => {
133
+ if (option.disabled) return
134
+ onValueChange?.(
135
+ option.value === value ? "" : option.value
136
+ )
137
+ setOpen(false)
138
+ }}
139
+ >
140
+ <Check
141
+ className={cn(
142
+ "mr-2 h-4 w-4",
143
+ value === option.value
144
+ ? "opacity-100"
145
+ : "opacity-0"
146
+ )}
147
+ />
148
+ <span className="min-w-0 flex-1 truncate">{option.label}</span>
149
+ </CommandItem>
150
+ )
151
+
152
+ if (!option.disabled || !option.disabledReason) return item
153
+
154
+ return (
155
+ <Tooltip key={option.value}>
156
+ <TooltipTrigger asChild>
157
+ <span className="block" aria-label={option.disabledReason}>
158
+ {item}
159
+ </span>
160
+ </TooltipTrigger>
161
+ <TooltipContent>{option.disabledReason}</TooltipContent>
162
+ </Tooltip>
163
+ )
164
+ })}
165
+ </CommandGroup>
166
+ </CommandList>
167
+ </Command>
168
+ </PopoverContent>
169
+ </Popover>
170
+ )
171
+ }
172
+ )
173
+ Combobox.displayName = "Combobox"
174
+
175
+ export { Combobox }
@@ -0,0 +1,187 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconCheck as Check, IconCopy as Copy } from "@tabler/icons-react"
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Icon } from "../display/Icon"
8
+ import type { IconVariantKey } from "../display/generated/variant-keys"
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
10
+ import { Button } from "./Button"
11
+ import { copyButtonDefaultVariantKey } from "./generated/default-variant-keys"
12
+ import type { CopyButtonVariantKey } from "./generated/variant-keys"
13
+ import type { TooltipButtonProps } from "./TooltipButton"
14
+
15
+ export interface CopyButtonProps
16
+ extends Omit<TooltipButtonProps, "children" | "tooltip" | "onClick" | "variant"> {
17
+ /** Text copied to the clipboard. */
18
+ value: string
19
+ /** Visual feedback style for the copy action. */
20
+ variant?: CopyButtonVariantKey
21
+ /** Button surface variant forwarded to TooltipButton. Default ghost. */
22
+ buttonVariant?: TooltipButtonProps["variant"]
23
+ /** Accessible label and tooltip before copying. */
24
+ copyLabel?: string
25
+ /** Accessible label and tooltip after copying. */
26
+ copiedLabel?: string
27
+ /** Accessible label and tooltip when copying fails. */
28
+ copyFailedLabel?: string
29
+ /** Duration in milliseconds to keep copied feedback visible. Default 5000. */
30
+ copiedDuration?: number
31
+ iconSize?: IconVariantKey
32
+ onCopied?: () => void
33
+ onCopyError?: (error: unknown) => void
34
+ }
35
+
36
+ const copyButtonFeedbackByVariant: Record<CopyButtonVariantKey, "icon" | "label"> = {
37
+ default: "icon",
38
+ label: "label",
39
+ }
40
+
41
+ async function copyTextToClipboard(value: string) {
42
+ if (navigator.clipboard?.writeText) {
43
+ try {
44
+ await navigator.clipboard.writeText(value)
45
+ return
46
+ } catch {
47
+ // Browser previews can deny Clipboard API access; keep the UI usable with the legacy fallback.
48
+ }
49
+ }
50
+
51
+ const textarea = document.createElement("textarea")
52
+ textarea.value = value
53
+ textarea.setAttribute("readonly", "")
54
+ textarea.className = "sr-only"
55
+ document.body.appendChild(textarea)
56
+ textarea.select()
57
+ document.execCommand("copy")
58
+ document.body.removeChild(textarea)
59
+ }
60
+
61
+ const CopyButton = React.forwardRef<HTMLButtonElement, CopyButtonProps>(
62
+ (
63
+ {
64
+ value,
65
+ copyLabel = "Copy",
66
+ copiedLabel = "Copied",
67
+ copyFailedLabel = "Copy failed",
68
+ copiedDuration = 5000,
69
+ variant = copyButtonDefaultVariantKey,
70
+ buttonVariant = "ghost",
71
+ iconSize = "xs",
72
+ onCopied,
73
+ onCopyError,
74
+ className,
75
+ size,
76
+ type = "button",
77
+ tooltipSide,
78
+ tooltipAlign,
79
+ tooltipSideOffset,
80
+ tooltipContentClassName,
81
+ ...props
82
+ },
83
+ ref
84
+ ) => {
85
+ const [state, setState] = React.useState<"idle" | "copied" | "failed">("idle")
86
+ const [feedbackOpen, setFeedbackOpen] = React.useState(false)
87
+ const timeoutRef = React.useRef<number | null>(null)
88
+
89
+ React.useEffect(
90
+ () => () => {
91
+ if (timeoutRef.current) {
92
+ window.clearTimeout(timeoutRef.current)
93
+ }
94
+ },
95
+ []
96
+ )
97
+
98
+ const showCopied = state === "copied"
99
+ const showFailed = state === "failed"
100
+ const resolvedFeedback = copyButtonFeedbackByVariant[variant]
101
+ const feedbackLabel = showCopied ? copiedLabel : showFailed ? copyFailedLabel : copyLabel
102
+
103
+ const handleCopy = async () => {
104
+ try {
105
+ await copyTextToClipboard(value)
106
+ setState("copied")
107
+ setFeedbackOpen(true)
108
+ onCopied?.()
109
+ } catch (error) {
110
+ setState("failed")
111
+ setFeedbackOpen(true)
112
+ onCopyError?.(error)
113
+ }
114
+
115
+ if (timeoutRef.current) {
116
+ window.clearTimeout(timeoutRef.current)
117
+ }
118
+ timeoutRef.current = window.setTimeout(() => {
119
+ setState("idle")
120
+ setFeedbackOpen(false)
121
+ }, copiedDuration)
122
+ }
123
+
124
+ return (
125
+ <Tooltip>
126
+ <span className="relative inline-flex">
127
+ <TooltipTrigger asChild>
128
+ <Button
129
+ ref={ref}
130
+ type={type}
131
+ variant={buttonVariant}
132
+ size={size ?? (resolvedFeedback === "label" ? "sm" : "icon")}
133
+ onClick={handleCopy}
134
+ aria-label={copyLabel}
135
+ className={cn(
136
+ resolvedFeedback === "label" ? "w-auto gap-1.5" : null,
137
+ className
138
+ )}
139
+ {...props}
140
+ >
141
+ <span className="relative inline-grid shrink-0 place-items-center">
142
+ <Icon
143
+ icon={Copy}
144
+ size={iconSize}
145
+ className={cn(
146
+ "transition-all duration-200 ease-out motion-reduce:transition-none",
147
+ showCopied ? "scale-75 rotate-6 opacity-0" : "scale-100 rotate-0 opacity-100"
148
+ )}
149
+ />
150
+ <Icon
151
+ icon={Check}
152
+ size={iconSize}
153
+ className={cn(
154
+ "absolute transition-all duration-200 ease-out motion-reduce:transition-none",
155
+ showCopied ? "scale-100 rotate-0 opacity-100" : "scale-75 -rotate-6 opacity-0"
156
+ )}
157
+ />
158
+ </span>
159
+ {resolvedFeedback === "label" ? <span>{feedbackLabel}</span> : null}
160
+ </Button>
161
+ </TooltipTrigger>
162
+ {feedbackOpen ? (
163
+ <span
164
+ className="pointer-events-none absolute bottom-full left-1/2 z-[60] mb-2 w-max max-w-xs -translate-x-1/2 rounded-md border bg-popover px-3 py-1.5 text-center text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 motion-reduce:animate-none"
165
+ role="status"
166
+ >
167
+ {feedbackLabel}
168
+ </span>
169
+ ) : null}
170
+ </span>
171
+ {!feedbackOpen ? (
172
+ <TooltipContent
173
+ side={tooltipSide}
174
+ align={tooltipAlign}
175
+ sideOffset={tooltipSideOffset}
176
+ className={tooltipContentClassName}
177
+ >
178
+ {copyLabel}
179
+ </TooltipContent>
180
+ ) : null}
181
+ </Tooltip>
182
+ )
183
+ }
184
+ )
185
+ CopyButton.displayName = "CopyButton"
186
+
187
+ export { CopyButton }