@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.
- package/LICENSE +21 -0
- package/README.ja.md +90 -0
- package/README.md +52 -91
- package/package.json +47 -6
- package/src/components/display/Accordion.tsx +185 -0
- package/src/components/display/AccordionGroup.tsx +155 -0
- package/src/components/display/ActionDataTable.tsx +413 -0
- package/src/components/display/ActivityTimelineCard.tsx +483 -0
- package/src/components/display/AnalyticsCard.tsx +167 -0
- package/src/components/display/AssetCard.tsx +242 -0
- package/src/components/display/AssetGrid.tsx +164 -0
- package/src/components/display/Avatar.tsx +127 -0
- package/src/components/display/AvatarGroup.tsx +131 -0
- package/src/components/{atoms → display}/Badge.tsx +3 -3
- package/src/components/display/BarChart.tsx +247 -0
- package/src/components/{molecules → display}/Card.tsx +1 -1
- package/src/components/display/Carousel.tsx +593 -0
- package/src/components/display/ChartLegend.tsx +124 -0
- package/src/components/display/ChatMessage.tsx +382 -0
- package/src/components/display/ChoroplethMap.tsx +613 -0
- package/src/components/display/Code.tsx +42 -0
- package/src/components/display/CodeBlock.tsx +338 -0
- package/src/components/display/ColorSwatch.tsx +71 -0
- package/src/components/display/ConcentricProgressCard.tsx +545 -0
- package/src/components/display/DataTable.tsx +522 -0
- package/src/components/display/DistributionBar.tsx +102 -0
- package/src/components/display/DocNote.tsx +36 -0
- package/src/components/display/DonutChart.tsx +257 -0
- package/src/components/display/EmptyState.tsx +44 -0
- package/src/components/display/FileTree.tsx +180 -0
- package/src/components/display/GaugeChart.tsx +219 -0
- package/src/components/display/HeatmapChart.tsx +266 -0
- package/src/components/display/Icon.tsx +66 -0
- package/src/components/display/ImagePreview.tsx +140 -0
- package/src/components/{atoms → display}/Img.tsx +46 -12
- package/src/components/display/LabeledDonutCard.tsx +475 -0
- package/src/components/display/LineChart.tsx +464 -0
- package/src/components/{molecules → display}/List.tsx +20 -13
- package/src/components/display/MarkdownRenderer.tsx +157 -0
- package/src/components/display/MetadataList.tsx +81 -0
- package/src/components/display/MiniDistributionBarCard.tsx +314 -0
- package/src/components/display/PieChart.tsx +234 -0
- package/src/components/display/QuadrantMatrix.tsx +330 -0
- package/src/components/display/RadarChart.tsx +335 -0
- package/src/components/display/RadialBarChart.tsx +264 -0
- package/src/components/display/RetentionCohortCard.tsx +350 -0
- package/src/components/display/RibbonChart.tsx +618 -0
- package/src/components/display/SearchableAccordion.tsx +270 -0
- package/src/components/display/SegmentTimelineCard.tsx +452 -0
- package/src/components/display/SegmentedGaugeCard.tsx +607 -0
- package/src/components/display/Spacer.tsx +51 -0
- package/src/components/display/SparklineChart.tsx +394 -0
- package/src/components/display/StackedBarChart.tsx +393 -0
- package/src/components/display/Statistic.tsx +70 -0
- package/src/components/{molecules → display}/Table.tsx +22 -7
- package/src/components/display/Tag.tsx +80 -0
- package/src/components/display/TagEditor.tsx +141 -0
- package/src/components/display/Timeline.tsx +121 -0
- package/src/components/{atoms → display}/ToolPill.tsx +42 -18
- package/src/components/display/TreeView.tsx +226 -0
- package/src/components/display/chart-tooltip.tsx +423 -0
- package/src/components/display/chart-utils.ts +71 -0
- package/src/components/display/circular-chart-utils.ts +147 -0
- package/src/components/display/generated/default-variant-keys.ts +90 -0
- package/src/components/display/generated/variant-keys.ts +169 -0
- package/src/components/{atoms → feedback}/Alert.tsx +12 -5
- package/src/components/feedback/Banner.tsx +90 -0
- package/src/components/{molecules → feedback}/NotificationCenter.tsx +64 -31
- package/src/components/feedback/ProgressWidget.tsx +44 -0
- package/src/components/{atoms → feedback}/Spinner.tsx +2 -2
- package/src/components/{molecules → feedback}/StatusBar.tsx +4 -4
- package/src/components/feedback/StatusScreen.tsx +148 -0
- package/src/components/{molecules → feedback}/Stepper.tsx +10 -5
- package/src/components/feedback/Toast.tsx +108 -0
- package/src/components/feedback/ToastProvider.tsx +78 -0
- package/src/components/feedback/generated/default-variant-keys.ts +16 -0
- package/src/components/feedback/generated/variant-keys.ts +21 -0
- package/src/components/generated/component-manifest.ts +1568 -454
- package/src/components/generated/component-style-hints.ts +1958 -718
- package/src/components/{atoms → inputs}/ButtonVariants.ts +13 -3
- package/src/components/inputs/Calendar.tsx +212 -0
- package/src/components/inputs/ChatComposer.tsx +75 -0
- package/src/components/inputs/ChatInput.tsx +528 -0
- package/src/components/{atoms → inputs}/Checkbox.tsx +2 -2
- package/src/components/inputs/Combobox.tsx +175 -0
- package/src/components/inputs/CopyButton.tsx +187 -0
- package/src/components/inputs/DatePicker.tsx +519 -0
- package/src/components/inputs/DateRangePicker.tsx +878 -0
- package/src/components/inputs/EditableField.tsx +182 -0
- package/src/components/{organisms → inputs}/FileUploader.tsx +24 -9
- package/src/components/inputs/FilterButton.tsx +163 -0
- package/src/components/{molecules → inputs}/Form.tsx +20 -3
- package/src/components/{atoms → inputs}/Input.tsx +2 -0
- package/src/components/inputs/InputOTP.tsx +75 -0
- package/src/components/inputs/Mention.tsx +279 -0
- package/src/components/inputs/NumberInput.tsx +109 -0
- package/src/components/inputs/PasswordGroup.tsx +138 -0
- package/src/components/inputs/PasswordInput.tsx +74 -0
- package/src/components/inputs/PasswordRequirementList.tsx +96 -0
- package/src/components/inputs/PasswordStrengthMeter.tsx +93 -0
- package/src/components/inputs/PhoneInput.tsx +99 -0
- package/src/components/inputs/PostalCodeInput.tsx +98 -0
- package/src/components/inputs/RangeSlider.tsx +129 -0
- package/src/components/inputs/SearchInput.tsx +76 -0
- package/src/components/inputs/Select.tsx +39 -0
- package/src/components/{atoms → inputs}/Slider.tsx +18 -5
- package/src/components/{molecules → inputs}/SortButton.tsx +5 -2
- package/src/components/{atoms → inputs}/Switch.tsx +15 -4
- package/src/components/inputs/TagInput.tsx +114 -0
- package/src/components/{atoms → inputs}/Textarea.tsx +1 -0
- package/src/components/inputs/TimePicker.tsx +150 -0
- package/src/components/inputs/Toggle.tsx +48 -0
- package/src/components/{atoms → inputs}/ToggleGroup.tsx +2 -2
- package/src/components/inputs/TooltipButton.tsx +148 -0
- package/src/components/inputs/VoiceInputButton.tsx +317 -0
- package/src/components/inputs/calendar-holidays.ts +56 -0
- package/src/components/inputs/generated/default-variant-keys.ts +32 -0
- package/src/components/{atoms → inputs}/generated/variant-keys.ts +19 -27
- package/src/components/layout/AspectRatio.tsx +12 -0
- package/src/components/layout/AssetInspectorPanel.tsx +416 -0
- package/src/components/layout/Cluster.tsx +56 -0
- package/src/components/layout/CollapsiblePanelToggle.tsx +94 -0
- package/src/components/layout/Container.tsx +43 -0
- package/src/components/layout/DeviceFrame.tsx +227 -0
- package/src/components/layout/Grid.tsx +65 -0
- package/src/components/layout/HStack.tsx +73 -0
- package/src/components/{organisms → layout}/InspectorPanel.tsx +6 -5
- package/src/components/layout/MarqueeFrame.tsx +158 -0
- package/src/components/layout/Resizable.tsx +94 -0
- package/src/components/layout/ScrollArea.tsx +71 -0
- package/src/components/{organisms → layout}/SpatialCanvas.tsx +12 -7
- package/src/components/layout/VStack.tsx +69 -0
- package/src/components/layout/generated/default-variant-keys.ts +16 -0
- package/src/components/layout/generated/variant-keys.ts +21 -0
- package/src/components/{molecules → navigation}/Breadcrumb.tsx +5 -4
- package/src/components/navigation/Command.tsx +266 -0
- package/src/components/navigation/CommandPalette.tsx +83 -0
- package/src/components/navigation/DocumentPager.tsx +171 -0
- package/src/components/navigation/Footer.tsx +88 -0
- package/src/components/navigation/Header.tsx +80 -0
- package/src/components/{molecules → navigation}/Menubar.tsx +45 -12
- package/src/components/navigation/NavigationMenu.tsx +128 -0
- package/src/components/navigation/PageAside.tsx +84 -0
- package/src/components/{molecules → navigation}/Pagination.tsx +60 -7
- package/src/components/{organisms → navigation}/RightRail.tsx +1 -1
- package/src/components/navigation/Sidebar.tsx +223 -0
- package/src/components/navigation/SidebarItem.tsx +160 -0
- package/src/components/{molecules → navigation}/Tabs.tsx +2 -2
- package/src/components/navigation/TextLink.tsx +71 -0
- package/src/components/navigation/generated/default-variant-keys.ts +12 -0
- package/src/components/navigation/generated/variant-keys.ts +13 -0
- package/src/components/overlay/AIChatInput.tsx +5 -0
- package/src/components/overlay/AIChatMessage.tsx +6 -0
- package/src/components/overlay/AlertDialog.tsx +145 -0
- package/src/components/overlay/ChatPanel.tsx +180 -0
- package/src/components/{molecules → overlay}/ContextMenu.tsx +65 -29
- package/src/components/{molecules → overlay}/Dialog.tsx +21 -13
- package/src/components/overlay/Drawer.tsx +131 -0
- package/src/components/{molecules → overlay}/DropdownMenu.tsx +52 -17
- package/src/components/overlay/FloatingPanel.tsx +90 -0
- package/src/components/overlay/HoverCard.tsx +36 -0
- package/src/components/overlay/MediaLightbox.tsx +403 -0
- package/src/components/overlay/MediaPickerDialog.tsx +198 -0
- package/src/components/overlay/Modal.tsx +103 -0
- package/src/components/overlay/OnboardingFlow.tsx +172 -0
- package/src/components/overlay/Popover.tsx +36 -0
- package/src/components/overlay/ShareModal.tsx +324 -0
- package/src/components/{molecules → overlay}/Sheet.tsx +76 -19
- package/src/components/overlay/Tooltip.tsx +130 -0
- package/src/components/overlay/generated/default-variant-keys.ts +14 -0
- package/src/components/overlay/generated/variant-keys.ts +17 -0
- package/src/components/patterns/BlogTemplate.tsx +46 -0
- package/src/components/{templates → patterns}/DashboardTemplate.tsx +2 -2
- package/src/components/patterns/DocsTemplate.tsx +41 -0
- package/src/components/{templates → patterns}/MediaLibraryTemplate.tsx +1 -1
- package/src/components/patterns/OnboardingTemplate.tsx +32 -0
- package/src/components/patterns/PricingTemplate.tsx +106 -0
- package/src/globals.css +173 -22
- package/src/index.ts +177 -76
- package/tailwind-theme-extend.cjs +48 -3
- package/design/atoms-metadata.json +0 -82
- package/design/molecules-metadata.json +0 -130
- package/design/organisms-metadata.json +0 -38
- package/design/templates-metadata.json +0 -38
- package/src/components/atoms/Avatar.tsx +0 -57
- package/src/components/atoms/Select.tsx +0 -28
- package/src/components/atoms/generated/default-variant-keys.ts +0 -36
- package/src/components/molecules/AIChatInput.tsx +0 -140
- package/src/components/molecules/AIChatMessage.tsx +0 -109
- package/src/components/molecules/Accordion.tsx +0 -99
- package/src/components/molecules/Calendar.tsx +0 -60
- package/src/components/molecules/Carousel.tsx +0 -261
- package/src/components/molecules/Command.tsx +0 -152
- package/src/components/molecules/FilterButton.tsx +0 -133
- package/src/components/molecules/HoverCard.tsx +0 -29
- package/src/components/molecules/Modal.tsx +0 -66
- package/src/components/molecules/Popover.tsx +0 -31
- package/src/components/molecules/ProgressWidget.tsx +0 -40
- package/src/components/molecules/Resizable.tsx +0 -47
- package/src/components/molecules/ScrollArea.tsx +0 -48
- package/src/components/molecules/SidebarItem.tsx +0 -134
- package/src/components/molecules/Toast.tsx +0 -57
- package/src/components/molecules/Tooltip.tsx +0 -30
- package/src/components/molecules/generated/default-variant-keys.ts +0 -22
- package/src/components/molecules/generated/variant-keys.ts +0 -33
- package/src/components/organisms/CommandPalette.tsx +0 -58
- package/src/components/organisms/FloatingPanel.tsx +0 -46
- package/src/components/organisms/ShareModal.tsx +0 -182
- package/src/components/organisms/ToastProvider.tsx +0 -49
- /package/src/components/{atoms → display}/Kbd.tsx +0 -0
- /package/src/components/{atoms → display}/Separator.tsx +0 -0
- /package/src/components/{atoms → display}/Skeleton.tsx +0 -0
- /package/src/components/{atoms → feedback}/Progress.tsx +0 -0
- /package/src/components/{atoms → inputs}/Button.tsx +0 -0
- /package/src/components/{atoms → inputs}/Label.tsx +0 -0
- /package/src/components/{atoms → inputs}/RadioGroup.tsx +0 -0
- /package/src/components/{organisms → navigation}/AppRail.tsx +0 -0
- /package/src/components/{templates → patterns}/AuthTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/BannalyzeTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/ChatTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/EditorTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/KanbanTemplate.tsx +0 -0
- /package/src/components/{templates → patterns}/LandingTemplate.tsx +0 -0
- /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 {
|
|
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:
|
|
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
|
|
39
|
-
active: "bg-primary text-primary-foreground shadow-lg shadow-primary
|
|
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
|
|
47
|
-
active: "bg-destructive text-destructive-foreground shadow-lg shadow-destructive
|
|
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
|
-
|
|
64
|
+
const button = (
|
|
52
65
|
<button
|
|
53
66
|
type="button"
|
|
54
67
|
className={cn(
|
|
55
|
-
"group relative flex
|
|
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 }
|