@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,416 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconArchive as Archive, IconDownload as Download, IconWorld as Globe2, IconHeart as Heart, IconPhoto as ImageIcon, IconSparkles as Sparkles, IconStar as Star, IconTrash as Trash2, IconX as X } from "@tabler/icons-react";
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Button } from "../inputs/Button"
8
+ import { EditableField } from "../inputs/EditableField"
9
+ import { Slider } from "../inputs/Slider"
10
+ import { TooltipButton } from "../inputs/TooltipButton"
11
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
12
+ import { MetadataList, type MetadataListItem } from "../display/MetadataList"
13
+ import { TagEditor } from "../display/TagEditor"
14
+ import type { AssetCardAsset } from "../display/AssetCard"
15
+ import {
16
+ InspectorField,
17
+ InspectorPanel,
18
+ InspectorSection,
19
+ type InspectorPanelProps,
20
+ } from "./InspectorPanel"
21
+ import { assetInspectorPanelDefaultVariantKey } from "./generated/default-variant-keys"
22
+ import type { AssetInspectorPanelVariantKey } from "./generated/variant-keys"
23
+
24
+ export interface AssetInspectorPanelLabels {
25
+ emptyTitle?: React.ReactNode
26
+ title?: React.ReactNode
27
+ note?: React.ReactNode
28
+ tags?: React.ReactNode
29
+ metadata?: React.ReactNode
30
+ rating?: React.ReactNode
31
+ actions?: React.ReactNode
32
+ analyze?: React.ReactNode
33
+ compress?: React.ReactNode
34
+ favorite?: string
35
+ preview?: string
36
+ share?: string
37
+ download?: string
38
+ delete?: string
39
+ close?: string
40
+ edit?: string
41
+ save?: string
42
+ cancel?: string
43
+ }
44
+
45
+ export interface AssetInspectorPanelProps
46
+ extends Omit<InspectorPanelProps, "title" | "children"> {
47
+ asset?: AssetCardAsset | null
48
+ title?: string
49
+ note?: string
50
+ tags?: string[]
51
+ tagSuggestions?: string[]
52
+ metadata?: MetadataListItem[]
53
+ labels?: AssetInspectorPanelLabels
54
+ variant?: AssetInspectorPanelVariantKey
55
+ onTitleChange?: (title: string) => void
56
+ onNoteChange?: (note: string) => void
57
+ onTagsChange?: (tags: string[]) => void
58
+ onPreview?: (asset: AssetCardAsset) => void
59
+ onFavorite?: (asset: AssetCardAsset) => void
60
+ onShare?: (asset: AssetCardAsset) => void
61
+ onDownload?: (asset: AssetCardAsset) => void
62
+ onDelete?: (asset: AssetCardAsset) => void
63
+ onClose?: () => void
64
+ onRatingChange?: (rating: number, asset: AssetCardAsset) => void
65
+ onAnalyze?: (asset: AssetCardAsset) => void
66
+ onCompress?: (asset: AssetCardAsset) => void
67
+ tooltipPortalContainer?: HTMLElement | null
68
+ }
69
+
70
+ const variantClasses: Record<AssetInspectorPanelVariantKey, { body: string; preview: string }> = {
71
+ default: {
72
+ body: "",
73
+ preview: "rounded-lg",
74
+ },
75
+ compact: {
76
+ body: "text-sm",
77
+ preview: "rounded-md",
78
+ },
79
+ }
80
+
81
+ function halfStep(value: number) {
82
+ return Math.min(5, Math.max(0, Math.round(value * 2) / 2))
83
+ }
84
+
85
+ function RatingStar({
86
+ index,
87
+ value,
88
+ onChange,
89
+ label,
90
+ tooltipPortalContainer,
91
+ }: {
92
+ index: number
93
+ value: number
94
+ onChange: (value: number) => void
95
+ label: string
96
+ tooltipPortalContainer?: HTMLElement | null
97
+ }) {
98
+ const fillAmount = Math.min(1, Math.max(0, value - (index - 1)))
99
+ const isFilled = fillAmount > 0
100
+ const iconSize = 24
101
+
102
+ return (
103
+ <Tooltip>
104
+ <TooltipTrigger asChild>
105
+ <button
106
+ type="button"
107
+ className="relative h-8 w-8 text-muted-foreground hover:text-primary"
108
+ aria-label={label}
109
+ aria-pressed={isFilled}
110
+ onClick={(event) => {
111
+ const rect = event.currentTarget.getBoundingClientRect()
112
+ const iconLeft = (rect.width - iconSize) / 2
113
+ const pointerX = Math.min(iconSize, Math.max(0, event.clientX - rect.left - iconLeft))
114
+ const next = index - (pointerX < iconSize / 2 ? 0.5 : 0)
115
+ onChange(value === next ? 0 : next)
116
+ }}
117
+ >
118
+ <Star className="absolute inset-1 h-6 w-6" aria-hidden="true" />
119
+ {isFilled ? (
120
+ <span
121
+ className="absolute left-1 top-1 h-6 overflow-hidden text-primary"
122
+ style={{ width: `${fillAmount * iconSize}px` }}
123
+ >
124
+ <Star className="h-6 w-6 fill-primary" aria-hidden="true" />
125
+ </span>
126
+ ) : null}
127
+ </button>
128
+ </TooltipTrigger>
129
+ <TooltipContent portalContainer={tooltipPortalContainer}>
130
+ {label}
131
+ </TooltipContent>
132
+ </Tooltip>
133
+ )
134
+ }
135
+
136
+ function defaultMetadata(asset: AssetCardAsset): MetadataListItem[] {
137
+ return [
138
+ { label: "File name", value: asset.title },
139
+ asset.width && asset.height
140
+ ? { label: "Dimensions", value: `${asset.width} x ${asset.height}` }
141
+ : null,
142
+ asset.type ? { label: "Type", value: asset.type } : null,
143
+ asset.size ? { label: "Size", value: asset.size } : null,
144
+ asset.createdAt ? { label: "Created", value: asset.createdAt } : null,
145
+ ].filter(Boolean) as MetadataListItem[]
146
+ }
147
+
148
+ function splitTitleExtension(value: string) {
149
+ const dotIndex = value.lastIndexOf(".")
150
+ if (dotIndex <= 0 || dotIndex === value.length - 1) {
151
+ return { name: value, extension: "" }
152
+ }
153
+ return { name: value.slice(0, dotIndex), extension: value.slice(dotIndex) }
154
+ }
155
+
156
+ const AssetInspectorPanel = React.forwardRef<HTMLDivElement, AssetInspectorPanelProps>(
157
+ (
158
+ {
159
+ asset,
160
+ title,
161
+ note = "",
162
+ tags,
163
+ tagSuggestions,
164
+ metadata,
165
+ labels,
166
+ variant = assetInspectorPanelDefaultVariantKey,
167
+ onTitleChange,
168
+ onNoteChange,
169
+ onTagsChange,
170
+ onPreview,
171
+ onFavorite,
172
+ onShare,
173
+ onDownload,
174
+ onDelete,
175
+ onClose,
176
+ onRatingChange,
177
+ onAnalyze,
178
+ onCompress,
179
+ tooltipPortalContainer,
180
+ className,
181
+ ...props
182
+ },
183
+ ref
184
+ ) => {
185
+ const classes = variantClasses[variant]
186
+ const resolvedTitle = title ?? "Details"
187
+
188
+ if (!asset) {
189
+ return (
190
+ <InspectorPanel ref={ref} title={resolvedTitle} className={cn("w-full p-0", className)} {...props}>
191
+ <div className="flex min-h-64 flex-col items-center justify-center gap-3 text-center text-muted-foreground">
192
+ <ImageIcon className="h-10 w-10 opacity-50" aria-hidden="true" />
193
+ <p className="text-sm">{labels?.emptyTitle ?? "Select an asset to view details."}</p>
194
+ </div>
195
+ </InspectorPanel>
196
+ )
197
+ }
198
+
199
+ const currentTags = tags ?? []
200
+ const metadataItems = metadata ?? defaultMetadata(asset)
201
+ const { name: editableTitleName, extension: titleExtension } = splitTitleExtension(asset.title)
202
+ const previewLabel = String(labels?.preview ?? "Preview")
203
+ const header = (
204
+ <div className="flex h-12 items-center justify-between gap-2 border-b border-border bg-background px-3">
205
+ <TooltipButton
206
+ type="button"
207
+ variant="ghost"
208
+ size="icon"
209
+ aria-label={labels?.favorite ?? "Favorite"}
210
+ tooltip={labels?.favorite ?? "Favorite"}
211
+ tooltipSide="bottom"
212
+ tooltipCloseOnPress
213
+ tooltipPortalContainer={tooltipPortalContainer}
214
+ className={cn("h-9 w-9 text-muted-foreground", asset.isFavorite && "text-primary")}
215
+ onClick={() => onFavorite?.(asset)}
216
+ >
217
+ <Heart className={cn("h-5 w-5", asset.isFavorite && "fill-current")} aria-hidden="true" />
218
+ </TooltipButton>
219
+ <div className="flex items-center gap-1">
220
+ {onShare ? (
221
+ <TooltipButton
222
+ type="button"
223
+ variant="ghost"
224
+ size="icon"
225
+ className="h-9 w-9 text-muted-foreground"
226
+ aria-label={labels?.share ?? "Share"}
227
+ tooltip={labels?.share ?? "Share"}
228
+ tooltipSide="bottom"
229
+ tooltipCloseOnPress
230
+ tooltipPortalContainer={tooltipPortalContainer}
231
+ onClick={() => onShare(asset)}
232
+ >
233
+ <Globe2 className="h-5 w-5" aria-hidden="true" />
234
+ </TooltipButton>
235
+ ) : null}
236
+ {onDownload ? (
237
+ <TooltipButton
238
+ type="button"
239
+ variant="ghost"
240
+ size="icon"
241
+ className="h-9 w-9 text-muted-foreground"
242
+ aria-label={labels?.download ?? "Download"}
243
+ tooltip={labels?.download ?? "Download"}
244
+ tooltipSide="bottom"
245
+ tooltipCloseOnPress
246
+ tooltipPortalContainer={tooltipPortalContainer}
247
+ onClick={() => onDownload(asset)}
248
+ >
249
+ <Download className="h-5 w-5" aria-hidden="true" />
250
+ </TooltipButton>
251
+ ) : null}
252
+ {(onDelete || onClose) ? <span className="mx-1 h-6 w-px bg-border" aria-hidden="true" /> : null}
253
+ {onDelete ? (
254
+ <TooltipButton
255
+ type="button"
256
+ variant="ghost"
257
+ size="icon"
258
+ aria-label={labels?.delete ?? "Delete"}
259
+ tooltip={labels?.delete ?? "Delete"}
260
+ tooltipSide="bottom"
261
+ tooltipCloseOnPress
262
+ tooltipPortalContainer={tooltipPortalContainer}
263
+ className="h-9 w-9 text-muted-foreground hover:text-destructive"
264
+ onClick={() => onDelete(asset)}
265
+ >
266
+ <Trash2 className="h-5 w-5" aria-hidden="true" />
267
+ </TooltipButton>
268
+ ) : null}
269
+ {onClose ? (
270
+ <TooltipButton
271
+ type="button"
272
+ variant="ghost"
273
+ size="icon"
274
+ aria-label={labels?.close ?? "Close"}
275
+ tooltip={labels?.close ?? "Close"}
276
+ tooltipSide="bottom"
277
+ tooltipCloseOnPress
278
+ tooltipPortalContainer={tooltipPortalContainer}
279
+ className="h-9 w-9 text-muted-foreground"
280
+ onClick={onClose}
281
+ >
282
+ <X className="h-5 w-5" aria-hidden="true" />
283
+ </TooltipButton>
284
+ ) : null}
285
+ </div>
286
+ </div>
287
+ )
288
+
289
+ return (
290
+ <InspectorPanel ref={ref} header={header} className={cn("w-full p-0", className)} {...props}>
291
+ <div className={cn("space-y-6", classes.body)}>
292
+ {onPreview ? (
293
+ <Tooltip>
294
+ <TooltipTrigger asChild>
295
+ <button
296
+ type="button"
297
+ className={cn(
298
+ "relative flex aspect-square w-full cursor-zoom-in items-center justify-center overflow-hidden border bg-muted/40 p-0",
299
+ classes.preview
300
+ )}
301
+ aria-label={previewLabel}
302
+ onClick={() => onPreview(asset)}
303
+ >
304
+ {asset.src ? (
305
+ <img src={asset.src} alt={asset.alt ?? asset.title} className="h-full w-full object-contain" />
306
+ ) : (
307
+ <ImageIcon className="h-10 w-10 text-muted-foreground" aria-hidden="true" />
308
+ )}
309
+ </button>
310
+ </TooltipTrigger>
311
+ <TooltipContent portalContainer={tooltipPortalContainer}>
312
+ {previewLabel}
313
+ </TooltipContent>
314
+ </Tooltip>
315
+ ) : (
316
+ <div
317
+ className={cn(
318
+ "relative flex aspect-square w-full items-center justify-center overflow-hidden border bg-muted/40 p-0",
319
+ classes.preview
320
+ )}
321
+ >
322
+ {asset.src ? (
323
+ <img src={asset.src} alt={asset.alt ?? asset.title} className="h-full w-full object-contain" />
324
+ ) : (
325
+ <ImageIcon className="h-10 w-10 text-muted-foreground" aria-hidden="true" />
326
+ )}
327
+ </div>
328
+ )}
329
+
330
+ <div className="space-y-3">
331
+ <EditableField
332
+ label={String(labels?.title ?? "Title")}
333
+ value={editableTitleName}
334
+ labels={labels}
335
+ onSave={onTitleChange ? (value) => {
336
+ const nextTitle = splitTitleExtension(value || editableTitleName).name
337
+ onTitleChange(`${nextTitle}${titleExtension}`)
338
+ } : undefined}
339
+ />
340
+ <EditableField
341
+ label={String(labels?.note ?? "Note")}
342
+ value={note}
343
+ labels={labels}
344
+ onSave={onNoteChange}
345
+ />
346
+ {onRatingChange ? (
347
+ <InspectorField label={String(labels?.rating ?? "Rating")}>
348
+ <div className="space-y-2">
349
+ <div className="flex items-center gap-2">
350
+ <div className="flex items-center gap-0.5">
351
+ {[1, 2, 3, 4, 5].map((index) => (
352
+ <RatingStar
353
+ key={index}
354
+ index={index}
355
+ value={asset.rating ?? 0}
356
+ onChange={(value) => onRatingChange(value, asset)}
357
+ label={`${String(labels?.rating ?? "Rating")} ${index}`}
358
+ tooltipPortalContainer={tooltipPortalContainer}
359
+ />
360
+ ))}
361
+ </div>
362
+ <span className="text-xs tabular-nums text-muted-foreground">
363
+ {Number(asset.rating ?? 0).toFixed(1)} / 5
364
+ </span>
365
+ </div>
366
+ <Slider
367
+ value={String(asset.rating ?? 0)}
368
+ min={0}
369
+ max={5}
370
+ step={0.5}
371
+ onChange={(event) => onRatingChange(halfStep(Number(event.currentTarget.value)), asset)}
372
+ aria-label={String(labels?.rating ?? "Rating")}
373
+ />
374
+ </div>
375
+ </InspectorField>
376
+ ) : null}
377
+ </div>
378
+
379
+ <InspectorSection title={String(labels?.tags ?? "Tags")}>
380
+ <TagEditor
381
+ value={currentTags}
382
+ onValueChange={onTagsChange}
383
+ suggestions={tagSuggestions}
384
+ label={null}
385
+ variant={variant}
386
+ />
387
+ </InspectorSection>
388
+
389
+ <InspectorSection title={String(labels?.metadata ?? "Metadata")}>
390
+ <MetadataList items={metadataItems} variant="compact" />
391
+ </InspectorSection>
392
+
393
+ {(onAnalyze || onCompress) ? (
394
+ <InspectorSection title={String(labels?.actions ?? "Actions")}>
395
+ {onAnalyze ? (
396
+ <Button type="button" className="w-full gap-2" onClick={() => onAnalyze(asset)}>
397
+ <Sparkles className="h-4 w-4" aria-hidden="true" />
398
+ {labels?.analyze ?? "Analyze"}
399
+ </Button>
400
+ ) : null}
401
+ {onCompress ? (
402
+ <Button type="button" variant="secondary" className="w-full gap-2" onClick={() => onCompress(asset)}>
403
+ <Archive className="h-4 w-4" aria-hidden="true" />
404
+ {labels?.compress ?? "Compress"}
405
+ </Button>
406
+ ) : null}
407
+ </InspectorSection>
408
+ ) : null}
409
+ </div>
410
+ </InspectorPanel>
411
+ )
412
+ }
413
+ )
414
+ AssetInspectorPanel.displayName = "AssetInspectorPanel"
415
+
416
+ export { AssetInspectorPanel }
@@ -0,0 +1,56 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const GAP_MAP = {
6
+ 0: "gap-0",
7
+ 1: "gap-1",
8
+ 2: "gap-2",
9
+ 3: "gap-3",
10
+ 4: "gap-4",
11
+ 5: "gap-5",
12
+ 6: "gap-6",
13
+ 8: "gap-8",
14
+ } as const
15
+
16
+ const ALIGN_MAP = {
17
+ start: "items-start",
18
+ center: "items-center",
19
+ end: "items-end",
20
+ baseline: "items-baseline",
21
+ } as const
22
+
23
+ const JUSTIFY_MAP = {
24
+ start: "justify-start",
25
+ center: "justify-center",
26
+ end: "justify-end",
27
+ between: "justify-between",
28
+ } as const
29
+
30
+ export interface ClusterProps extends React.HTMLAttributes<HTMLDivElement> {
31
+ gap?: keyof typeof GAP_MAP
32
+ align?: keyof typeof ALIGN_MAP
33
+ justify?: keyof typeof JUSTIFY_MAP
34
+ }
35
+
36
+ const Cluster = React.forwardRef<HTMLDivElement, ClusterProps>(
37
+ (
38
+ { className, gap = 2, align = "center", justify = "start", ...props },
39
+ ref
40
+ ) => (
41
+ <div
42
+ ref={ref}
43
+ className={cn(
44
+ "flex flex-row flex-wrap",
45
+ GAP_MAP[gap],
46
+ ALIGN_MAP[align],
47
+ JUSTIFY_MAP[justify],
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ )
53
+ )
54
+ Cluster.displayName = "Cluster"
55
+
56
+ export { Cluster }
@@ -0,0 +1,94 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconLayoutBottombarCollapse as PanelBottomClose, IconLayoutBottombarExpand as PanelBottomOpen, IconLayoutSidebarLeftCollapse as PanelLeftClose, IconLayoutSidebarLeftExpand as PanelLeftOpen, IconLayoutSidebarRightCollapse as PanelRightClose, IconLayoutSidebarRightExpand as PanelRightOpen, IconLayoutNavbarCollapse as PanelTopClose, IconLayoutNavbarExpand as PanelTopOpen } from "@tabler/icons-react";
5
+ import { TooltipButton, type TooltipButtonProps } from "../inputs/TooltipButton"
6
+ import { cn } from "../../lib/utils"
7
+ import type { CollapsiblePanelToggleVariantKey } from "./generated/variant-keys"
8
+ import { collapsiblePanelToggleDefaultVariantKey } from "./generated/default-variant-keys"
9
+
10
+ export type CollapsiblePanelToggleSide = CollapsiblePanelToggleVariantKey
11
+
12
+ export interface CollapsiblePanelToggleProps
13
+ extends Omit<TooltipButtonProps, "children" | "tooltip" | "tooltipSide"> {
14
+ side?: CollapsiblePanelToggleSide
15
+ collapsed: boolean
16
+ openLabel?: string
17
+ closeLabel?: string
18
+ tooltip?: React.ReactNode
19
+ iconClassName?: string
20
+ }
21
+
22
+ const iconBySide: Record<CollapsiblePanelToggleVariantKey, {
23
+ open: React.ComponentType<React.SVGProps<SVGSVGElement>>
24
+ close: React.ComponentType<React.SVGProps<SVGSVGElement>>
25
+ tooltipSide: "top" | "right" | "bottom" | "left"
26
+ }> = {
27
+ left: {
28
+ open: PanelLeftOpen,
29
+ close: PanelLeftClose,
30
+ tooltipSide: "right",
31
+ },
32
+ right: {
33
+ open: PanelRightOpen,
34
+ close: PanelRightClose,
35
+ tooltipSide: "left",
36
+ },
37
+ top: {
38
+ open: PanelTopOpen,
39
+ close: PanelTopClose,
40
+ tooltipSide: "bottom",
41
+ },
42
+ bottom: {
43
+ open: PanelBottomOpen,
44
+ close: PanelBottomClose,
45
+ tooltipSide: "top",
46
+ },
47
+ } as const
48
+
49
+ const CollapsiblePanelToggle = React.forwardRef<
50
+ HTMLButtonElement,
51
+ CollapsiblePanelToggleProps
52
+ >(
53
+ (
54
+ {
55
+ side = collapsiblePanelToggleDefaultVariantKey,
56
+ collapsed,
57
+ openLabel = "Open panel",
58
+ closeLabel = "Close panel",
59
+ tooltip,
60
+ iconClassName,
61
+ className,
62
+ "aria-label": ariaLabel,
63
+ ...props
64
+ },
65
+ ref
66
+ ) => {
67
+ const config = iconBySide[side]
68
+ const Icon = collapsed ? config.open : config.close
69
+ const label = collapsed ? openLabel : closeLabel
70
+
71
+ return (
72
+ <TooltipButton
73
+ ref={ref}
74
+ type="button"
75
+ variant="outline"
76
+ size="icon"
77
+ aria-label={ariaLabel ?? label}
78
+ tooltip={tooltip ?? label}
79
+ tooltipSide={config.tooltipSide}
80
+ tooltipCloseOnPress
81
+ className={cn(
82
+ "h-10 w-10 rounded-full border-border bg-background text-muted-foreground shadow-md ring-1 ring-border/70 transition-[background-color,color,box-shadow,transform] duration-200 ease-out hover:bg-background hover:text-foreground motion-reduce:transition-none",
83
+ className
84
+ )}
85
+ {...props}
86
+ >
87
+ <Icon className={cn("h-5 w-5", iconClassName)} aria-hidden="true" />
88
+ </TooltipButton>
89
+ )
90
+ }
91
+ )
92
+ CollapsiblePanelToggle.displayName = "CollapsiblePanelToggle"
93
+
94
+ export { CollapsiblePanelToggle }
@@ -0,0 +1,43 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "../../lib/utils"
5
+
6
+ export const containerVariants = cva("mx-auto w-full px-4 sm:px-6 lg:px-8", {
7
+ variants: {
8
+ size: {
9
+ sm: "max-w-screen-sm",
10
+ md: "max-w-screen-md",
11
+ lg: "max-w-screen-lg",
12
+ xl: "max-w-screen-xl",
13
+ "2xl": "max-w-screen-2xl",
14
+ full: "max-w-full",
15
+ prose: "max-w-prose",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ size: "lg",
20
+ },
21
+ })
22
+
23
+ export interface ContainerProps
24
+ extends React.HTMLAttributes<HTMLDivElement>,
25
+ VariantProps<typeof containerVariants> {
26
+ as?: keyof React.JSX.IntrinsicElements
27
+ }
28
+
29
+ const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
30
+ ({ className, size, as = "div", ...props }, ref) => {
31
+ const Comp = as as React.ElementType
32
+ return (
33
+ <Comp
34
+ ref={ref}
35
+ className={cn(containerVariants({ size }), className)}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+ )
41
+ Container.displayName = "Container"
42
+
43
+ export { Container }