@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
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
IconChevronLeft as ChevronLeft,
|
|
4
|
+
IconChevronLeftPipe as ChevronLeftPipe,
|
|
5
|
+
IconChevronRight as ChevronRight,
|
|
6
|
+
IconChevronRightPipe as ChevronRightPipe,
|
|
7
|
+
IconDots as MoreHorizontal,
|
|
8
|
+
} from "@tabler/icons-react";
|
|
3
9
|
import { cn } from "../../lib/utils"
|
|
4
|
-
import { ButtonProps } from "../
|
|
5
|
-
import { buttonVariants } from "../
|
|
10
|
+
import { ButtonProps } from "../inputs/Button"
|
|
11
|
+
import { buttonVariants } from "../inputs/ButtonVariants"
|
|
6
12
|
|
|
7
13
|
|
|
8
14
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
|
@@ -40,6 +46,10 @@ type PaginationLinkProps = {
|
|
|
40
46
|
} & Pick<ButtonProps, "size"> &
|
|
41
47
|
React.ComponentProps<"a">
|
|
42
48
|
|
|
49
|
+
type PaginationDirectionProps = React.ComponentProps<typeof PaginationLink> & {
|
|
50
|
+
label?: React.ReactNode
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
const PaginationLink = ({
|
|
44
54
|
className,
|
|
45
55
|
isActive,
|
|
@@ -53,6 +63,7 @@ const PaginationLink = ({
|
|
|
53
63
|
variant: isActive ? "outline" : "ghost",
|
|
54
64
|
size,
|
|
55
65
|
}),
|
|
66
|
+
"aria-disabled:pointer-events-none aria-disabled:opacity-50",
|
|
56
67
|
className
|
|
57
68
|
)}
|
|
58
69
|
{...props}
|
|
@@ -60,10 +71,30 @@ const PaginationLink = ({
|
|
|
60
71
|
)
|
|
61
72
|
PaginationLink.displayName = "PaginationLink"
|
|
62
73
|
|
|
74
|
+
const PaginationFirst = ({
|
|
75
|
+
className,
|
|
76
|
+
children,
|
|
77
|
+
label = "First",
|
|
78
|
+
...props
|
|
79
|
+
}: PaginationDirectionProps) => (
|
|
80
|
+
<PaginationLink
|
|
81
|
+
aria-label="Go to first page"
|
|
82
|
+
size="default"
|
|
83
|
+
className={cn("gap-1 pl-2.5", className)}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
<ChevronLeftPipe className="h-4 w-4" />
|
|
87
|
+
<span>{children ?? label}</span>
|
|
88
|
+
</PaginationLink>
|
|
89
|
+
)
|
|
90
|
+
PaginationFirst.displayName = "PaginationFirst"
|
|
91
|
+
|
|
63
92
|
const PaginationPrevious = ({
|
|
64
93
|
className,
|
|
94
|
+
children,
|
|
95
|
+
label = "Previous",
|
|
65
96
|
...props
|
|
66
|
-
}:
|
|
97
|
+
}: PaginationDirectionProps) => (
|
|
67
98
|
<PaginationLink
|
|
68
99
|
aria-label="Go to previous page"
|
|
69
100
|
size="default"
|
|
@@ -71,27 +102,47 @@ const PaginationPrevious = ({
|
|
|
71
102
|
{...props}
|
|
72
103
|
>
|
|
73
104
|
<ChevronLeft className="h-4 w-4" />
|
|
74
|
-
<span>
|
|
105
|
+
<span>{children ?? label}</span>
|
|
75
106
|
</PaginationLink>
|
|
76
107
|
)
|
|
77
108
|
PaginationPrevious.displayName = "PaginationPrevious"
|
|
78
109
|
|
|
79
110
|
const PaginationNext = ({
|
|
80
111
|
className,
|
|
112
|
+
children,
|
|
113
|
+
label = "Next",
|
|
81
114
|
...props
|
|
82
|
-
}:
|
|
115
|
+
}: PaginationDirectionProps) => (
|
|
83
116
|
<PaginationLink
|
|
84
117
|
aria-label="Go to next page"
|
|
85
118
|
size="default"
|
|
86
119
|
className={cn("gap-1 pr-2.5", className)}
|
|
87
120
|
{...props}
|
|
88
121
|
>
|
|
89
|
-
<span>
|
|
122
|
+
<span>{children ?? label}</span>
|
|
90
123
|
<ChevronRight className="h-4 w-4" />
|
|
91
124
|
</PaginationLink>
|
|
92
125
|
)
|
|
93
126
|
PaginationNext.displayName = "PaginationNext"
|
|
94
127
|
|
|
128
|
+
const PaginationLast = ({
|
|
129
|
+
className,
|
|
130
|
+
children,
|
|
131
|
+
label = "Last",
|
|
132
|
+
...props
|
|
133
|
+
}: PaginationDirectionProps) => (
|
|
134
|
+
<PaginationLink
|
|
135
|
+
aria-label="Go to last page"
|
|
136
|
+
size="default"
|
|
137
|
+
className={cn("gap-1 pr-2.5", className)}
|
|
138
|
+
{...props}
|
|
139
|
+
>
|
|
140
|
+
<span>{children ?? label}</span>
|
|
141
|
+
<ChevronRightPipe className="h-4 w-4" />
|
|
142
|
+
</PaginationLink>
|
|
143
|
+
)
|
|
144
|
+
PaginationLast.displayName = "PaginationLast"
|
|
145
|
+
|
|
95
146
|
const PaginationEllipsis = ({
|
|
96
147
|
className,
|
|
97
148
|
...props
|
|
@@ -111,7 +162,9 @@ export {
|
|
|
111
162
|
Pagination,
|
|
112
163
|
PaginationContent,
|
|
113
164
|
PaginationEllipsis,
|
|
165
|
+
PaginationFirst,
|
|
114
166
|
PaginationItem,
|
|
167
|
+
PaginationLast,
|
|
115
168
|
PaginationLink,
|
|
116
169
|
PaginationNext,
|
|
117
170
|
PaginationPrevious,
|
|
@@ -13,7 +13,7 @@ const RightRail = React.forwardRef<HTMLDivElement, RightRailProps>(
|
|
|
13
13
|
<div
|
|
14
14
|
ref={ref}
|
|
15
15
|
className={cn(
|
|
16
|
-
"flex
|
|
16
|
+
"z-30 flex h-full min-h-0 flex-shrink-0 flex-col border-l border-border bg-background",
|
|
17
17
|
width,
|
|
18
18
|
className
|
|
19
19
|
)}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
IconChevronLeft as ChevronLeft,
|
|
6
|
+
IconChevronRight as ChevronRight,
|
|
7
|
+
} from "@tabler/icons-react"
|
|
8
|
+
|
|
9
|
+
import { cn } from "../../lib/utils"
|
|
10
|
+
import { TooltipButton, type TooltipButtonProps } from "../inputs/TooltipButton"
|
|
11
|
+
|
|
12
|
+
interface SidebarContextValue {
|
|
13
|
+
collapsed: boolean
|
|
14
|
+
setCollapsed: (collapsed: boolean) => void
|
|
15
|
+
toggleCollapsed: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SidebarContext = React.createContext<SidebarContextValue | null>(null)
|
|
19
|
+
|
|
20
|
+
export function useSidebar() {
|
|
21
|
+
const ctx = React.useContext(SidebarContext)
|
|
22
|
+
if (!ctx)
|
|
23
|
+
throw new Error("useSidebar must be used within <SidebarProvider>")
|
|
24
|
+
return ctx
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SidebarProviderProps {
|
|
28
|
+
defaultCollapsed?: boolean
|
|
29
|
+
collapsed?: boolean
|
|
30
|
+
onCollapsedChange?: (collapsed: boolean) => void
|
|
31
|
+
children: React.ReactNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const SidebarProvider = ({
|
|
35
|
+
defaultCollapsed = false,
|
|
36
|
+
collapsed: controlledCollapsed,
|
|
37
|
+
onCollapsedChange,
|
|
38
|
+
children,
|
|
39
|
+
}: SidebarProviderProps) => {
|
|
40
|
+
const [internalCollapsed, setInternalCollapsed] =
|
|
41
|
+
React.useState(defaultCollapsed)
|
|
42
|
+
const isControlled = controlledCollapsed !== undefined
|
|
43
|
+
const collapsed = isControlled ? controlledCollapsed : internalCollapsed
|
|
44
|
+
|
|
45
|
+
const setCollapsed = React.useCallback(
|
|
46
|
+
(value: boolean) => {
|
|
47
|
+
if (!isControlled) setInternalCollapsed(value)
|
|
48
|
+
onCollapsedChange?.(value)
|
|
49
|
+
},
|
|
50
|
+
[isControlled, onCollapsedChange]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const toggleCollapsed = React.useCallback(
|
|
54
|
+
() => setCollapsed(!collapsed),
|
|
55
|
+
[collapsed, setCollapsed]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const value = React.useMemo(
|
|
59
|
+
() => ({ collapsed, setCollapsed, toggleCollapsed }),
|
|
60
|
+
[collapsed, setCollapsed, toggleCollapsed]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<SidebarContext.Provider value={value}>
|
|
65
|
+
{children}
|
|
66
|
+
</SidebarContext.Provider>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
SidebarProvider.displayName = "SidebarProvider"
|
|
70
|
+
|
|
71
|
+
const Sidebar = React.forwardRef<
|
|
72
|
+
HTMLElement,
|
|
73
|
+
React.HTMLAttributes<HTMLElement>
|
|
74
|
+
>(({ className, ...props }, ref) => {
|
|
75
|
+
const { collapsed } = useSidebar()
|
|
76
|
+
return (
|
|
77
|
+
<aside
|
|
78
|
+
ref={ref}
|
|
79
|
+
data-collapsed={collapsed}
|
|
80
|
+
className={cn(
|
|
81
|
+
"relative flex h-full flex-col overflow-visible border-r bg-muted/40 transition-[width] duration-200",
|
|
82
|
+
collapsed ? "w-[60px]" : "w-[240px]",
|
|
83
|
+
className
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
})
|
|
89
|
+
Sidebar.displayName = "Sidebar"
|
|
90
|
+
|
|
91
|
+
const SidebarHeader = React.forwardRef<
|
|
92
|
+
HTMLDivElement,
|
|
93
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
94
|
+
>(({ className, ...props }, ref) => (
|
|
95
|
+
<div
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex items-center gap-2 border-b bg-background px-4 py-3",
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
))
|
|
104
|
+
SidebarHeader.displayName = "SidebarHeader"
|
|
105
|
+
|
|
106
|
+
const SidebarBody = React.forwardRef<
|
|
107
|
+
HTMLDivElement,
|
|
108
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
109
|
+
>(({ className, ...props }, ref) => (
|
|
110
|
+
<div
|
|
111
|
+
ref={ref}
|
|
112
|
+
className={cn(
|
|
113
|
+
"flex-1 overflow-y-auto flex flex-col gap-0.5 p-2",
|
|
114
|
+
// Scrollbar UX: hide by default, reveal a thin, track-less
|
|
115
|
+
// thumb only while the cursor is inside the sidebar. Firefox
|
|
116
|
+
// uses `scrollbar-width` / `scrollbar-color`; WebKit uses
|
|
117
|
+
// ::-webkit-scrollbar pseudo-elements. Consumers can override
|
|
118
|
+
// by passing their own scrollbar utilities through className.
|
|
119
|
+
"[scrollbar-width:none] hover:[scrollbar-width:thin]",
|
|
120
|
+
"[scrollbar-color:transparent_transparent] hover:[scrollbar-color:hsl(var(--border))_transparent]",
|
|
121
|
+
"[&::-webkit-scrollbar]:w-0 hover:[&::-webkit-scrollbar]:w-1.5",
|
|
122
|
+
"[&::-webkit-scrollbar-track]:bg-transparent",
|
|
123
|
+
"[&::-webkit-scrollbar-thumb]:bg-transparent hover:[&::-webkit-scrollbar-thumb]:bg-border/60",
|
|
124
|
+
"[&::-webkit-scrollbar-thumb]:rounded-full",
|
|
125
|
+
className
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
/>
|
|
129
|
+
))
|
|
130
|
+
SidebarBody.displayName = "SidebarBody"
|
|
131
|
+
|
|
132
|
+
const SidebarFooter = React.forwardRef<
|
|
133
|
+
HTMLDivElement,
|
|
134
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
135
|
+
>(({ className, ...props }, ref) => (
|
|
136
|
+
<div
|
|
137
|
+
ref={ref}
|
|
138
|
+
className={cn(
|
|
139
|
+
"flex items-center gap-2 border-t bg-background px-4 py-3",
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
))
|
|
145
|
+
SidebarFooter.displayName = "SidebarFooter"
|
|
146
|
+
|
|
147
|
+
export interface SidebarToggleProps
|
|
148
|
+
extends Omit<TooltipButtonProps, "children" | "tooltip" | "onClick"> {
|
|
149
|
+
expandLabel?: React.ReactNode
|
|
150
|
+
collapseLabel?: React.ReactNode
|
|
151
|
+
placement?: "center" | "header" | "footer"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
|
|
155
|
+
(
|
|
156
|
+
{
|
|
157
|
+
className,
|
|
158
|
+
expandLabel = "Expand sidebar",
|
|
159
|
+
collapseLabel = "Collapse sidebar",
|
|
160
|
+
placement = "footer",
|
|
161
|
+
tooltipSide = "right",
|
|
162
|
+
tooltipAlign = "center",
|
|
163
|
+
tooltipSideOffset = 8,
|
|
164
|
+
...props
|
|
165
|
+
},
|
|
166
|
+
ref
|
|
167
|
+
) => {
|
|
168
|
+
const { collapsed, toggleCollapsed } = useSidebar()
|
|
169
|
+
const label = collapsed ? expandLabel : collapseLabel
|
|
170
|
+
const placementClass = {
|
|
171
|
+
center: "top-1/2 -translate-y-1/2",
|
|
172
|
+
header: "top-[52px] -translate-y-1/2",
|
|
173
|
+
footer: "bottom-[52px] translate-y-1/2",
|
|
174
|
+
}[placement]
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<TooltipButton
|
|
178
|
+
ref={ref}
|
|
179
|
+
type="button"
|
|
180
|
+
variant="outline"
|
|
181
|
+
size="icon"
|
|
182
|
+
className={cn(
|
|
183
|
+
"absolute right-0 z-20 h-7 w-7 translate-x-1/2 rounded-full bg-background shadow-sm",
|
|
184
|
+
placementClass,
|
|
185
|
+
className
|
|
186
|
+
)}
|
|
187
|
+
tooltip={label}
|
|
188
|
+
tooltipSide={tooltipSide}
|
|
189
|
+
tooltipAlign={tooltipAlign}
|
|
190
|
+
tooltipSideOffset={tooltipSideOffset}
|
|
191
|
+
onClick={toggleCollapsed}
|
|
192
|
+
aria-label={typeof label === "string" ? label : undefined}
|
|
193
|
+
{...props}
|
|
194
|
+
>
|
|
195
|
+
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
|
196
|
+
</TooltipButton>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
SidebarToggle.displayName = "SidebarToggle"
|
|
201
|
+
|
|
202
|
+
const SidebarSeparator = React.forwardRef<
|
|
203
|
+
HTMLDivElement,
|
|
204
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
205
|
+
>(({ className, ...props }, ref) => (
|
|
206
|
+
<div
|
|
207
|
+
ref={ref}
|
|
208
|
+
role="separator"
|
|
209
|
+
className={cn("mx-2 my-2 h-px bg-border", className)}
|
|
210
|
+
{...props}
|
|
211
|
+
/>
|
|
212
|
+
))
|
|
213
|
+
SidebarSeparator.displayName = "SidebarSeparator"
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
Sidebar,
|
|
217
|
+
SidebarProvider,
|
|
218
|
+
SidebarHeader,
|
|
219
|
+
SidebarBody,
|
|
220
|
+
SidebarFooter,
|
|
221
|
+
SidebarToggle,
|
|
222
|
+
SidebarSeparator,
|
|
223
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
|
+
import { IconTrash as Trash2, IconChevronRight as ChevronRight } from "@tabler/icons-react";
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import type { SidebarItemVariantKey } from './generated/variant-keys';
|
|
7
|
+
import { sidebarItemDefaultVariantKey } from "./generated/default-variant-keys";
|
|
8
|
+
import { TooltipButton } from "../inputs/TooltipButton";
|
|
9
|
+
|
|
10
|
+
export interface SidebarItemProps {
|
|
11
|
+
icon: React.ReactNode;
|
|
12
|
+
label: string;
|
|
13
|
+
count?: number;
|
|
14
|
+
isActive: boolean;
|
|
15
|
+
onClick: () => void;
|
|
16
|
+
onDrop?: (e: React.DragEvent) => void;
|
|
17
|
+
onDragOver?: (e: React.DragEvent) => void;
|
|
18
|
+
dragOverId?: string | null;
|
|
19
|
+
dragAction?: 'nest' | 'reorder-above' | 'reorder-below' | null;
|
|
20
|
+
id: string;
|
|
21
|
+
level?: number;
|
|
22
|
+
hasChildren?: boolean;
|
|
23
|
+
isExpanded?: boolean;
|
|
24
|
+
reserveChevronSpace?: boolean;
|
|
25
|
+
onToggleExpand?: (e: React.MouseEvent) => void;
|
|
26
|
+
onDelete?: (e: React.MouseEvent) => void;
|
|
27
|
+
deleteLabel?: React.ReactNode;
|
|
28
|
+
draggable?: boolean;
|
|
29
|
+
onDragStart?: (e: React.DragEvent) => void;
|
|
30
|
+
onDragEnd?: (e: React.DragEvent) => void;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const SidebarItem = memo(({
|
|
35
|
+
icon,
|
|
36
|
+
label,
|
|
37
|
+
count,
|
|
38
|
+
isActive,
|
|
39
|
+
onClick,
|
|
40
|
+
onDrop,
|
|
41
|
+
onDragOver,
|
|
42
|
+
dragOverId,
|
|
43
|
+
dragAction,
|
|
44
|
+
id,
|
|
45
|
+
level = 0,
|
|
46
|
+
hasChildren = false,
|
|
47
|
+
isExpanded = false,
|
|
48
|
+
reserveChevronSpace = true,
|
|
49
|
+
onToggleExpand,
|
|
50
|
+
onDelete,
|
|
51
|
+
deleteLabel = "Delete",
|
|
52
|
+
draggable,
|
|
53
|
+
onDragStart,
|
|
54
|
+
onDragEnd,
|
|
55
|
+
className
|
|
56
|
+
}: SidebarItemProps) => {
|
|
57
|
+
const baseVariant: SidebarItemVariantKey = isActive ? "active" : sidebarItemDefaultVariantKey;
|
|
58
|
+
const variantClasses: Record<SidebarItemVariantKey, string> = {
|
|
59
|
+
active: "bg-secondary text-foreground",
|
|
60
|
+
default: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const dragNestClass =
|
|
64
|
+
dragOverId === id && dragAction === "nest"
|
|
65
|
+
? "bg-primary-subtle text-primary-subtle-foreground ring-2 ring-primary-border shadow-lg shadow-primary-border scale-[1.02] z-10"
|
|
66
|
+
: null;
|
|
67
|
+
const showChevronSlot = hasChildren || reserveChevronSpace;
|
|
68
|
+
const deleteAriaLabel = typeof deleteLabel === "string" ? deleteLabel : undefined;
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
onDragOver={onDragOver}
|
|
73
|
+
onDrop={onDrop}
|
|
74
|
+
draggable={draggable}
|
|
75
|
+
onDragStart={onDragStart}
|
|
76
|
+
onDragEnd={onDragEnd}
|
|
77
|
+
className={cn(
|
|
78
|
+
"group relative flex h-9 w-full cursor-pointer items-center rounded-md text-sm transition-colors",
|
|
79
|
+
dragNestClass ?? variantClasses[baseVariant],
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
{dragOverId === id && dragAction === 'reorder-above' && (
|
|
84
|
+
<div className="absolute top-0 left-0 right-0 h-0.5 bg-primary z-20 pointer-events-none" />
|
|
85
|
+
)}
|
|
86
|
+
{dragOverId === id && dragAction === 'reorder-below' && (
|
|
87
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary z-20 pointer-events-none" />
|
|
88
|
+
)}
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
onClick={(e) => {
|
|
92
|
+
onClick();
|
|
93
|
+
if (hasChildren && onToggleExpand) {
|
|
94
|
+
onToggleExpand(e);
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
data-sidebar-item-button
|
|
98
|
+
className="flex h-full min-w-0 flex-1 items-center gap-2 overflow-hidden py-1.5 pr-0 text-left"
|
|
99
|
+
style={{ "--sidebar-item-indent": `calc(0.5rem + ${level}rem)` } as React.CSSProperties}
|
|
100
|
+
aria-current={isActive ? "page" : undefined}
|
|
101
|
+
>
|
|
102
|
+
{showChevronSlot ? (
|
|
103
|
+
<div
|
|
104
|
+
onClick={(e) => {
|
|
105
|
+
if (hasChildren && onToggleExpand) {
|
|
106
|
+
e.stopPropagation();
|
|
107
|
+
onToggleExpand(e);
|
|
108
|
+
}
|
|
109
|
+
}}
|
|
110
|
+
className={cn(
|
|
111
|
+
"w-5 h-5 flex items-center justify-center cursor-pointer transition-colors",
|
|
112
|
+
hasChildren ? "" : "pointer-events-none"
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
{hasChildren && (
|
|
116
|
+
<ChevronRight size={12} className={cn("transition-transform", isExpanded ? "rotate-90" : "")} />
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
) : null}
|
|
120
|
+
|
|
121
|
+
<div className={cn("flex-shrink-0", isActive ? "text-primary" : "text-muted-foreground group-hover:text-foreground")}>
|
|
122
|
+
{icon}
|
|
123
|
+
</div>
|
|
124
|
+
<span className="truncate" title={label}>{label}</span>
|
|
125
|
+
</button>
|
|
126
|
+
|
|
127
|
+
{/* Right Side: Delete + Count */}
|
|
128
|
+
<div className="grid w-14 shrink-0 grid-cols-[1.5rem_1.5rem] items-center justify-end">
|
|
129
|
+
{onDelete ? (
|
|
130
|
+
<TooltipButton
|
|
131
|
+
type="button"
|
|
132
|
+
variant="ghost"
|
|
133
|
+
size="icon"
|
|
134
|
+
className={cn(
|
|
135
|
+
"w-6 h-6 transition-opacity",
|
|
136
|
+
isActive
|
|
137
|
+
? "text-destructive"
|
|
138
|
+
: "text-muted-foreground/50 hover:text-destructive opacity-100 md:opacity-0 md:group-hover:opacity-100"
|
|
139
|
+
)}
|
|
140
|
+
tooltip={deleteLabel}
|
|
141
|
+
tooltipSide="right"
|
|
142
|
+
aria-label={deleteAriaLabel}
|
|
143
|
+
onClick={(e) => {
|
|
144
|
+
e.stopPropagation();
|
|
145
|
+
onDelete(e);
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<Trash2 size={12} />
|
|
149
|
+
</TooltipButton>
|
|
150
|
+
) : (
|
|
151
|
+
<div className="h-6 w-6" aria-hidden />
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{count !== undefined && (
|
|
155
|
+
<span className="text-xs opacity-60 w-6 text-center tabular-nums text-muted-foreground translate-y-[0.5px]">{count}</span>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
});
|
|
@@ -23,7 +23,7 @@ const TabsList = React.forwardRef<
|
|
|
23
23
|
<TabsPrimitive.List
|
|
24
24
|
ref={ref}
|
|
25
25
|
className={cn(
|
|
26
|
-
"inline-flex h-
|
|
26
|
+
"inline-flex min-h-12 items-center justify-center overflow-y-hidden rounded-md bg-muted p-1.5 text-muted-foreground",
|
|
27
27
|
className
|
|
28
28
|
)}
|
|
29
29
|
{...props}
|
|
@@ -38,7 +38,7 @@ const TabsTrigger = React.forwardRef<
|
|
|
38
38
|
<TabsPrimitive.Trigger
|
|
39
39
|
ref={ref}
|
|
40
40
|
className={cn(
|
|
41
|
-
"inline-flex h-9 items-center justify-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
41
|
+
"inline-flex h-9 cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
42
42
|
className
|
|
43
43
|
)}
|
|
44
44
|
{...props}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { IconExternalLink } from "@tabler/icons-react"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { Icon } from "../display/Icon"
|
|
8
|
+
import { textLinkDefaultVariantKey } from "./generated/default-variant-keys"
|
|
9
|
+
import type { TextLinkVariantKey } from "./generated/variant-keys"
|
|
10
|
+
|
|
11
|
+
const textLinkVariantClasses: Record<TextLinkVariantKey, string> = {
|
|
12
|
+
default: "font-medium text-primary underline underline-offset-4 hover:text-primary-strong",
|
|
13
|
+
muted: "font-medium text-foreground underline underline-offset-4 hover:text-primary",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TextLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
17
|
+
variant?: TextLinkVariantKey
|
|
18
|
+
newTabLabel?: string
|
|
19
|
+
external?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeRel(rel: string | undefined, opensNewTab: boolean): string | undefined {
|
|
23
|
+
if (!opensNewTab) return rel
|
|
24
|
+
const tokens = new Set((rel ?? "").split(/\s+/).filter(Boolean))
|
|
25
|
+
tokens.add("noreferrer")
|
|
26
|
+
return Array.from(tokens).join(" ")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TextLink = React.forwardRef<HTMLAnchorElement, TextLinkProps>(
|
|
30
|
+
(
|
|
31
|
+
{
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
external,
|
|
35
|
+
newTabLabel = "opens in a new tab",
|
|
36
|
+
rel,
|
|
37
|
+
target,
|
|
38
|
+
variant = textLinkDefaultVariantKey,
|
|
39
|
+
...props
|
|
40
|
+
},
|
|
41
|
+
ref
|
|
42
|
+
) => {
|
|
43
|
+
const opensNewTab = target === "_blank"
|
|
44
|
+
const showExternalIcon = opensNewTab || Boolean(external)
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<a
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"inline-flex items-center gap-1 p-0 align-baseline text-sm transition-colors",
|
|
51
|
+
textLinkVariantClasses[variant],
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
rel={normalizeRel(rel, opensNewTab)}
|
|
55
|
+
target={target}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
<span>{children}</span>
|
|
59
|
+
{showExternalIcon ? (
|
|
60
|
+
<>
|
|
61
|
+
<Icon icon={IconExternalLink} size="xs" decorative className="translate-y-px" />
|
|
62
|
+
{opensNewTab && newTabLabel ? <span className="sr-only">({newTabLabel})</span> : null}
|
|
63
|
+
</>
|
|
64
|
+
) : null}
|
|
65
|
+
</a>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
TextLink.displayName = "TextLink"
|
|
70
|
+
|
|
71
|
+
export { TextLink }
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by `npm run design:sync:components`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
import type { SidebarItemVariantKey, TextLinkVariantKey } from "./variant-keys";
|
|
5
|
+
|
|
6
|
+
export const sidebarItemDefaultVariantKey: SidebarItemVariantKey = "default";
|
|
7
|
+
export const textLinkDefaultVariantKey: TextLinkVariantKey = "default";
|
|
8
|
+
|
|
9
|
+
export const navigationDefaultVariantKeys = {
|
|
10
|
+
sidebarItem: sidebarItemDefaultVariantKey,
|
|
11
|
+
textLink: textLinkDefaultVariantKey,
|
|
12
|
+
} as const;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by `npm run design:sync:components`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
export const sidebarItemVariantKeys = ["active", "default"] as const;
|
|
5
|
+
export type SidebarItemVariantKey = (typeof sidebarItemVariantKeys)[number];
|
|
6
|
+
|
|
7
|
+
export const textLinkVariantKeys = ["default", "muted"] as const;
|
|
8
|
+
export type TextLinkVariantKey = (typeof textLinkVariantKeys)[number];
|
|
9
|
+
|
|
10
|
+
export const navigationVariantKeys = {
|
|
11
|
+
sidebarItem: sidebarItemVariantKeys,
|
|
12
|
+
textLink: textLinkVariantKeys,
|
|
13
|
+
} as const;
|