@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,117 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const Table = React.forwardRef<
|
|
6
|
+
HTMLTableElement,
|
|
7
|
+
React.HTMLAttributes<HTMLTableElement>
|
|
8
|
+
>(({ className, ...props }, ref) => (
|
|
9
|
+
<div className="relative flex flex-col w-[400px] w-full overflow-auto rounded-md rounded-lg border bg-card">
|
|
10
|
+
<table
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
</div>
|
|
16
|
+
))
|
|
17
|
+
Table.displayName = "Table"
|
|
18
|
+
|
|
19
|
+
const TableHeader = React.forwardRef<
|
|
20
|
+
HTMLTableSectionElement,
|
|
21
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
|
24
|
+
))
|
|
25
|
+
TableHeader.displayName = "TableHeader"
|
|
26
|
+
|
|
27
|
+
const TableBody = React.forwardRef<
|
|
28
|
+
HTMLTableSectionElement,
|
|
29
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
30
|
+
>(({ className, ...props }, ref) => (
|
|
31
|
+
<tbody
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
))
|
|
37
|
+
TableBody.displayName = "TableBody"
|
|
38
|
+
|
|
39
|
+
const TableFooter = React.forwardRef<
|
|
40
|
+
HTMLTableSectionElement,
|
|
41
|
+
React.HTMLAttributes<HTMLTableSectionElement>
|
|
42
|
+
>(({ className, ...props }, ref) => (
|
|
43
|
+
<tfoot
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn(
|
|
46
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
))
|
|
52
|
+
TableFooter.displayName = "TableFooter"
|
|
53
|
+
|
|
54
|
+
const TableRow = React.forwardRef<
|
|
55
|
+
HTMLTableRowElement,
|
|
56
|
+
React.HTMLAttributes<HTMLTableRowElement>
|
|
57
|
+
>(({ className, ...props }, ref) => (
|
|
58
|
+
<tr
|
|
59
|
+
ref={ref}
|
|
60
|
+
className={cn(
|
|
61
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
))
|
|
67
|
+
TableRow.displayName = "TableRow"
|
|
68
|
+
|
|
69
|
+
const TableHead = React.forwardRef<
|
|
70
|
+
HTMLTableCellElement,
|
|
71
|
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
|
72
|
+
>(({ className, ...props }, ref) => (
|
|
73
|
+
<th
|
|
74
|
+
ref={ref}
|
|
75
|
+
className={cn(
|
|
76
|
+
"h-10 px-3 py-3 text-left align-middle text-xs font-semibold text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
))
|
|
82
|
+
TableHead.displayName = "TableHead"
|
|
83
|
+
|
|
84
|
+
const TableCell = React.forwardRef<
|
|
85
|
+
HTMLTableCellElement,
|
|
86
|
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
|
87
|
+
>(({ className, ...props }, ref) => (
|
|
88
|
+
<td
|
|
89
|
+
ref={ref}
|
|
90
|
+
className={cn("px-3 py-3 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
))
|
|
94
|
+
TableCell.displayName = "TableCell"
|
|
95
|
+
|
|
96
|
+
const TableCaption = React.forwardRef<
|
|
97
|
+
HTMLTableCaptionElement,
|
|
98
|
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
|
99
|
+
>(({ className, ...props }, ref) => (
|
|
100
|
+
<caption
|
|
101
|
+
ref={ref}
|
|
102
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
))
|
|
106
|
+
TableCaption.displayName = "TableCaption"
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
Table,
|
|
110
|
+
TableHeader,
|
|
111
|
+
TableBody,
|
|
112
|
+
TableFooter,
|
|
113
|
+
TableHead,
|
|
114
|
+
TableRow,
|
|
115
|
+
TableCell,
|
|
116
|
+
TableCaption,
|
|
117
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const Tabs = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof TabsPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
|
|
10
|
+
>(({ className, ...props }, ref) => (
|
|
11
|
+
<TabsPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
className={cn("flex flex-col w-[400px] max-w-full rounded-lg border", className)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
))
|
|
17
|
+
Tabs.displayName = TabsPrimitive.Root.displayName
|
|
18
|
+
|
|
19
|
+
const TabsList = React.forwardRef<
|
|
20
|
+
React.ElementRef<typeof TabsPrimitive.List>,
|
|
21
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<TabsPrimitive.List
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn(
|
|
26
|
+
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
))
|
|
32
|
+
TabsList.displayName = TabsPrimitive.List.displayName
|
|
33
|
+
|
|
34
|
+
const TabsTrigger = React.forwardRef<
|
|
35
|
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
36
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
37
|
+
>(({ className, ...props }, ref) => (
|
|
38
|
+
<TabsPrimitive.Trigger
|
|
39
|
+
ref={ref}
|
|
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",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
))
|
|
47
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
|
48
|
+
|
|
49
|
+
const TabsContent = React.forwardRef<
|
|
50
|
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
51
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
52
|
+
>(({ className, ...props }, ref) => (
|
|
53
|
+
<TabsPrimitive.Content
|
|
54
|
+
ref={ref}
|
|
55
|
+
className={cn(
|
|
56
|
+
"mt-2 p-4 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
))
|
|
62
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName
|
|
63
|
+
|
|
64
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import { CheckCircle, XCircle, Info, X } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils'; // Relative path to lib
|
|
5
|
+
import type { ToastVariantKey } from "./generated/variant-keys";
|
|
6
|
+
import { toastDefaultVariantKey } from "./generated/default-variant-keys";
|
|
7
|
+
|
|
8
|
+
export type ToastType = ToastVariantKey;
|
|
9
|
+
|
|
10
|
+
export interface ToastProps {
|
|
11
|
+
message: string;
|
|
12
|
+
type?: ToastType;
|
|
13
|
+
isVisible: boolean;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
duration?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Toast({ message, type = toastDefaultVariantKey, isVisible, onClose, duration = 3000 }: ToastProps) {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (isVisible) {
|
|
21
|
+
const timer = setTimeout(() => {
|
|
22
|
+
onClose();
|
|
23
|
+
}, duration);
|
|
24
|
+
return () => clearTimeout(timer);
|
|
25
|
+
}
|
|
26
|
+
}, [isVisible, duration, onClose]);
|
|
27
|
+
|
|
28
|
+
if (!isVisible) return null;
|
|
29
|
+
|
|
30
|
+
const icons: Record<ToastVariantKey, React.ReactNode> = {
|
|
31
|
+
success: <CheckCircle className="text-success" size={20} />,
|
|
32
|
+
error: <XCircle className="text-destructive" size={20} />,
|
|
33
|
+
info: <Info className="text-info" size={20} />
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const bgColors: Record<ToastVariantKey, string> = {
|
|
37
|
+
success: 'bg-card border-success/20 text-foreground',
|
|
38
|
+
error: 'bg-card border-destructive/20 text-foreground',
|
|
39
|
+
info: 'bg-card border-info/20 text-foreground'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className={cn(
|
|
44
|
+
"fixed top-4 right-4 z-[9999] flex flex-col flex-row items-center w-[384px] max-w-sm w-full md:w-auto gap-3 px-4 py-3 rounded-xl border shadow-2xl animate-in slide-in-from-top-5 fade-in duration-300 pointer-events-auto",
|
|
45
|
+
bgColors[type]
|
|
46
|
+
)}>
|
|
47
|
+
{icons[type]}
|
|
48
|
+
<p className="text-sm font-medium">{message}</p>
|
|
49
|
+
<button
|
|
50
|
+
onClick={onClose}
|
|
51
|
+
className="ml-2 p-1 text-muted-foreground hover:text-foreground rounded-lg transition-colors"
|
|
52
|
+
>
|
|
53
|
+
<X size={14} />
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils"
|
|
7
|
+
|
|
8
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
9
|
+
|
|
10
|
+
const Tooltip = TooltipPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const TooltipContent = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
17
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
18
|
+
<TooltipPrimitive.Content
|
|
19
|
+
ref={ref}
|
|
20
|
+
sideOffset={sideOffset}
|
|
21
|
+
className={cn(
|
|
22
|
+
"z-50 flex flex-col w-[168px] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
29
|
+
|
|
30
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by `npm run design:sync:components`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
import type { AccordionVariantKey, AiChatMessageVariantKey, FilterButtonVariantKey, ListVariantKey, SidebarItemVariantKey, SortButtonVariantKey, ToastVariantKey } from "./variant-keys";
|
|
5
|
+
|
|
6
|
+
export const accordionDefaultVariantKey: AccordionVariantKey = "collapsed";
|
|
7
|
+
export const aiChatMessageDefaultVariantKey: AiChatMessageVariantKey = "assistant";
|
|
8
|
+
export const filterButtonDefaultVariantKey: FilterButtonVariantKey = "default";
|
|
9
|
+
export const listDefaultVariantKey: ListVariantKey = "dot";
|
|
10
|
+
export const sidebarItemDefaultVariantKey: SidebarItemVariantKey = "default";
|
|
11
|
+
export const sortButtonDefaultVariantKey: SortButtonVariantKey = "none";
|
|
12
|
+
export const toastDefaultVariantKey: ToastVariantKey = "success";
|
|
13
|
+
|
|
14
|
+
export const moleculeDefaultVariantKeys = {
|
|
15
|
+
accordion: accordionDefaultVariantKey,
|
|
16
|
+
aiChatMessage: aiChatMessageDefaultVariantKey,
|
|
17
|
+
filterButton: filterButtonDefaultVariantKey,
|
|
18
|
+
list: listDefaultVariantKey,
|
|
19
|
+
sidebarItem: sidebarItemDefaultVariantKey,
|
|
20
|
+
sortButton: sortButtonDefaultVariantKey,
|
|
21
|
+
toast: toastDefaultVariantKey,
|
|
22
|
+
} as const;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by `npm run design:sync:components`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
export const accordionVariantKeys = ["collapsed", "expanded"] as const;
|
|
5
|
+
export type AccordionVariantKey = (typeof accordionVariantKeys)[number];
|
|
6
|
+
|
|
7
|
+
export const aiChatMessageVariantKeys = ["assistant", "user"] as const;
|
|
8
|
+
export type AiChatMessageVariantKey = (typeof aiChatMessageVariantKeys)[number];
|
|
9
|
+
|
|
10
|
+
export const filterButtonVariantKeys = ["default", "popover", "selected"] as const;
|
|
11
|
+
export type FilterButtonVariantKey = (typeof filterButtonVariantKeys)[number];
|
|
12
|
+
|
|
13
|
+
export const listVariantKeys = ["check", "circle", "dot"] as const;
|
|
14
|
+
export type ListVariantKey = (typeof listVariantKeys)[number];
|
|
15
|
+
|
|
16
|
+
export const sidebarItemVariantKeys = ["active", "default"] as const;
|
|
17
|
+
export type SidebarItemVariantKey = (typeof sidebarItemVariantKeys)[number];
|
|
18
|
+
|
|
19
|
+
export const sortButtonVariantKeys = ["asc", "desc", "none"] as const;
|
|
20
|
+
export type SortButtonVariantKey = (typeof sortButtonVariantKeys)[number];
|
|
21
|
+
|
|
22
|
+
export const toastVariantKeys = ["error", "info", "success"] as const;
|
|
23
|
+
export type ToastVariantKey = (typeof toastVariantKeys)[number];
|
|
24
|
+
|
|
25
|
+
export const moleculeVariantKeys = {
|
|
26
|
+
accordion: accordionVariantKeys,
|
|
27
|
+
aiChatMessage: aiChatMessageVariantKeys,
|
|
28
|
+
filterButton: filterButtonVariantKeys,
|
|
29
|
+
list: listVariantKeys,
|
|
30
|
+
sidebarItem: sidebarItemVariantKeys,
|
|
31
|
+
sortButton: sortButtonVariantKeys,
|
|
32
|
+
toast: toastVariantKeys,
|
|
33
|
+
} as const;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
export interface AppRailProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const AppRail = React.forwardRef<HTMLDivElement, AppRailProps>(
|
|
11
|
+
({ className, children, ...props }, ref) => {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
"w-16 h-[320px] h-full bg-foreground flex flex-col items-center py-4 px-0 gap-4 text-muted z-40 flex-shrink-0",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
AppRail.displayName = "AppRail"
|
|
27
|
+
|
|
28
|
+
export { AppRail }
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
CommandDialog,
|
|
6
|
+
CommandEmpty,
|
|
7
|
+
CommandGroup,
|
|
8
|
+
CommandInput,
|
|
9
|
+
CommandItem,
|
|
10
|
+
CommandList,
|
|
11
|
+
CommandSeparator,
|
|
12
|
+
CommandShortcut,
|
|
13
|
+
} from "../molecules/Command"
|
|
14
|
+
|
|
15
|
+
import { DialogProps } from "@radix-ui/react-dialog"
|
|
16
|
+
|
|
17
|
+
interface ToggleAction {
|
|
18
|
+
id: string
|
|
19
|
+
label: string
|
|
20
|
+
icon?: React.ReactNode
|
|
21
|
+
shortcut?: string
|
|
22
|
+
action: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CommandPaletteProps extends DialogProps {
|
|
26
|
+
groups: {
|
|
27
|
+
heading: string
|
|
28
|
+
items: ToggleAction[]
|
|
29
|
+
}[]
|
|
30
|
+
placeholder?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function CommandPalette({ groups, placeholder = "Type a command or search...", ...props }: CommandPaletteProps) {
|
|
34
|
+
return (
|
|
35
|
+
<CommandDialog {...props}>
|
|
36
|
+
<div className="flex flex-col w-[480px] max-w-full rounded-lg border">
|
|
37
|
+
<CommandInput placeholder={placeholder} />
|
|
38
|
+
<CommandList>
|
|
39
|
+
<CommandEmpty>No results found.</CommandEmpty>
|
|
40
|
+
{groups.map((group) => (
|
|
41
|
+
<React.Fragment key={group.heading}>
|
|
42
|
+
<CommandGroup heading={group.heading}>
|
|
43
|
+
{group.items.map((item) => (
|
|
44
|
+
<CommandItem key={item.id} onSelect={item.action}>
|
|
45
|
+
{item.icon && <span className="mr-2 h-4 w-4">{item.icon}</span>}
|
|
46
|
+
<span>{item.label}</span>
|
|
47
|
+
{item.shortcut && <CommandShortcut>{item.shortcut}</CommandShortcut>}
|
|
48
|
+
</CommandItem>
|
|
49
|
+
))}
|
|
50
|
+
</CommandGroup>
|
|
51
|
+
<CommandSeparator />
|
|
52
|
+
</React.Fragment>
|
|
53
|
+
))}
|
|
54
|
+
</CommandList>
|
|
55
|
+
</div>
|
|
56
|
+
</CommandDialog>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Upload, X, FileText, CheckCircle, AlertCircle } from "lucide-react"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
import { Button } from "../atoms/Button"
|
|
7
|
+
import { Progress } from "../atoms/Progress"
|
|
8
|
+
|
|
9
|
+
export interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
onValueChange?: (files: File[]) => void
|
|
11
|
+
maxFiles?: number
|
|
12
|
+
maxSize?: number // in bytes
|
|
13
|
+
accept?: Record<string, string[]>
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FileState {
|
|
18
|
+
file: File
|
|
19
|
+
progress: number
|
|
20
|
+
status: "pending" | "uploading" | "complete" | "error"
|
|
21
|
+
error?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const FileUploader = React.forwardRef<HTMLDivElement, FileUploaderProps>(
|
|
25
|
+
({ className, onValueChange, maxFiles = 1, maxSize = 1024 * 1024 * 5, accept, disabled, ...props }, ref) => {
|
|
26
|
+
const [dragActive, setDragActive] = React.useState(false)
|
|
27
|
+
const [files, setFiles] = React.useState<FileState[]>([])
|
|
28
|
+
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
29
|
+
|
|
30
|
+
const handleDrag = React.useCallback((e: React.DragEvent) => {
|
|
31
|
+
e.preventDefault()
|
|
32
|
+
e.stopPropagation()
|
|
33
|
+
if (e.type === "dragenter" || e.type === "dragover") {
|
|
34
|
+
setDragActive(true)
|
|
35
|
+
} else if (e.type === "dragleave") {
|
|
36
|
+
setDragActive(false)
|
|
37
|
+
}
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
const validateFile = (file: File): string | undefined => {
|
|
41
|
+
if (file.size > maxSize) return "File too large"
|
|
42
|
+
// Simple check, in real app use proper mime matching
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handleDrop = React.useCallback(
|
|
47
|
+
(e: React.DragEvent) => {
|
|
48
|
+
e.preventDefault()
|
|
49
|
+
e.stopPropagation()
|
|
50
|
+
setDragActive(false)
|
|
51
|
+
|
|
52
|
+
if (disabled) return
|
|
53
|
+
|
|
54
|
+
const droppedFiles = Array.from(e.dataTransfer.files)
|
|
55
|
+
if (droppedFiles && droppedFiles.length > 0) {
|
|
56
|
+
const newFiles = droppedFiles.map(file => ({
|
|
57
|
+
file,
|
|
58
|
+
progress: 0,
|
|
59
|
+
status: "pending" as const,
|
|
60
|
+
error: validateFile(file)
|
|
61
|
+
}))
|
|
62
|
+
setFiles(prev => [...prev, ...newFiles].slice(0, maxFiles))
|
|
63
|
+
onValueChange?.(droppedFiles)
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[disabled, maxFiles, maxSize, onValueChange]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
70
|
+
e.preventDefault()
|
|
71
|
+
if (disabled || !e.target.files) return
|
|
72
|
+
|
|
73
|
+
const selectedFiles = Array.from(e.target.files)
|
|
74
|
+
const newFiles = selectedFiles.map(file => ({
|
|
75
|
+
file,
|
|
76
|
+
progress: 0,
|
|
77
|
+
status: "pending" as const,
|
|
78
|
+
error: validateFile(file)
|
|
79
|
+
}))
|
|
80
|
+
setFiles(prev => [...prev, ...newFiles].slice(0, maxFiles))
|
|
81
|
+
onValueChange?.(selectedFiles)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const removeFile = (index: number) => {
|
|
85
|
+
setFiles(prev => prev.filter((_, i) => i !== index))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Simulate upload for visual effect if needed, or consumer handles it.
|
|
89
|
+
// For this UI component, we just accept the files.
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className={cn("grid gap-4 w-[640px] w-full", className)} ref={ref} {...props}>
|
|
93
|
+
<div
|
|
94
|
+
className={cn(
|
|
95
|
+
"relative flex flex-col items-center justify-center w-full h-32 rounded-lg border-2 border-dashed transition-colors",
|
|
96
|
+
dragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:bg-muted/50",
|
|
97
|
+
disabled && "opacity-60 cursor-not-allowed"
|
|
98
|
+
)}
|
|
99
|
+
onDragEnter={handleDrag}
|
|
100
|
+
onDragLeave={handleDrag}
|
|
101
|
+
onDragOver={handleDrag}
|
|
102
|
+
onDrop={handleDrop}
|
|
103
|
+
>
|
|
104
|
+
<input
|
|
105
|
+
ref={inputRef}
|
|
106
|
+
type="file"
|
|
107
|
+
className="hidden"
|
|
108
|
+
multiple={maxFiles > 1}
|
|
109
|
+
onChange={handleChange}
|
|
110
|
+
disabled={disabled}
|
|
111
|
+
/>
|
|
112
|
+
<div className="flex flex-col items-center justify-center pt-5 pb-6 text-center cursor-pointer" onClick={() => inputRef.current?.click()}>
|
|
113
|
+
<Upload className="w-8 h-8 mb-3 text-muted-foreground" />
|
|
114
|
+
<p className="mb-1 text-sm text-foreground">
|
|
115
|
+
<span className="font-semibold">Click to upload</span> or drag and drop
|
|
116
|
+
</p>
|
|
117
|
+
<p className="text-xs text-muted-foreground">
|
|
118
|
+
Max size {Math.round(maxSize / 1024 / 1024)}MB
|
|
119
|
+
</p>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{files.length > 0 && (
|
|
124
|
+
<div className="grid gap-2">
|
|
125
|
+
{files.map((fileState, index) => (
|
|
126
|
+
<div key={index} className="flex items-center gap-2 p-2 rounded-md border bg-background">
|
|
127
|
+
<FileText className="h-4 w-4 text-primary" />
|
|
128
|
+
<div className="flex-1 min-w-0 grid gap-1">
|
|
129
|
+
<div className="flex justify-between text-xs">
|
|
130
|
+
<span className="truncate font-medium">{fileState.file.name}</span>
|
|
131
|
+
<span className="text-muted-foreground">{(fileState.file.size / 1024).toFixed(0)}KB</span>
|
|
132
|
+
</div>
|
|
133
|
+
{/* Progress Simulation would go here */}
|
|
134
|
+
</div>
|
|
135
|
+
<Button
|
|
136
|
+
variant="ghost"
|
|
137
|
+
size="icon"
|
|
138
|
+
className="h-6 w-6"
|
|
139
|
+
onClick={() => removeFile(index)}
|
|
140
|
+
>
|
|
141
|
+
<X className="h-4 w-4" />
|
|
142
|
+
</Button>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
FileUploader.displayName = "FileUploader"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
import { motion, HTMLMotionProps } from 'framer-motion';
|
|
4
|
+
|
|
5
|
+
interface FloatingPanelProps extends HTMLMotionProps<"div"> {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
variant?: 'glass' | 'solid' | 'ghost';
|
|
9
|
+
title?: string;
|
|
10
|
+
contentClassName?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const FloatingPanel: React.FC<FloatingPanelProps> = ({
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
variant = 'glass',
|
|
17
|
+
title,
|
|
18
|
+
contentClassName,
|
|
19
|
+
...props
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<motion.div
|
|
23
|
+
initial={{ opacity: 0, y: 10, scale: 0.98 }}
|
|
24
|
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
25
|
+
exit={{ opacity: 0, y: 10, scale: 0.98 }}
|
|
26
|
+
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
27
|
+
className={cn(
|
|
28
|
+
"w-[360px] rounded-2xl border flex flex-col overflow-hidden transition-all duration-300 hover:shadow-lg", // Reduced hover shadow intensity
|
|
29
|
+
variant === 'glass' && "bg-background/80 backdrop-blur-md border-border/70",
|
|
30
|
+
variant === 'solid' && "bg-background border-border",
|
|
31
|
+
variant === 'ghost' && "bg-transparent border-transparent shadow-none hover:shadow-none",
|
|
32
|
+
className
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{title && (
|
|
37
|
+
<div className="px-4 py-3 border-b border-border/60 flex items-center justify-between shrink-0">
|
|
38
|
+
<h3 className="font-semibold text-sm text-foreground">{title}</h3>
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
<div className={cn("flex-1 overflow-auto", contentClassName)}>
|
|
42
|
+
{children}
|
|
43
|
+
</div>
|
|
44
|
+
</motion.div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
export interface InspectorPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
title?: string
|
|
8
|
+
footer?: React.ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const InspectorPanel = React.forwardRef<HTMLDivElement, InspectorPanelProps>(
|
|
12
|
+
({ className, title, children, footer, ...props }, ref) => {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex flex-col w-[320px] h-[420px] w-full h-full bg-background border-l border-border",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{title && (
|
|
23
|
+
<div className="flex items-center h-12 px-4 border-b border-border bg-muted/30">
|
|
24
|
+
<h3 className="text-sm font-semibold text-foreground">{title}</h3>
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
27
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden p-4 space-y-6">
|
|
28
|
+
{children}
|
|
29
|
+
</div>
|
|
30
|
+
{footer && (
|
|
31
|
+
<div className="p-4 border-t border-border bg-muted/30">
|
|
32
|
+
{footer}
|
|
33
|
+
</div>
|
|
34
|
+
)}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
InspectorPanel.displayName = "InspectorPanel"
|
|
40
|
+
|
|
41
|
+
export const InspectorSection = React.forwardRef<
|
|
42
|
+
HTMLDivElement,
|
|
43
|
+
React.HTMLAttributes<HTMLDivElement> & { title: string }
|
|
44
|
+
>(({ className, title, children, ...props }, ref) => (
|
|
45
|
+
<div ref={ref} className={cn("space-y-3", className)} {...props}>
|
|
46
|
+
<h4 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
47
|
+
{title}
|
|
48
|
+
</h4>
|
|
49
|
+
<div className="space-y-2">
|
|
50
|
+
{children}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
))
|
|
54
|
+
InspectorSection.displayName = "InspectorSection"
|
|
55
|
+
|
|
56
|
+
export const InspectorField = React.forwardRef<
|
|
57
|
+
HTMLDivElement,
|
|
58
|
+
React.HTMLAttributes<HTMLDivElement> & { label: string }
|
|
59
|
+
>(({ className, label, children, ...props }, ref) => (
|
|
60
|
+
<div ref={ref} className={cn("grid gap-1.5", className)} {...props}>
|
|
61
|
+
<label className="text-xs font-medium text-foreground">{label}</label>
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
))
|
|
65
|
+
InspectorField.displayName = "InspectorField"
|