@gunjo/ui 0.0.1-alpha.1 → 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,141 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconPlus as Plus } from "@tabler/icons-react";
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Button } from "../inputs/Button"
8
+ import { TagInput } from "../inputs/TagInput"
9
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../overlay/Tooltip"
10
+ import { tagEditorDefaultVariantKey } from "./generated/default-variant-keys"
11
+ import type { TagEditorVariantKey } from "./generated/variant-keys"
12
+
13
+ export interface TagEditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
14
+ value: string[]
15
+ onValueChange?: (value: string[]) => void
16
+ suggestions?: string[]
17
+ label?: React.ReactNode
18
+ placeholder?: string
19
+ removeLabel?: string
20
+ maxTagsReachedLabel?: string
21
+ disabledLabel?: string
22
+ maxTags?: number
23
+ variant?: TagEditorVariantKey
24
+ disabled?: boolean
25
+ }
26
+
27
+ const variantClasses: Record<TagEditorVariantKey, { root: string; input: string }> = {
28
+ default: {
29
+ root: "space-y-3",
30
+ input: "min-h-10",
31
+ },
32
+ compact: {
33
+ root: "space-y-2",
34
+ input: "min-h-9 text-xs",
35
+ },
36
+ }
37
+
38
+ function addTag(value: string[], next: string, maxTags?: number) {
39
+ const trimmed = next.trim()
40
+ if (!trimmed) return value
41
+ if (maxTags !== undefined && value.length >= maxTags) return value
42
+ if (value.some((tag) => tag.toLowerCase() === trimmed.toLowerCase())) return value
43
+ return [...value, trimmed]
44
+ }
45
+
46
+ const TagEditor = React.forwardRef<HTMLDivElement, TagEditorProps>(
47
+ (
48
+ {
49
+ value,
50
+ onValueChange,
51
+ suggestions = [],
52
+ label = "Tags",
53
+ placeholder = "Add tag...",
54
+ removeLabel = "Remove tag",
55
+ maxTagsReachedLabel = "Maximum number of tags reached",
56
+ disabledLabel = "Tag editing is disabled",
57
+ maxTags,
58
+ variant = tagEditorDefaultVariantKey,
59
+ disabled,
60
+ className,
61
+ ...props
62
+ },
63
+ ref
64
+ ) => {
65
+ const classes = variantClasses[variant]
66
+ const isAtMax = maxTags !== undefined && value.length >= maxTags
67
+ const availableSuggestions = suggestions.filter(
68
+ (suggestion) => !value.some((tag) => tag.toLowerCase() === suggestion.toLowerCase())
69
+ )
70
+
71
+ return (
72
+ <div ref={ref} className={cn("w-full p-0", classes.root, className)} {...props}>
73
+ {label ? (
74
+ <div className="flex items-center justify-between gap-3">
75
+ <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
76
+ {label}
77
+ </span>
78
+ {maxTags ? (
79
+ <span className="text-xs text-muted-foreground">
80
+ {value.length}/{maxTags}
81
+ </span>
82
+ ) : null}
83
+ </div>
84
+ ) : null}
85
+ <TagInput
86
+ value={value}
87
+ onValueChange={onValueChange}
88
+ placeholder={placeholder}
89
+ removeLabel={removeLabel}
90
+ maxTags={maxTags}
91
+ disabled={disabled}
92
+ className={classes.input}
93
+ />
94
+ {availableSuggestions.length > 0 ? (
95
+ <div className="flex flex-wrap gap-1.5">
96
+ {availableSuggestions.slice(0, 6).map((suggestion) => {
97
+ const suggestionDisabled = disabled || isAtMax
98
+ const button = (
99
+ <Button
100
+ type="button"
101
+ variant="outline"
102
+ size="sm"
103
+ disabled={suggestionDisabled}
104
+ className="h-7 gap-1 px-2 text-xs"
105
+ onClick={() => onValueChange?.(addTag(value, suggestion, maxTags))}
106
+ >
107
+ <Plus className="h-3 w-3" aria-hidden="true" />
108
+ {suggestion}
109
+ </Button>
110
+ )
111
+
112
+ if (!suggestionDisabled) {
113
+ return (
114
+ <React.Fragment key={suggestion}>
115
+ {button}
116
+ </React.Fragment>
117
+ )
118
+ }
119
+
120
+ const disabledReason = isAtMax ? maxTagsReachedLabel : disabledLabel
121
+
122
+ return (
123
+ <Tooltip key={suggestion}>
124
+ <TooltipTrigger asChild>
125
+ <span className="inline-flex cursor-not-allowed" tabIndex={0}>
126
+ {button}
127
+ </span>
128
+ </TooltipTrigger>
129
+ <TooltipContent>{disabledReason}</TooltipContent>
130
+ </Tooltip>
131
+ )
132
+ })}
133
+ </div>
134
+ ) : null}
135
+ </div>
136
+ )
137
+ }
138
+ )
139
+ TagEditor.displayName = "TagEditor"
140
+
141
+ export { TagEditor }
@@ -0,0 +1,121 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Timeline = React.forwardRef<
6
+ HTMLOListElement,
7
+ React.HTMLAttributes<HTMLOListElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <ol
10
+ ref={ref}
11
+ className={cn("relative flex flex-col", className)}
12
+ {...props}
13
+ />
14
+ ))
15
+ Timeline.displayName = "Timeline"
16
+
17
+ export interface TimelineItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
18
+ /** Whether to draw a connector line below this item. Default true; set false on last item. */
19
+ connector?: boolean
20
+ /** Marker variant. */
21
+ variant?: "default" | "muted" | "active" | "outline"
22
+ /** Custom marker node (icon, etc.). Overrides default dot. */
23
+ marker?: React.ReactNode
24
+ }
25
+
26
+ const VARIANT_CLASS = {
27
+ default: "bg-foreground",
28
+ muted: "bg-muted-foreground",
29
+ active: "gunjo-timeline-marker-active border-2 border-primary-border bg-primary",
30
+ outline: "border-2 border-foreground bg-background",
31
+ } as const
32
+
33
+ function hasTimelineTime(children: React.ReactNode): boolean {
34
+ return React.Children.toArray(children).some((child) => {
35
+ if (!React.isValidElement(child)) return false
36
+ const type = child.type as { displayName?: string }
37
+ return type.displayName === "TimelineTime"
38
+ })
39
+ }
40
+
41
+ const TimelineItem = React.forwardRef<HTMLLIElement, TimelineItemProps>(
42
+ (
43
+ {
44
+ className,
45
+ connector = true,
46
+ variant = "default",
47
+ marker,
48
+ children,
49
+ ...props
50
+ },
51
+ ref
52
+ ) => {
53
+ const hasTime = hasTimelineTime(children)
54
+
55
+ return (
56
+ <li
57
+ ref={ref}
58
+ className={cn("relative flex items-start gap-3", className)}
59
+ {...props}
60
+ >
61
+ <div className={cn("flex self-stretch flex-col items-center", hasTime ? "pt-1" : "pt-0.5")}>
62
+ {marker ? (
63
+ <div className="flex h-4 w-4 items-center justify-center">
64
+ {marker}
65
+ </div>
66
+ ) : (
67
+ <span
68
+ aria-hidden
69
+ className={cn("h-4 w-4 rounded-full", VARIANT_CLASS[variant])}
70
+ />
71
+ )}
72
+ {connector ? (
73
+ <span aria-hidden className={cn("min-h-4 w-0.5 flex-1 bg-border", hasTime ? "-mb-1" : "-mb-0.5")} />
74
+ ) : null}
75
+ </div>
76
+ <div className={cn("min-w-0 pb-6", connector ? "" : "pb-0")}>
77
+ {children}
78
+ </div>
79
+ </li>
80
+ )
81
+ }
82
+ )
83
+ TimelineItem.displayName = "TimelineItem"
84
+
85
+ const TimelineTitle = React.forwardRef<
86
+ HTMLDivElement,
87
+ React.HTMLAttributes<HTMLDivElement>
88
+ >(({ className, ...props }, ref) => (
89
+ <div
90
+ ref={ref}
91
+ className={cn("text-sm font-semibold text-foreground", className)}
92
+ {...props}
93
+ />
94
+ ))
95
+ TimelineTitle.displayName = "TimelineTitle"
96
+
97
+ const TimelineDescription = React.forwardRef<
98
+ HTMLParagraphElement,
99
+ React.HTMLAttributes<HTMLParagraphElement>
100
+ >(({ className, ...props }, ref) => (
101
+ <p
102
+ ref={ref}
103
+ className={cn("text-sm text-muted-foreground", className)}
104
+ {...props}
105
+ />
106
+ ))
107
+ TimelineDescription.displayName = "TimelineDescription"
108
+
109
+ const TimelineTime = React.forwardRef<
110
+ HTMLTimeElement,
111
+ React.TimeHTMLAttributes<HTMLTimeElement>
112
+ >(({ className, ...props }, ref) => (
113
+ <time
114
+ ref={ref}
115
+ className={cn("text-xs text-muted-foreground", className)}
116
+ {...props}
117
+ />
118
+ ))
119
+ TimelineTime.displayName = "TimelineTime"
120
+
121
+ export { Timeline, TimelineItem, TimelineTitle, TimelineDescription, TimelineTime }
@@ -1,15 +1,26 @@
1
+ "use client";
2
+
1
3
  import React from 'react';
2
4
  import { cn } from '../../lib/utils';
3
- import { LucideIcon } from 'lucide-react';
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../overlay/Tooltip';
4
6
  import type { ToolPillVariantKey } from "./generated/variant-keys";
5
7
  import { toolPillDefaultVariantKey } from "./generated/default-variant-keys";
6
8
 
9
+ type ToolPillIcon = React.ComponentType<{
10
+ className?: string;
11
+ size?: string | number;
12
+ strokeWidth?: string | number;
13
+ }>;
14
+
15
+ type ToolPillTooltipSide = "top" | "right" | "bottom" | "left";
16
+
7
17
  interface ToolPillProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
8
- icon: LucideIcon;
18
+ icon: ToolPillIcon;
9
19
  label?: string;
10
20
  isActive?: boolean;
11
21
  variant?: ToolPillVariantKey;
12
22
  size?: 'sm' | 'md' | 'lg';
23
+ tooltipSide?: ToolPillTooltipSide;
13
24
  }
14
25
 
15
26
  export const ToolPill: React.FC<ToolPillProps> = ({
@@ -18,13 +29,15 @@ export const ToolPill: React.FC<ToolPillProps> = ({
18
29
  isActive,
19
30
  variant = toolPillDefaultVariantKey,
20
31
  size = 'md',
32
+ tooltipSide = 'top',
21
33
  className,
22
34
  ...props
23
35
  }) => {
36
+ const { disabled, "aria-label": ariaLabel } = props;
24
37
  const sizeClasses = {
25
- sm: 'p-1.5',
26
- md: 'p-2.5',
27
- lg: 'p-3.5',
38
+ sm: 'h-8 w-8 rounded-lg p-1.5',
39
+ md: 'h-10 w-10 rounded-xl p-2.5',
40
+ lg: 'h-12 w-12 rounded-xl p-3.5',
28
41
  };
29
42
 
30
43
  const iconSizes = {
@@ -35,43 +48,54 @@ export const ToolPill: React.FC<ToolPillProps> = ({
35
48
 
36
49
  const variantClasses: Record<ToolPillVariantKey, { inactive: string; active: string }> = {
37
50
  primary: {
38
- inactive: "bg-primary/10 text-primary hover:bg-primary/20 hover:shadow-md hover:-translate-y-0.5",
39
- active: "bg-primary text-primary-foreground shadow-lg shadow-primary/30 ring-2 ring-primary/20",
51
+ inactive: "bg-primary-subtle text-primary-subtle-foreground hover:bg-primary-subtle hover:shadow-md hover:-translate-y-0.5",
52
+ active: "bg-primary text-primary-foreground shadow-lg shadow-primary-border ring-2 ring-primary-border",
40
53
  },
41
54
  secondary: {
42
55
  inactive: "bg-background/70 text-muted-foreground hover:bg-background hover:shadow-md hover:-translate-y-0.5",
43
56
  active: "bg-secondary text-secondary-foreground shadow-lg shadow-foreground/10 ring-2 ring-border",
44
57
  },
45
58
  danger: {
46
- inactive: "bg-destructive/10 text-destructive hover:bg-destructive/20 hover:shadow-md hover:-translate-y-0.5",
47
- active: "bg-destructive text-destructive-foreground shadow-lg shadow-destructive/30 ring-2 ring-destructive/20",
59
+ inactive: "bg-destructive-subtle text-destructive-subtle-foreground hover:bg-destructive-subtle hover:shadow-md hover:-translate-y-0.5",
60
+ active: "bg-destructive-strong text-destructive-strong-foreground shadow-lg shadow-destructive-border ring-2 ring-destructive-border",
48
61
  },
49
62
  };
50
63
 
51
- return (
64
+ const button = (
52
65
  <button
53
66
  type="button"
54
67
  className={cn(
55
- "group relative flex h-10 w-10 items-center justify-center rounded-xl transition-all duration-200",
68
+ "group relative flex items-center justify-center transition-all duration-200",
56
69
  isActive ? variantClasses[variant].active : variantClasses[variant].inactive,
57
70
 
58
71
  sizeClasses[size],
59
72
  className
60
73
  )}
61
74
  {...props}
75
+ aria-label={ariaLabel ?? label}
62
76
  >
63
77
  <Icon size={iconSizes[size]} strokeWidth={isActive ? 2.5 : 2} />
64
78
 
65
79
  {label && (
66
80
  <span className="sr-only">{label}</span>
67
81
  )}
68
-
69
- {/* Tooltip on Hover */}
70
- {label && (
71
- <div className="absolute left-full ml-2 px-2 py-1 bg-foreground text-background text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap z-50">
72
- {label}
73
- </div>
74
- )}
75
82
  </button>
76
83
  );
84
+
85
+ if (!label) return button;
86
+
87
+ const trigger = disabled ? (
88
+ <span className="inline-flex" tabIndex={0} aria-label={label}>
89
+ {button}
90
+ </span>
91
+ ) : button;
92
+
93
+ return (
94
+ <Tooltip>
95
+ <TooltipTrigger asChild>{trigger}</TooltipTrigger>
96
+ <TooltipContent side={tooltipSide} className="text-xs">
97
+ {label}
98
+ </TooltipContent>
99
+ </Tooltip>
100
+ );
77
101
  };
@@ -0,0 +1,226 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { IconChevronDown as ChevronDown, IconChevronRight as ChevronRight } from "@tabler/icons-react";
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ export interface TreeNode {
9
+ id: string
10
+ label: React.ReactNode
11
+ icon?: React.ReactNode
12
+ children?: TreeNode[]
13
+ }
14
+
15
+ export interface TreeViewProps extends React.HTMLAttributes<HTMLUListElement> {
16
+ nodes: TreeNode[]
17
+ /** Controlled set of expanded ids. */
18
+ expanded?: Set<string>
19
+ onExpandedChange?: (expanded: Set<string>) => void
20
+ /** Default-expanded ids when uncontrolled. */
21
+ defaultExpanded?: string[]
22
+ /** Selected id. */
23
+ selectedId?: string
24
+ /** Selected ids for controlled multi-selection display. */
25
+ selectedIds?: Iterable<string>
26
+ /** Selection semantics used for aria state and selected row styling. */
27
+ selectionMode?: "single" | "multiple" | "none"
28
+ onSelectedIdChange?: (id: string) => void
29
+ /** Optional supplemental content shown after the label, such as item count or file size. */
30
+ renderNodeMeta?: (node: TreeNode) => React.ReactNode
31
+ /** Optional actions rendered at the end of each row. */
32
+ renderNodeActions?: (node: TreeNode) => React.ReactNode
33
+ /** Optional props applied to the row wrapper, useful for drag and drop or instrumentation. */
34
+ getNodeRowProps?: (node: TreeNode) => React.HTMLAttributes<HTMLDivElement> | undefined
35
+ }
36
+
37
+ const TreeView = React.forwardRef<HTMLUListElement, TreeViewProps>(
38
+ (
39
+ {
40
+ className,
41
+ nodes,
42
+ expanded: controlledExpanded,
43
+ onExpandedChange,
44
+ defaultExpanded,
45
+ selectedId,
46
+ selectedIds,
47
+ selectionMode = "single",
48
+ onSelectedIdChange,
49
+ renderNodeMeta,
50
+ renderNodeActions,
51
+ getNodeRowProps,
52
+ ...props
53
+ },
54
+ ref
55
+ ) => {
56
+ const [internalExpanded, setInternalExpanded] = React.useState<Set<string>>(
57
+ () => new Set(defaultExpanded ?? [])
58
+ )
59
+ const isControlled = controlledExpanded !== undefined
60
+ const expanded = isControlled ? controlledExpanded : internalExpanded
61
+ const selectedSet = React.useMemo(
62
+ () => (selectedIds ? new Set(selectedIds) : undefined),
63
+ [selectedIds]
64
+ )
65
+
66
+ const setExpanded = (next: Set<string>) => {
67
+ if (!isControlled) setInternalExpanded(next)
68
+ onExpandedChange?.(next)
69
+ }
70
+
71
+ const toggle = (id: string) => {
72
+ const next = new Set(expanded)
73
+ if (next.has(id)) next.delete(id)
74
+ else next.add(id)
75
+ setExpanded(next)
76
+ }
77
+
78
+ return (
79
+ <ul
80
+ ref={ref}
81
+ role="tree"
82
+ aria-multiselectable={selectionMode === "multiple" ? true : undefined}
83
+ className={cn("flex flex-col gap-0.5", className)}
84
+ {...props}
85
+ >
86
+ {nodes.map((node) => (
87
+ <TreeViewItem
88
+ key={node.id}
89
+ node={node}
90
+ depth={0}
91
+ expanded={expanded}
92
+ onToggle={toggle}
93
+ selectedId={selectedId}
94
+ selectedIds={selectedSet}
95
+ selectionMode={selectionMode}
96
+ onSelect={onSelectedIdChange}
97
+ renderNodeMeta={renderNodeMeta}
98
+ renderNodeActions={renderNodeActions}
99
+ getNodeRowProps={getNodeRowProps}
100
+ />
101
+ ))}
102
+ </ul>
103
+ )
104
+ }
105
+ )
106
+ TreeView.displayName = "TreeView"
107
+
108
+ interface TreeViewItemProps {
109
+ node: TreeNode
110
+ depth: number
111
+ expanded: Set<string>
112
+ onToggle: (id: string) => void
113
+ selectedId?: string
114
+ selectedIds?: Set<string>
115
+ selectionMode: "single" | "multiple" | "none"
116
+ onSelect?: (id: string) => void
117
+ renderNodeMeta?: (node: TreeNode) => React.ReactNode
118
+ renderNodeActions?: (node: TreeNode) => React.ReactNode
119
+ getNodeRowProps?: (node: TreeNode) => React.HTMLAttributes<HTMLDivElement> | undefined
120
+ }
121
+
122
+ function TreeViewItem({
123
+ node,
124
+ depth,
125
+ expanded,
126
+ onToggle,
127
+ selectedId,
128
+ selectedIds,
129
+ selectionMode,
130
+ onSelect,
131
+ renderNodeMeta,
132
+ renderNodeActions,
133
+ getNodeRowProps,
134
+ }: TreeViewItemProps) {
135
+ const hasChildren = !!node.children && node.children.length > 0
136
+ const isOpen = expanded.has(node.id)
137
+ const isSelected = selectionMode !== "none" && (selectedIds ? selectedIds.has(node.id) : selectedId === node.id)
138
+ const meta = renderNodeMeta?.(node)
139
+ const actions = renderNodeActions?.(node)
140
+ const rowProps = getNodeRowProps?.(node)
141
+ const {
142
+ className: rowClassName,
143
+ style: rowStyle,
144
+ ...rowPropsRest
145
+ } = rowProps ?? {}
146
+ const rowStyleWithIndent = {
147
+ ...rowStyle,
148
+ "--tree-view-indent": `calc(0.5rem + ${depth}rem)`,
149
+ } as React.CSSProperties
150
+
151
+ return (
152
+ <li
153
+ role="treeitem"
154
+ aria-expanded={hasChildren ? isOpen : undefined}
155
+ aria-selected={selectionMode !== "none" ? isSelected : undefined}
156
+ >
157
+ <div
158
+ {...rowPropsRest}
159
+ data-tree-view-row
160
+ className={cn(
161
+ "group flex w-full items-center rounded-md text-sm hover:bg-muted",
162
+ isSelected && "bg-muted font-medium",
163
+ rowClassName
164
+ )}
165
+ style={rowStyleWithIndent}
166
+ >
167
+ <button
168
+ type="button"
169
+ onClick={() => {
170
+ if (hasChildren) onToggle(node.id)
171
+ onSelect?.(node.id)
172
+ }}
173
+ className="flex min-w-0 flex-1 items-center gap-1.5 rounded-sm py-1.5 pr-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
174
+ >
175
+ {hasChildren ? (
176
+ isOpen ? (
177
+ <ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
178
+ ) : (
179
+ <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
180
+ )
181
+ ) : (
182
+ <span className="w-3.5 shrink-0" aria-hidden />
183
+ )}
184
+ {node.icon ? (
185
+ <span className="flex shrink-0 items-center" aria-hidden>
186
+ {node.icon}
187
+ </span>
188
+ ) : null}
189
+ <span className="min-w-0 flex-1 truncate">{node.label}</span>
190
+ {meta !== null && meta !== undefined ? (
191
+ <span className="shrink-0 text-xs font-normal tabular-nums text-muted-foreground">
192
+ {meta}
193
+ </span>
194
+ ) : null}
195
+ </button>
196
+ {actions !== null && actions !== undefined ? (
197
+ <div className="flex shrink-0 items-center gap-1 pr-1">
198
+ {actions}
199
+ </div>
200
+ ) : null}
201
+ </div>
202
+ {hasChildren && isOpen ? (
203
+ <ul role="group" className="flex flex-col gap-0.5">
204
+ {node.children!.map((child) => (
205
+ <TreeViewItem
206
+ key={child.id}
207
+ node={child}
208
+ depth={depth + 1}
209
+ expanded={expanded}
210
+ onToggle={onToggle}
211
+ selectedId={selectedId}
212
+ selectedIds={selectedIds}
213
+ selectionMode={selectionMode}
214
+ onSelect={onSelect}
215
+ renderNodeMeta={renderNodeMeta}
216
+ renderNodeActions={renderNodeActions}
217
+ getNodeRowProps={getNodeRowProps}
218
+ />
219
+ ))}
220
+ </ul>
221
+ ) : null}
222
+ </li>
223
+ )
224
+ }
225
+
226
+ export { TreeView }