@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,338 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import { CopyButton } from "../inputs/CopyButton"
7
+
8
+ type CodeBlockTheme = "dark" | "light" | "muted"
9
+
10
+ export interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {
11
+ /** Code text to display. */
12
+ code: string
13
+ /** Optional filename shown in the header. If omitted, header is hidden. */
14
+ filename?: string
15
+ /** Language label shown next to the filename. */
16
+ language?: string
17
+ /** Show a copy-to-clipboard button. Default true. */
18
+ copyable?: boolean
19
+ /** Accessible label and tooltip for the copy button. */
20
+ copyLabel?: string
21
+ /** Label shown briefly after copying. */
22
+ copiedLabel?: string
23
+ /** Duration in milliseconds to keep the copied feedback visible. Default 5000. */
24
+ copiedDuration?: number
25
+ /** Show line numbers next to each line. */
26
+ showLineNumbers?: boolean
27
+ /** Apply lightweight syntax highlighting for common source tokens. */
28
+ highlight?: boolean
29
+ /** Allow users to edit the source inside the code block. */
30
+ editable?: boolean
31
+ /** Called when editable source changes. */
32
+ onCodeChange?: (code: string) => void
33
+ /** Select all source text when the code area is double-clicked. Default true. */
34
+ selectOnDoubleClick?: boolean
35
+ /** Visual theme for the code block surface. */
36
+ theme?: CodeBlockTheme
37
+ }
38
+
39
+ const KEYWORDS = new Set([
40
+ "as",
41
+ "async",
42
+ "await",
43
+ "break",
44
+ "case",
45
+ "catch",
46
+ "class",
47
+ "const",
48
+ "continue",
49
+ "default",
50
+ "else",
51
+ "export",
52
+ "extends",
53
+ "false",
54
+ "finally",
55
+ "for",
56
+ "from",
57
+ "function",
58
+ "if",
59
+ "import",
60
+ "in",
61
+ "interface",
62
+ "let",
63
+ "new",
64
+ "null",
65
+ "return",
66
+ "switch",
67
+ "true",
68
+ "try",
69
+ "type",
70
+ "undefined",
71
+ "var",
72
+ "while",
73
+ ])
74
+
75
+ const TOKEN_PATTERN =
76
+ /(\/\/.*$|\/\*[\s\S]*?\*\/|`(?:\\.|[^`])*`|"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|\b\d+(?:\.\d+)?\b|\b[A-Za-z_$][\w$]*\b)/gm
77
+
78
+ const themeClasses: Record<
79
+ CodeBlockTheme,
80
+ {
81
+ root: string
82
+ header: string
83
+ meta: string
84
+ badge: string
85
+ button: string
86
+ surface: string
87
+ lineNumber: string
88
+ textarea: string
89
+ keyword: string
90
+ string: string
91
+ comment: string
92
+ number: string
93
+ identifier: string
94
+ }
95
+ > = {
96
+ dark: {
97
+ root: "border-[hsl(var(--pure-white)/0.14)] bg-[hsl(var(--pure-black))] text-[hsl(var(--pure-white))]",
98
+ header: "border-[hsl(var(--pure-white)/0.14)] bg-[hsl(var(--pure-black))]",
99
+ meta: "text-[hsl(var(--pure-white)/0.7)]",
100
+ badge: "border-[hsl(var(--pure-white)/0.2)] bg-[hsl(var(--pure-white)/0.08)] text-[hsl(var(--pure-white)/0.84)]",
101
+ button: "text-[hsl(var(--pure-white)/0.7)] hover:bg-[hsl(var(--pure-white)/0.1)] hover:text-[hsl(var(--pure-white))]",
102
+ surface: "bg-[hsl(var(--pure-black))]",
103
+ lineNumber: "border-[hsl(var(--pure-white)/0.16)] text-[hsl(var(--pure-white)/0.45)]",
104
+ textarea: "text-[hsl(var(--pure-white))] caret-[hsl(var(--pure-white))] placeholder:text-[hsl(var(--pure-white)/0.45)]",
105
+ keyword: "text-info",
106
+ string: "text-success",
107
+ comment: "text-[hsl(var(--pure-white)/0.55)]",
108
+ number: "text-warning",
109
+ identifier: "text-[hsl(var(--pure-white))]",
110
+ },
111
+ light: {
112
+ root: "border-border bg-background text-foreground",
113
+ header: "border-border bg-muted/40",
114
+ meta: "text-muted-foreground",
115
+ badge: "border-border bg-background text-muted-foreground",
116
+ button: "text-muted-foreground hover:bg-muted hover:text-foreground",
117
+ surface: "bg-background",
118
+ lineNumber: "border-border text-muted-foreground",
119
+ textarea: "text-foreground caret-foreground placeholder:text-muted-foreground",
120
+ keyword: "text-info",
121
+ string: "text-success",
122
+ comment: "text-muted-foreground",
123
+ number: "text-warning",
124
+ identifier: "text-foreground",
125
+ },
126
+ muted: {
127
+ root: "border-border bg-muted/40 text-foreground",
128
+ header: "border-border bg-muted/60",
129
+ meta: "text-muted-foreground",
130
+ badge: "border-border bg-background/70 text-muted-foreground",
131
+ button: "text-muted-foreground hover:bg-background/80 hover:text-foreground",
132
+ surface: "bg-muted/20",
133
+ lineNumber: "border-border text-muted-foreground",
134
+ textarea: "text-foreground caret-foreground placeholder:text-muted-foreground",
135
+ keyword: "text-info",
136
+ string: "text-success",
137
+ comment: "text-muted-foreground",
138
+ number: "text-warning",
139
+ identifier: "text-foreground",
140
+ },
141
+ }
142
+
143
+ function renderHighlightedLine(line: string, theme: (typeof themeClasses)[CodeBlockTheme]) {
144
+ const nodes: React.ReactNode[] = []
145
+ let cursor = 0
146
+ const pattern = new RegExp(TOKEN_PATTERN)
147
+ let match: RegExpExecArray | null
148
+
149
+ while ((match = pattern.exec(line)) !== null) {
150
+ const token = match[0]
151
+ const start = match.index
152
+ if (start > cursor) {
153
+ nodes.push(line.slice(cursor, start))
154
+ }
155
+
156
+ let className = theme.identifier
157
+ if (token.startsWith("//") || token.startsWith("/*")) {
158
+ className = theme.comment
159
+ } else if (token.startsWith("\"") || token.startsWith("'") || token.startsWith("`")) {
160
+ className = theme.string
161
+ } else if (/^\d/.test(token)) {
162
+ className = theme.number
163
+ } else if (KEYWORDS.has(token) || line.slice(0, start).endsWith("<") || line.slice(0, start).endsWith("</")) {
164
+ className = theme.keyword
165
+ }
166
+
167
+ nodes.push(
168
+ <span key={`${start}-${token}`} className={className}>
169
+ {token}
170
+ </span>
171
+ )
172
+ cursor = start + token.length
173
+ }
174
+
175
+ if (cursor < line.length) {
176
+ nodes.push(line.slice(cursor))
177
+ }
178
+
179
+ return nodes.length ? nodes : " "
180
+ }
181
+
182
+ const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
183
+ (
184
+ {
185
+ className,
186
+ code,
187
+ filename,
188
+ language,
189
+ copyable = true,
190
+ copyLabel = "Copy code",
191
+ copiedLabel = "Copied",
192
+ copiedDuration = 5000,
193
+ showLineNumbers = false,
194
+ highlight = false,
195
+ editable = false,
196
+ onCodeChange,
197
+ selectOnDoubleClick = true,
198
+ theme = "dark",
199
+ ...props
200
+ },
201
+ ref
202
+ ) => {
203
+ const [internalCode, setInternalCode] = React.useState(code)
204
+ const codeRef = React.useRef<HTMLElement>(null)
205
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null)
206
+ const showHeader = !!filename || !!language
207
+ const styles = themeClasses[theme]
208
+ const currentCode = onCodeChange ? code : internalCode
209
+ const lines = currentCode.split("\n")
210
+
211
+ React.useEffect(() => {
212
+ setInternalCode(code)
213
+ }, [code])
214
+
215
+ const handleCodeChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
216
+ const nextCode = event.target.value
217
+ setInternalCode(nextCode)
218
+ onCodeChange?.(nextCode)
219
+ }
220
+
221
+ const handleSelectAll = () => {
222
+ if (!selectOnDoubleClick) return
223
+
224
+ if (editable) {
225
+ textareaRef.current?.select()
226
+ return
227
+ }
228
+
229
+ const codeElement = codeRef.current
230
+ if (!codeElement || !window.getSelection) return
231
+
232
+ const range = document.createRange()
233
+ range.selectNodeContents(codeElement)
234
+ const selection = window.getSelection()
235
+ selection?.removeAllRanges()
236
+ selection?.addRange(range)
237
+ }
238
+
239
+ return (
240
+ <div
241
+ ref={ref}
242
+ className={cn(
243
+ "overflow-hidden rounded-md border",
244
+ styles.root,
245
+ className
246
+ )}
247
+ {...props}
248
+ >
249
+ {showHeader || copyable ? (
250
+ <div className={cn("flex items-center justify-between gap-3 border-b px-3 py-2 text-xs", styles.header)}>
251
+ <div className={cn("flex min-w-0 items-center gap-2", styles.meta)}>
252
+ {language ? (
253
+ <span className={cn("shrink-0 rounded border px-1.5 py-0.5 text-[10px] uppercase", styles.badge)}>
254
+ {language}
255
+ </span>
256
+ ) : null}
257
+ {filename ? <span className="truncate font-mono">{filename}</span> : null}
258
+ </div>
259
+ {copyable ? (
260
+ <CopyButton
261
+ value={currentCode}
262
+ copyLabel={copyLabel}
263
+ copiedLabel={copiedLabel}
264
+ copiedDuration={copiedDuration}
265
+ variant="label"
266
+ className={cn(
267
+ "h-7 shrink-0 rounded-md px-2 text-xs",
268
+ styles.button
269
+ )}
270
+ />
271
+ ) : null}
272
+ </div>
273
+ ) : null}
274
+ {editable ? (
275
+ <div className={cn("flex overflow-hidden", styles.surface)} onDoubleClick={handleSelectAll}>
276
+ {showLineNumbers ? (
277
+ <div
278
+ aria-hidden="true"
279
+ className={cn(
280
+ "select-none border-r px-3 py-4 text-right font-mono text-sm leading-relaxed",
281
+ styles.lineNumber
282
+ )}
283
+ >
284
+ {lines.map((_, index) => (
285
+ <div key={index}>{index + 1}</div>
286
+ ))}
287
+ </div>
288
+ ) : null}
289
+ <textarea
290
+ ref={textareaRef}
291
+ value={currentCode}
292
+ onChange={handleCodeChange}
293
+ spellCheck={false}
294
+ className={cn(
295
+ "min-h-[11rem] flex-1 resize-y bg-transparent p-4 font-mono text-sm leading-relaxed outline-none",
296
+ styles.textarea
297
+ )}
298
+ aria-label={filename ?? language ?? "Code"}
299
+ />
300
+ </div>
301
+ ) : (
302
+ <pre
303
+ className={cn("overflow-x-auto p-4 text-sm leading-relaxed", styles.surface)}
304
+ onDoubleClick={handleSelectAll}
305
+ >
306
+ <code ref={codeRef} className="font-mono">
307
+ {showLineNumbers
308
+ ? lines.map((line, index) => (
309
+ <span key={index} className="table-row">
310
+ <span
311
+ aria-hidden="true"
312
+ className={cn(
313
+ "table-cell select-none border-r pr-3 text-right",
314
+ styles.lineNumber
315
+ )}
316
+ >
317
+ {index + 1}
318
+ </span>
319
+ <span className="table-cell whitespace-pre pl-3">
320
+ {highlight ? renderHighlightedLine(line, styles) : line || " "}
321
+ </span>
322
+ </span>
323
+ ))
324
+ : lines.map((line, index) => (
325
+ <span key={index} className="block whitespace-pre">
326
+ {highlight ? renderHighlightedLine(line, styles) : line || " "}
327
+ </span>
328
+ ))}
329
+ </code>
330
+ </pre>
331
+ )}
332
+ </div>
333
+ )
334
+ }
335
+ )
336
+ CodeBlock.displayName = "CodeBlock"
337
+
338
+ export { CodeBlock }
@@ -0,0 +1,71 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+ import { CopyButton } from "../inputs/CopyButton"
7
+
8
+ export interface ColorSwatchProps
9
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "color"> {
10
+ /** CSS color value (hex, rgb, hsl, var(--token), etc.). */
11
+ color: string
12
+ /** Optional label override. Defaults to `color`. */
13
+ label?: string
14
+ /** Show a copy-to-clipboard button. Default true. */
15
+ copyable?: boolean
16
+ /** Accessible label and tooltip for the copy button. */
17
+ copyLabel?: string
18
+ /** Label shown briefly after copying. */
19
+ copiedLabel?: string
20
+ /** Duration in milliseconds to keep copied feedback visible. Default 5000. */
21
+ copiedDuration?: number
22
+ size?: "sm" | "default" | "lg"
23
+ }
24
+
25
+ const SIZE_MAP = {
26
+ sm: "h-4 w-4",
27
+ default: "h-6 w-6",
28
+ lg: "h-8 w-8",
29
+ } as const
30
+
31
+ const ColorSwatch = React.forwardRef<HTMLDivElement, ColorSwatchProps>(
32
+ (
33
+ { className, color, label, copyable = true, copyLabel, copiedLabel = "Copied", copiedDuration = 5000, size = "default", ...props },
34
+ ref
35
+ ) => {
36
+ const display = label ?? color
37
+
38
+ return (
39
+ <div
40
+ ref={ref}
41
+ className={cn(
42
+ "inline-flex items-center gap-2 rounded-md border border-input bg-transparent p-1.5 pr-2 text-sm",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <span
48
+ aria-hidden
49
+ className={cn(
50
+ "shrink-0 rounded border border-border/50",
51
+ SIZE_MAP[size]
52
+ )}
53
+ style={{ backgroundColor: color }}
54
+ />
55
+ <span className="font-mono text-xs">{display}</span>
56
+ {copyable ? (
57
+ <CopyButton
58
+ value={color}
59
+ copyLabel={copyLabel ?? `Copy ${color}`}
60
+ copiedLabel={copiedLabel}
61
+ copiedDuration={copiedDuration}
62
+ className="ml-auto h-5 w-5 text-muted-foreground hover:text-foreground"
63
+ />
64
+ ) : null}
65
+ </div>
66
+ )
67
+ }
68
+ )
69
+ ColorSwatch.displayName = "ColorSwatch"
70
+
71
+ export { ColorSwatch }