@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.
Files changed (88) hide show
  1. package/README.md +129 -0
  2. package/design/atoms-metadata.json +82 -0
  3. package/design/molecules-metadata.json +130 -0
  4. package/design/organisms-metadata.json +38 -0
  5. package/design/templates-metadata.json +38 -0
  6. package/package.json +158 -0
  7. package/src/components/atoms/Alert.tsx +63 -0
  8. package/src/components/atoms/Avatar.tsx +57 -0
  9. package/src/components/atoms/Badge.tsx +30 -0
  10. package/src/components/atoms/Button.tsx +29 -0
  11. package/src/components/atoms/ButtonVariants.ts +37 -0
  12. package/src/components/atoms/Checkbox.tsx +52 -0
  13. package/src/components/atoms/Img.tsx +102 -0
  14. package/src/components/atoms/Input.tsx +37 -0
  15. package/src/components/atoms/Kbd.tsx +22 -0
  16. package/src/components/atoms/Label.tsx +22 -0
  17. package/src/components/atoms/Progress.tsx +38 -0
  18. package/src/components/atoms/RadioGroup.tsx +86 -0
  19. package/src/components/atoms/Select.tsx +28 -0
  20. package/src/components/atoms/Separator.tsx +33 -0
  21. package/src/components/atoms/Skeleton.tsx +36 -0
  22. package/src/components/atoms/Slider.tsx +26 -0
  23. package/src/components/atoms/Spinner.tsx +34 -0
  24. package/src/components/atoms/Switch.tsx +47 -0
  25. package/src/components/atoms/Textarea.tsx +34 -0
  26. package/src/components/atoms/ToggleGroup.tsx +60 -0
  27. package/src/components/atoms/ToolPill.tsx +77 -0
  28. package/src/components/atoms/generated/default-variant-keys.ts +36 -0
  29. package/src/components/atoms/generated/variant-keys.ts +61 -0
  30. package/src/components/generated/component-manifest.ts +741 -0
  31. package/src/components/generated/component-style-hints.ts +1262 -0
  32. package/src/components/molecules/AIChatInput.tsx +140 -0
  33. package/src/components/molecules/AIChatMessage.tsx +109 -0
  34. package/src/components/molecules/Accordion.tsx +99 -0
  35. package/src/components/molecules/Breadcrumb.tsx +115 -0
  36. package/src/components/molecules/Calendar.tsx +60 -0
  37. package/src/components/molecules/Card.tsx +78 -0
  38. package/src/components/molecules/Carousel.tsx +261 -0
  39. package/src/components/molecules/Command.tsx +152 -0
  40. package/src/components/molecules/ContextMenu.tsx +200 -0
  41. package/src/components/molecules/Dialog.tsx +122 -0
  42. package/src/components/molecules/DropdownMenu.tsx +200 -0
  43. package/src/components/molecules/FilterButton.tsx +133 -0
  44. package/src/components/molecules/Form.tsx +90 -0
  45. package/src/components/molecules/HoverCard.tsx +29 -0
  46. package/src/components/molecules/List.tsx +120 -0
  47. package/src/components/molecules/Menubar.tsx +231 -0
  48. package/src/components/molecules/Modal.tsx +66 -0
  49. package/src/components/molecules/NotificationCenter.tsx +118 -0
  50. package/src/components/molecules/Pagination.tsx +118 -0
  51. package/src/components/molecules/Popover.tsx +31 -0
  52. package/src/components/molecules/ProgressWidget.tsx +40 -0
  53. package/src/components/molecules/Resizable.tsx +47 -0
  54. package/src/components/molecules/ScrollArea.tsx +48 -0
  55. package/src/components/molecules/Sheet.tsx +140 -0
  56. package/src/components/molecules/SidebarItem.tsx +134 -0
  57. package/src/components/molecules/SortButton.tsx +56 -0
  58. package/src/components/molecules/StatusBar.tsx +41 -0
  59. package/src/components/molecules/Stepper.tsx +108 -0
  60. package/src/components/molecules/Table.tsx +117 -0
  61. package/src/components/molecules/Tabs.tsx +64 -0
  62. package/src/components/molecules/Toast.tsx +57 -0
  63. package/src/components/molecules/Tooltip.tsx +30 -0
  64. package/src/components/molecules/generated/default-variant-keys.ts +22 -0
  65. package/src/components/molecules/generated/variant-keys.ts +33 -0
  66. package/src/components/organisms/AppRail.tsx +28 -0
  67. package/src/components/organisms/CommandPalette.tsx +58 -0
  68. package/src/components/organisms/FileUploader.tsx +151 -0
  69. package/src/components/organisms/FloatingPanel.tsx +46 -0
  70. package/src/components/organisms/InspectorPanel.tsx +65 -0
  71. package/src/components/organisms/RightRail.tsx +29 -0
  72. package/src/components/organisms/ShareModal.tsx +182 -0
  73. package/src/components/organisms/SpatialCanvas.tsx +36 -0
  74. package/src/components/organisms/ToastProvider.tsx +49 -0
  75. package/src/components/templates/AuthTemplate.tsx +58 -0
  76. package/src/components/templates/BannalyzeTemplate.tsx +55 -0
  77. package/src/components/templates/ChatTemplate.tsx +55 -0
  78. package/src/components/templates/DashboardTemplate.tsx +34 -0
  79. package/src/components/templates/EditorTemplate.tsx +46 -0
  80. package/src/components/templates/KanbanTemplate.tsx +38 -0
  81. package/src/components/templates/LandingTemplate.tsx +53 -0
  82. package/src/components/templates/MediaLibraryTemplate.tsx +55 -0
  83. package/src/components/templates/SettingsTemplate.tsx +48 -0
  84. package/src/globals.css +108 -0
  85. package/src/index.ts +89 -0
  86. package/src/lib/utils.ts +6 -0
  87. package/tailwind-preset.js +11 -0
  88. 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"