@gunjo/ui 0.0.1-alpha.0
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/README.md +129 -0
- package/design/atoms-metadata.json +82 -0
- package/design/molecules-metadata.json +130 -0
- package/design/organisms-metadata.json +38 -0
- package/design/templates-metadata.json +38 -0
- package/package.json +158 -0
- package/src/components/atoms/Alert.tsx +63 -0
- package/src/components/atoms/Avatar.tsx +57 -0
- package/src/components/atoms/Badge.tsx +30 -0
- package/src/components/atoms/Button.tsx +29 -0
- package/src/components/atoms/ButtonVariants.ts +37 -0
- package/src/components/atoms/Checkbox.tsx +52 -0
- package/src/components/atoms/Img.tsx +102 -0
- package/src/components/atoms/Input.tsx +37 -0
- package/src/components/atoms/Kbd.tsx +22 -0
- package/src/components/atoms/Label.tsx +22 -0
- package/src/components/atoms/Progress.tsx +38 -0
- package/src/components/atoms/RadioGroup.tsx +86 -0
- package/src/components/atoms/Select.tsx +28 -0
- package/src/components/atoms/Separator.tsx +33 -0
- package/src/components/atoms/Skeleton.tsx +36 -0
- package/src/components/atoms/Slider.tsx +26 -0
- package/src/components/atoms/Spinner.tsx +34 -0
- package/src/components/atoms/Switch.tsx +47 -0
- package/src/components/atoms/Textarea.tsx +34 -0
- package/src/components/atoms/ToggleGroup.tsx +60 -0
- package/src/components/atoms/ToolPill.tsx +77 -0
- package/src/components/atoms/generated/default-variant-keys.ts +36 -0
- package/src/components/atoms/generated/variant-keys.ts +61 -0
- package/src/components/generated/component-manifest.ts +741 -0
- package/src/components/generated/component-style-hints.ts +1262 -0
- package/src/components/molecules/AIChatInput.tsx +140 -0
- package/src/components/molecules/AIChatMessage.tsx +109 -0
- package/src/components/molecules/Accordion.tsx +99 -0
- package/src/components/molecules/Breadcrumb.tsx +115 -0
- package/src/components/molecules/Calendar.tsx +60 -0
- package/src/components/molecules/Card.tsx +78 -0
- package/src/components/molecules/Carousel.tsx +261 -0
- package/src/components/molecules/Command.tsx +152 -0
- package/src/components/molecules/ContextMenu.tsx +200 -0
- package/src/components/molecules/Dialog.tsx +122 -0
- package/src/components/molecules/DropdownMenu.tsx +200 -0
- package/src/components/molecules/FilterButton.tsx +133 -0
- package/src/components/molecules/Form.tsx +90 -0
- package/src/components/molecules/HoverCard.tsx +29 -0
- package/src/components/molecules/List.tsx +120 -0
- package/src/components/molecules/Menubar.tsx +231 -0
- package/src/components/molecules/Modal.tsx +66 -0
- package/src/components/molecules/NotificationCenter.tsx +118 -0
- package/src/components/molecules/Pagination.tsx +118 -0
- package/src/components/molecules/Popover.tsx +31 -0
- package/src/components/molecules/ProgressWidget.tsx +40 -0
- package/src/components/molecules/Resizable.tsx +47 -0
- package/src/components/molecules/ScrollArea.tsx +48 -0
- package/src/components/molecules/Sheet.tsx +140 -0
- package/src/components/molecules/SidebarItem.tsx +134 -0
- package/src/components/molecules/SortButton.tsx +56 -0
- package/src/components/molecules/StatusBar.tsx +41 -0
- package/src/components/molecules/Stepper.tsx +108 -0
- package/src/components/molecules/Table.tsx +117 -0
- package/src/components/molecules/Tabs.tsx +64 -0
- package/src/components/molecules/Toast.tsx +57 -0
- package/src/components/molecules/Tooltip.tsx +30 -0
- package/src/components/molecules/generated/default-variant-keys.ts +22 -0
- package/src/components/molecules/generated/variant-keys.ts +33 -0
- package/src/components/organisms/AppRail.tsx +28 -0
- package/src/components/organisms/CommandPalette.tsx +58 -0
- package/src/components/organisms/FileUploader.tsx +151 -0
- package/src/components/organisms/FloatingPanel.tsx +46 -0
- package/src/components/organisms/InspectorPanel.tsx +65 -0
- package/src/components/organisms/RightRail.tsx +29 -0
- package/src/components/organisms/ShareModal.tsx +182 -0
- package/src/components/organisms/SpatialCanvas.tsx +36 -0
- package/src/components/organisms/ToastProvider.tsx +49 -0
- package/src/components/templates/AuthTemplate.tsx +58 -0
- package/src/components/templates/BannalyzeTemplate.tsx +55 -0
- package/src/components/templates/ChatTemplate.tsx +55 -0
- package/src/components/templates/DashboardTemplate.tsx +34 -0
- package/src/components/templates/EditorTemplate.tsx +46 -0
- package/src/components/templates/KanbanTemplate.tsx +38 -0
- package/src/components/templates/LandingTemplate.tsx +53 -0
- package/src/components/templates/MediaLibraryTemplate.tsx +55 -0
- package/src/components/templates/SettingsTemplate.tsx +48 -0
- package/src/globals.css +108 -0
- package/src/index.ts +89 -0
- package/src/lib/utils.ts +6 -0
- package/tailwind-preset.js +11 -0
- package/tailwind-theme-extend.cjs +86 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
import { Progress } from "../atoms/Progress"
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from "./Card"
|
|
7
|
+
|
|
8
|
+
export interface ProgressWidgetProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
title: string
|
|
10
|
+
value: number
|
|
11
|
+
max?: number
|
|
12
|
+
label?: string
|
|
13
|
+
subtext?: string
|
|
14
|
+
icon?: React.ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ProgressWidget = React.forwardRef<HTMLDivElement, ProgressWidgetProps>(
|
|
18
|
+
({ className, title, value, max = 100, label, subtext, icon, ...props }, ref) => {
|
|
19
|
+
const percentage = Math.round((value / max) * 100);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Card ref={ref} className={cn("flex flex-col w-[320px] rounded-lg border overflow-hidden", className)} {...props}>
|
|
23
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
24
|
+
<CardTitle className="text-sm font-medium">{title}</CardTitle>
|
|
25
|
+
{icon && <div className="text-muted-foreground">{icon}</div>}
|
|
26
|
+
</CardHeader>
|
|
27
|
+
<CardContent>
|
|
28
|
+
<div className="text-2xl font-bold">{label || `${percentage}%`}</div>
|
|
29
|
+
<Progress value={percentage} className="mt-2 h-2" />
|
|
30
|
+
{subtext && (
|
|
31
|
+
<p className="text-xs text-muted-foreground mt-2">{subtext}</p>
|
|
32
|
+
)}
|
|
33
|
+
</CardContent>
|
|
34
|
+
</Card>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
ProgressWidget.displayName = "ProgressWidget"
|
|
39
|
+
|
|
40
|
+
export { ProgressWidget }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { GripVertical } from "lucide-react"
|
|
5
|
+
// @ts-ignore - Runtime inspection shows Group/Separator but types might verify PanelGroup
|
|
6
|
+
import { Panel, Group as PanelGroup, Separator as PanelResizeHandle } from "react-resizable-panels"
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils"
|
|
9
|
+
|
|
10
|
+
const ResizablePanelGroup = ({
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}: React.HTMLAttributes<HTMLDivElement> & { direction: "vertical" | "horizontal"; autoSaveId?: string; onLayout?: (sizes: number[]) => void; id?: string }) => (
|
|
14
|
+
<PanelGroup
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col flex-col w-[520px] h-[240px]",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const ResizablePanel = Panel
|
|
24
|
+
|
|
25
|
+
const ResizableHandle = ({
|
|
26
|
+
withHandle,
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof PanelResizeHandle> & {
|
|
30
|
+
withHandle?: boolean
|
|
31
|
+
}) => (
|
|
32
|
+
<PanelResizeHandle
|
|
33
|
+
className={cn(
|
|
34
|
+
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{withHandle && (
|
|
40
|
+
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
|
|
41
|
+
<GripVertical className="h-2.5 w-2.5" />
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</PanelResizeHandle>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
const ScrollArea = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
|
11
|
+
>(({ className, children, ...props }, ref) => (
|
|
12
|
+
<ScrollAreaPrimitive.Root
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn("relative overflow-hidden flex flex-col w-[320px] h-[240px]", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
18
|
+
{children}
|
|
19
|
+
</ScrollAreaPrimitive.Viewport>
|
|
20
|
+
<ScrollBar />
|
|
21
|
+
<ScrollAreaPrimitive.Corner />
|
|
22
|
+
</ScrollAreaPrimitive.Root>
|
|
23
|
+
))
|
|
24
|
+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
|
25
|
+
|
|
26
|
+
const ScrollBar = React.forwardRef<
|
|
27
|
+
React.ElementRef<typeof ScrollAreaPrimitive.Scrollbar>,
|
|
28
|
+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Scrollbar>
|
|
29
|
+
>(({ className, orientation = "vertical", ...props }, ref) => (
|
|
30
|
+
<ScrollAreaPrimitive.Scrollbar
|
|
31
|
+
ref={ref}
|
|
32
|
+
orientation={orientation}
|
|
33
|
+
className={cn(
|
|
34
|
+
"flex touch-none select-none transition-colors",
|
|
35
|
+
orientation === "vertical" &&
|
|
36
|
+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
|
37
|
+
orientation === "horizontal" &&
|
|
38
|
+
"h-2.5 border-t border-t-transparent p-[1px]",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<ScrollAreaPrimitive.Thumb className="relative flex-1 rounded-full bg-border" />
|
|
44
|
+
</ScrollAreaPrimitive.Scrollbar>
|
|
45
|
+
))
|
|
46
|
+
ScrollBar.displayName = ScrollAreaPrimitive.Scrollbar.displayName
|
|
47
|
+
|
|
48
|
+
export { ScrollArea, ScrollBar }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
|
5
|
+
import { type VariantProps, cva } from "class-variance-authority"
|
|
6
|
+
import { X } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils"
|
|
9
|
+
|
|
10
|
+
const Sheet = SheetPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const SheetTrigger = SheetPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const SheetClose = SheetPrimitive.Close
|
|
15
|
+
|
|
16
|
+
const SheetPortal = SheetPrimitive.Portal
|
|
17
|
+
|
|
18
|
+
const SheetOverlay = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
|
21
|
+
>(({ className, ...props }, ref) => (
|
|
22
|
+
<SheetPrimitive.Overlay
|
|
23
|
+
className={cn(
|
|
24
|
+
"fixed inset-0 z-50 bg-overlay/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
ref={ref}
|
|
29
|
+
/>
|
|
30
|
+
))
|
|
31
|
+
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
|
32
|
+
|
|
33
|
+
const sheetVariants = cva(
|
|
34
|
+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
|
35
|
+
{
|
|
36
|
+
variants: {
|
|
37
|
+
side: {
|
|
38
|
+
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
39
|
+
bottom:
|
|
40
|
+
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
41
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
42
|
+
right:
|
|
43
|
+
"inset-y-0 right-0 h-full w-[384px] w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultVariants: {
|
|
47
|
+
side: "right",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
interface SheetContentProps
|
|
53
|
+
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
|
54
|
+
VariantProps<typeof sheetVariants> { }
|
|
55
|
+
|
|
56
|
+
const SheetContent = React.forwardRef<
|
|
57
|
+
React.ElementRef<typeof SheetPrimitive.Content>,
|
|
58
|
+
SheetContentProps
|
|
59
|
+
>(({ side = "right", className, children, ...props }, ref) => (
|
|
60
|
+
<SheetPortal>
|
|
61
|
+
<SheetOverlay />
|
|
62
|
+
<SheetPrimitive.Content
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn(sheetVariants({ side }), className)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
68
|
+
<X className="h-4 w-4" />
|
|
69
|
+
<span className="sr-only">Close</span>
|
|
70
|
+
</SheetPrimitive.Close>
|
|
71
|
+
{children}
|
|
72
|
+
</SheetPrimitive.Content>
|
|
73
|
+
</SheetPortal>
|
|
74
|
+
))
|
|
75
|
+
SheetContent.displayName = SheetPrimitive.Content.displayName
|
|
76
|
+
|
|
77
|
+
const SheetHeader = ({
|
|
78
|
+
className,
|
|
79
|
+
...props
|
|
80
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
81
|
+
<div
|
|
82
|
+
className={cn(
|
|
83
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
SheetHeader.displayName = "SheetHeader"
|
|
90
|
+
|
|
91
|
+
const SheetFooter = ({
|
|
92
|
+
className,
|
|
93
|
+
...props
|
|
94
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
95
|
+
<div
|
|
96
|
+
className={cn(
|
|
97
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
98
|
+
className
|
|
99
|
+
)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
SheetFooter.displayName = "SheetFooter"
|
|
104
|
+
|
|
105
|
+
const SheetTitle = React.forwardRef<
|
|
106
|
+
React.ElementRef<typeof SheetPrimitive.Title>,
|
|
107
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
|
108
|
+
>(({ className, ...props }, ref) => (
|
|
109
|
+
<SheetPrimitive.Title
|
|
110
|
+
ref={ref}
|
|
111
|
+
className={cn("text-lg font-semibold text-foreground", className)}
|
|
112
|
+
{...props}
|
|
113
|
+
/>
|
|
114
|
+
))
|
|
115
|
+
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
|
116
|
+
|
|
117
|
+
const SheetDescription = React.forwardRef<
|
|
118
|
+
React.ElementRef<typeof SheetPrimitive.Description>,
|
|
119
|
+
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
|
120
|
+
>(({ className, ...props }, ref) => (
|
|
121
|
+
<SheetPrimitive.Description
|
|
122
|
+
ref={ref}
|
|
123
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
))
|
|
127
|
+
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
|
128
|
+
|
|
129
|
+
export {
|
|
130
|
+
Sheet,
|
|
131
|
+
SheetPortal,
|
|
132
|
+
SheetOverlay,
|
|
133
|
+
SheetTrigger,
|
|
134
|
+
SheetClose,
|
|
135
|
+
SheetContent,
|
|
136
|
+
SheetHeader,
|
|
137
|
+
SheetFooter,
|
|
138
|
+
SheetTitle,
|
|
139
|
+
SheetDescription,
|
|
140
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { memo } from 'react';
|
|
4
|
+
import { Trash2, ChevronRight } from 'lucide-react';
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import type { SidebarItemVariantKey } from './generated/variant-keys';
|
|
7
|
+
import { sidebarItemDefaultVariantKey } from "./generated/default-variant-keys";
|
|
8
|
+
|
|
9
|
+
export interface SidebarItemProps {
|
|
10
|
+
icon: React.ReactNode;
|
|
11
|
+
label: string;
|
|
12
|
+
count?: number;
|
|
13
|
+
isActive: boolean;
|
|
14
|
+
onClick: () => void;
|
|
15
|
+
onDrop?: (e: React.DragEvent) => void;
|
|
16
|
+
onDragOver?: (e: React.DragEvent) => void;
|
|
17
|
+
dragOverId?: string | null;
|
|
18
|
+
dragAction?: 'nest' | 'reorder-above' | 'reorder-below' | null;
|
|
19
|
+
id: string;
|
|
20
|
+
level?: number;
|
|
21
|
+
hasChildren?: boolean;
|
|
22
|
+
isExpanded?: boolean;
|
|
23
|
+
onToggleExpand?: (e: React.MouseEvent) => void;
|
|
24
|
+
onDelete?: (e: React.MouseEvent) => void;
|
|
25
|
+
draggable?: boolean;
|
|
26
|
+
onDragStart?: (e: React.DragEvent) => void;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const SidebarItem = memo(({
|
|
31
|
+
icon,
|
|
32
|
+
label,
|
|
33
|
+
count,
|
|
34
|
+
isActive,
|
|
35
|
+
onClick,
|
|
36
|
+
onDrop,
|
|
37
|
+
onDragOver,
|
|
38
|
+
dragOverId,
|
|
39
|
+
dragAction,
|
|
40
|
+
id,
|
|
41
|
+
level = 0,
|
|
42
|
+
hasChildren = false,
|
|
43
|
+
isExpanded = false,
|
|
44
|
+
onToggleExpand,
|
|
45
|
+
onDelete,
|
|
46
|
+
draggable,
|
|
47
|
+
onDragStart,
|
|
48
|
+
className
|
|
49
|
+
}: SidebarItemProps) => {
|
|
50
|
+
const baseVariant: SidebarItemVariantKey = isActive ? "active" : sidebarItemDefaultVariantKey;
|
|
51
|
+
const variantClasses: Record<SidebarItemVariantKey, string> = {
|
|
52
|
+
active: "bg-secondary text-foreground",
|
|
53
|
+
default: "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const dragNestClass =
|
|
57
|
+
dragOverId === id && dragAction === "nest"
|
|
58
|
+
? "bg-primary/20 text-primary ring-2 ring-primary shadow-lg shadow-primary/40 scale-[1.02] z-10"
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<button
|
|
63
|
+
onClick={(e) => {
|
|
64
|
+
onClick();
|
|
65
|
+
if (hasChildren && onToggleExpand) {
|
|
66
|
+
onToggleExpand(e);
|
|
67
|
+
}
|
|
68
|
+
}}
|
|
69
|
+
onDragOver={onDragOver}
|
|
70
|
+
onDrop={onDrop}
|
|
71
|
+
draggable={draggable}
|
|
72
|
+
onDragStart={onDragStart}
|
|
73
|
+
className={cn(
|
|
74
|
+
"w-[320px] w-full h-9 flex flex-col flex-row items-center justify-between px-4 py-1.5 rounded-md text-sm transition-colors group relative",
|
|
75
|
+
dragNestClass ?? variantClasses[baseVariant],
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
style={{ paddingLeft: `${(level * 16) + 8}px` }}
|
|
79
|
+
>
|
|
80
|
+
{dragOverId === id && dragAction === 'reorder-above' && (
|
|
81
|
+
<div className="absolute top-0 left-0 right-0 h-0.5 bg-primary z-20 pointer-events-none" />
|
|
82
|
+
)}
|
|
83
|
+
{dragOverId === id && dragAction === 'reorder-below' && (
|
|
84
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary z-20 pointer-events-none" />
|
|
85
|
+
)}
|
|
86
|
+
<div className="flex items-center gap-2 overflow-hidden flex-1">
|
|
87
|
+
{/* Arrow / Spacer - Fixed Width for Alignment */}
|
|
88
|
+
<div
|
|
89
|
+
onClick={(e) => {
|
|
90
|
+
if (hasChildren && onToggleExpand) {
|
|
91
|
+
e.stopPropagation();
|
|
92
|
+
onToggleExpand(e);
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
className={cn(
|
|
96
|
+
"w-5 h-5 flex items-center justify-center cursor-pointer transition-colors",
|
|
97
|
+
hasChildren ? "" : "pointer-events-none"
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{hasChildren && (
|
|
101
|
+
<ChevronRight size={12} className={cn("transition-transform", isExpanded ? "rotate-90" : "")} />
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className={cn("flex-shrink-0", isActive ? "text-primary" : "text-muted-foreground group-hover:text-foreground")}>
|
|
106
|
+
{icon}
|
|
107
|
+
</div>
|
|
108
|
+
<span className="truncate" title={label}>{label}</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Right Side: Delete + Count */}
|
|
112
|
+
<div className="flex items-center -mr-1">
|
|
113
|
+
{/* Delete Button */}
|
|
114
|
+
<div
|
|
115
|
+
onClick={(e) => onDelete && onDelete(e)}
|
|
116
|
+
className={cn(
|
|
117
|
+
"w-6 h-6 flex items-center justify-center transition-opacity cursor-pointer mr-0.5",
|
|
118
|
+
onDelete
|
|
119
|
+
? (isActive
|
|
120
|
+
? "opacity-100 text-destructive"
|
|
121
|
+
: "text-muted-foreground/50 hover:text-destructive opacity-100 md:opacity-0 md:group-hover:opacity-100")
|
|
122
|
+
: "pointer-events-none"
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
{onDelete && <Trash2 size={12} />}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{count !== undefined && (
|
|
129
|
+
<span className="text-xs opacity-60 w-6 text-center tabular-nums text-muted-foreground translate-y-[0.5px]">{count}</span>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
</button>
|
|
133
|
+
);
|
|
134
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { ArrowDownAZ, ArrowUpAZ, ArrowUpDown, type LucideIcon } from "lucide-react"
|
|
5
|
+
import { Button, ButtonProps } from "../atoms/Button"
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
import { sortButtonDefaultVariantKey } from "./generated/default-variant-keys"
|
|
8
|
+
import { sortButtonVariantKeys, type SortButtonVariantKey } from "./generated/variant-keys"
|
|
9
|
+
|
|
10
|
+
export interface SortButtonProps extends Omit<ButtonProps, "onChange"> {
|
|
11
|
+
value?: SortButtonVariantKey
|
|
12
|
+
onSortChange?: (value: SortButtonVariantKey) => void
|
|
13
|
+
label?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const sortIconByValue: Record<SortButtonVariantKey, LucideIcon> = {
|
|
17
|
+
asc: ArrowUpAZ,
|
|
18
|
+
desc: ArrowDownAZ,
|
|
19
|
+
none: ArrowUpDown,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sortButtonStateClasses: Record<SortButtonVariantKey, string> = {
|
|
23
|
+
none: "flex flex-col items-center h-9 py-2 px-3 gap-2 rounded-md",
|
|
24
|
+
asc: "inline-flex items-center h-9 py-2 px-3 gap-2 rounded-md",
|
|
25
|
+
desc: "inline-flex items-center h-9 py-2 px-3 gap-2 rounded-md",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SortButton = React.forwardRef<HTMLButtonElement, SortButtonProps>(
|
|
29
|
+
({ className, value = sortButtonDefaultVariantKey, onSortChange, label = "Sort", variant = "ghost", size = "sm", ...props }, ref) => {
|
|
30
|
+
|
|
31
|
+
const handleClick = () => {
|
|
32
|
+
const currentIndex = sortButtonVariantKeys.indexOf(value)
|
|
33
|
+
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % sortButtonVariantKeys.length
|
|
34
|
+
onSortChange?.(sortButtonVariantKeys[nextIndex])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const Icon = sortIconByValue[value]
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Button
|
|
41
|
+
ref={ref}
|
|
42
|
+
variant={variant}
|
|
43
|
+
size={size}
|
|
44
|
+
className={cn(sortButtonStateClasses[value], className)}
|
|
45
|
+
onClick={handleClick}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<Icon className="h-4 w-4" />
|
|
49
|
+
<span>{label}</span>
|
|
50
|
+
</Button>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
SortButton.displayName = "SortButton"
|
|
55
|
+
|
|
56
|
+
export { SortButton }
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
export interface StatusBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
leftNode?: React.ReactNode
|
|
8
|
+
rightNode?: React.ReactNode
|
|
9
|
+
fixed?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const StatusBar = React.forwardRef<HTMLDivElement, StatusBarProps>(
|
|
13
|
+
({ className, leftNode, rightNode, fixed = true, children, ...props }, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
ref={ref}
|
|
17
|
+
className={cn(
|
|
18
|
+
"flex flex-col flex-row items-center w-[640px] justify-between px-4 py-1 bg-primary text-primary-foreground text-xs z-50 shadow-md",
|
|
19
|
+
fixed && "fixed bottom-0 left-0 right-0",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
>
|
|
24
|
+
<div className="flex items-center gap-4">
|
|
25
|
+
{leftNode}
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div className="flex items-center gap-2">
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div className="flex items-center gap-4">
|
|
33
|
+
{rightNode}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
StatusBar.displayName = "StatusBar"
|
|
40
|
+
|
|
41
|
+
export { StatusBar }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Check } from "lucide-react"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
const stepperVariants = cva("inline-flex items-start", {
|
|
7
|
+
variants: {
|
|
8
|
+
orientation: {
|
|
9
|
+
horizontal: "flex-row gap-0",
|
|
10
|
+
vertical: "flex-col gap-0",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: { orientation: "horizontal" },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export type StepperState = "completed" | "current" | "upcoming"
|
|
17
|
+
|
|
18
|
+
export interface StepperStep {
|
|
19
|
+
label: string
|
|
20
|
+
state: StepperState
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface StepperProps
|
|
24
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
25
|
+
VariantProps<typeof stepperVariants> {
|
|
26
|
+
steps: StepperStep[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function StepCircle({ state, index }: { state: StepperState; index: number }) {
|
|
30
|
+
const baseClasses =
|
|
31
|
+
"inline-flex h-8 w-8 items-center justify-center rounded-full text-sm font-semibold"
|
|
32
|
+
if (state === "completed") {
|
|
33
|
+
return (
|
|
34
|
+
<div className={cn(baseClasses, "bg-foreground text-background")}>
|
|
35
|
+
<Check className="h-4 w-4" />
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
if (state === "current") {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={cn(
|
|
43
|
+
baseClasses,
|
|
44
|
+
"border-2 border-foreground bg-background text-foreground"
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
{index + 1}
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
return (
|
|
52
|
+
<div className={cn(baseClasses, "bg-muted text-muted-foreground")}>
|
|
53
|
+
{index + 1}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
|
|
59
|
+
({ className, orientation, steps, ...props }, ref) => {
|
|
60
|
+
const isHorizontal = orientation !== "vertical"
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
ref={ref}
|
|
64
|
+
className={cn(stepperVariants({ orientation }), "p-4", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
{steps.map((step, idx) => {
|
|
68
|
+
const isLast = idx === steps.length - 1
|
|
69
|
+
return (
|
|
70
|
+
<React.Fragment key={`${step.label}-${idx}`}>
|
|
71
|
+
<div
|
|
72
|
+
className={cn(
|
|
73
|
+
"flex items-center gap-2",
|
|
74
|
+
isHorizontal ? "flex-col" : "flex-row"
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
<StepCircle state={step.state} index={idx} />
|
|
78
|
+
<span
|
|
79
|
+
className={cn(
|
|
80
|
+
"text-xs font-medium",
|
|
81
|
+
step.state === "upcoming"
|
|
82
|
+
? "text-muted-foreground"
|
|
83
|
+
: "text-foreground"
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
{step.label}
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
{!isLast ? (
|
|
90
|
+
<div
|
|
91
|
+
className={cn(
|
|
92
|
+
"bg-border",
|
|
93
|
+
isHorizontal
|
|
94
|
+
? "mx-2 mt-4 h-0.5 w-[60px] self-start"
|
|
95
|
+
: "my-2 ml-4 h-[24px] w-0.5 self-start"
|
|
96
|
+
)}
|
|
97
|
+
/>
|
|
98
|
+
) : null}
|
|
99
|
+
</React.Fragment>
|
|
100
|
+
)
|
|
101
|
+
})}
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
Stepper.displayName = "Stepper"
|
|
107
|
+
|
|
108
|
+
export { Stepper, stepperVariants }
|