@hanzo/ui 4.5.4 → 4.6.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-MCP.md +3 -3
- package/README.md +229 -0
- package/assets/ai-icons.tsx +207 -0
- package/assets/crypto.tsx +33 -0
- package/assets/file-type-icon.tsx +66 -0
- package/assets/file.tsx +45 -0
- package/assets/general.tsx +2318 -0
- package/assets/hanzo-logo.svg +9 -0
- package/assets/hanzo-logo.tsx +15 -0
- package/assets/index.ts +8 -0
- package/assets/index.tsx +4 -0
- package/assets/llm-provider.tsx +1094 -0
- package/bin/create-registry.js +1 -1
- package/bin/test-mcp.sh +1 -1
- package/bin/update-registry.js +2 -2
- package/blocks/components/content.tsx +1 -1
- package/blocks/components/grid-block/index.tsx +1 -1
- package/blocks/components/screenful-block/content.tsx +1 -1
- package/blocks/components/screenful-block/poster-background.tsx +1 -1
- package/components/index.ts +56 -0
- package/dist/button.d.ts +1 -0
- package/dist/button.js +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/use-click-away.d.ts +2 -0
- package/dist/hooks/use-click-away.js +23 -0
- package/dist/hooks/use-combined-refs.d.ts +3 -0
- package/dist/hooks/use-combined-refs.js +18 -0
- package/dist/hooks/use-copy-clipboard.d.ts +9 -0
- package/dist/hooks/use-copy-clipboard.js +21 -0
- package/dist/hooks/use-debounce.d.ts +1 -0
- package/dist/hooks/use-debounce.js +13 -0
- package/dist/hooks/use-fill-ids.d.ts +8 -0
- package/dist/hooks/use-fill-ids.js +20 -0
- package/dist/hooks/use-map.d.ts +1 -0
- package/dist/hooks/use-map.js +20 -0
- package/dist/hooks/use-measure.d.ts +8 -0
- package/dist/hooks/use-measure.js +25 -0
- package/dist/hooks/use-reverse-video-playback.d.ts +1 -0
- package/dist/hooks/use-reverse-video-playback.js +41 -0
- package/dist/hooks/use-scroll-restoration.d.ts +8 -0
- package/dist/hooks/use-scroll-restoration.js +36 -0
- package/dist/mcp/enhanced-server.js +2 -2
- package/dist/registry/api.d.ts +1 -1
- package/dist/registry/api.js +3 -3
- package/dist/registry/index.d.ts +48 -48
- package/dist/registry/index.js +3 -3
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +1 -0
- package/helpers/file.ts +33 -0
- package/helpers/memoization.ts +40 -0
- package/package.json +27 -6
- package/primitives/accordion.tsx +53 -45
- package/primitives/alert-dialog.tsx +185 -0
- package/primitives/alert.tsx +74 -0
- package/primitives/apply-typography.tsx +1 -1
- package/primitives/avatar.tsx +37 -29
- package/primitives/background-beams.tsx +142 -0
- package/primitives/badge.tsx +27 -19
- package/primitives/breadcrumb.tsx +77 -62
- package/primitives/button.tsx +69 -72
- package/primitives/card.tsx +73 -59
- package/primitives/chat/chat-input-area.tsx +87 -0
- package/primitives/chat/chat-input.tsx +71 -0
- package/primitives/chat/files-preview.tsx +330 -0
- package/primitives/chat/index.ts +6 -0
- package/primitives/chat/json-form.tsx +8 -0
- package/primitives/chat/message-list.tsx +307 -0
- package/primitives/chat/message.tsx +569 -0
- package/primitives/chat/sqlite-preview.tsx +215 -0
- package/primitives/checkbox.tsx +18 -19
- package/primitives/collapsible.tsx +9 -0
- package/primitives/command.tsx +75 -83
- package/primitives/context-menu.tsx +115 -109
- package/primitives/copy-to-clipboard-icon.tsx +60 -0
- package/primitives/dialog-video-controller.tsx +1 -1
- package/primitives/dialog.tsx +111 -145
- package/primitives/dot-pattern.tsx +57 -0
- package/primitives/dots-loader.tsx +13 -0
- package/primitives/drawer.tsx +59 -87
- package/primitives/dropdown-menu.tsx +199 -0
- package/primitives/error-message.tsx +19 -0
- package/primitives/file-uploader.tsx +200 -0
- package/primitives/form.tsx +92 -87
- package/primitives/hover-card.tsx +28 -0
- package/primitives/icons/github.tsx +1 -1
- package/primitives/icons/youtube-logo.tsx +1 -1
- package/primitives/index-common.ts +121 -42
- package/primitives/index-next.ts +3 -1
- package/primitives/input.tsx +115 -20
- package/primitives/label.tsx +15 -23
- package/primitives/loading-spinner.tsx +1 -1
- package/primitives/markdown-preview.tsx +609 -0
- package/primitives/mermaid.tsx +196 -0
- package/primitives/next/link-element.tsx +1 -1
- package/primitives/next/mdx-link.tsx +1 -1
- package/primitives/pagination.tsx +117 -0
- package/primitives/popover.tsx +20 -25
- package/primitives/pretty-json-print.tsx +28 -0
- package/primitives/progress.tsx +14 -15
- package/primitives/prompt-textarea.tsx +72 -0
- package/primitives/qr-code.tsx +112 -0
- package/primitives/radio-group.tsx +25 -39
- package/primitives/resizable.tsx +47 -0
- package/primitives/scroll-area.tsx +35 -25
- package/primitives/search-input.tsx +66 -0
- package/primitives/select.tsx +62 -109
- package/primitives/separator.tsx +22 -26
- package/primitives/sheet.tsx +78 -117
- package/primitives/skeleton.tsx +13 -16
- package/primitives/slider.tsx +50 -60
- package/primitives/stepper.tsx +272 -0
- package/primitives/switch.tsx +14 -23
- package/primitives/table.tsx +65 -77
- package/primitives/tabs.tsx +29 -39
- package/primitives/text-link.tsx +25 -0
- package/primitives/textarea.tsx +61 -0
- package/primitives/textfield.tsx +75 -0
- package/primitives/toast.tsx +30 -0
- package/primitives/toggle-group.tsx +33 -33
- package/primitives/toggle.tsx +22 -51
- package/primitives/tooltip.tsx +37 -38
- package/registry.json +1 -1
- package/src/button.ts +1 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-click-away.ts +31 -0
- package/src/hooks/use-combined-refs.ts +22 -0
- package/src/hooks/use-copy-clipboard.ts +30 -0
- package/src/hooks/use-debounce.ts +17 -0
- package/src/hooks/use-fill-ids.ts +25 -0
- package/src/hooks/use-map.ts +26 -0
- package/src/hooks/use-measure.ts +42 -0
- package/src/hooks/use-reverse-video-playback.ts +43 -0
- package/src/hooks/use-scroll-restoration.ts +50 -0
- package/src/mcp/README.md +1 -1
- package/src/mcp/enhanced-server.ts +2 -2
- package/src/registry/api.ts +3 -3
- package/src/registry/index.ts +3 -3
- package/src/utils.ts +1 -0
- package/style/theme-provider.tsx +1 -1
- package/test-imports.mjs +19 -0
- package/types/animation-def.ts +1 -1
- package/types/button-def.ts +1 -1
- package/types/index.ts +1 -1
- package/util/blob.ts +28 -0
- package/util/copy-to-clipboard.ts +17 -0
- package/util/create-shadow-root.ts +22 -0
- package/util/date.ts +83 -0
- package/util/debounce.ts +11 -0
- package/util/file.ts +15 -0
- package/util/format-and-abbreviate-as-currency.ts +1 -1
- package/util/format-text.ts +33 -0
- package/util/index.ts +9 -78
- package/util/timing.ts +3 -0
- package/util/toasts.tsx +17 -0
- package/utils.ts +9 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useId } from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../src/utils';
|
|
4
|
+
|
|
5
|
+
interface DotPatternProps {
|
|
6
|
+
width?: any;
|
|
7
|
+
height?: any;
|
|
8
|
+
x?: any;
|
|
9
|
+
y?: any;
|
|
10
|
+
cx?: any;
|
|
11
|
+
cy?: any;
|
|
12
|
+
cr?: any;
|
|
13
|
+
className?: string;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function DotPattern({
|
|
18
|
+
width = 16,
|
|
19
|
+
height = 16,
|
|
20
|
+
x = 0,
|
|
21
|
+
y = 0,
|
|
22
|
+
cx = 1,
|
|
23
|
+
cy = 1,
|
|
24
|
+
cr = 1,
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: DotPatternProps) {
|
|
28
|
+
const id = useId();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<svg
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
className={cn(
|
|
34
|
+
'pointer-events-none absolute inset-0 h-full w-full fill-neutral-400/80',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<defs>
|
|
40
|
+
<pattern
|
|
41
|
+
height={height}
|
|
42
|
+
id={id}
|
|
43
|
+
patternContentUnits="userSpaceOnUse"
|
|
44
|
+
patternUnits="userSpaceOnUse"
|
|
45
|
+
width={width}
|
|
46
|
+
x={x}
|
|
47
|
+
y={y}
|
|
48
|
+
>
|
|
49
|
+
<circle cx={cx} cy={cy} id="pattern-circle" r={cr} />
|
|
50
|
+
</pattern>
|
|
51
|
+
</defs>
|
|
52
|
+
<rect fill={`url(#${id})`} height="100%" strokeWidth={0} width="100%" />
|
|
53
|
+
</svg>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { DotPattern };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const DotsLoader = ({ className }: { className?: string }) => {
|
|
2
|
+
return (
|
|
3
|
+
<div className={className}>
|
|
4
|
+
<div className="flex h-4 space-x-1.5">
|
|
5
|
+
<div className="h-2 w-2 animate-[loaderDots_0.6s_0s_infinite_alternate] rounded-full bg-slate-100" />
|
|
6
|
+
<div className="h-2 w-2 animate-[loaderDots_0.6s_0.3s_infinite_alternate] rounded-full bg-slate-100" />
|
|
7
|
+
<div className="h-2 w-2 animate-[loaderDots_0.6s_0.6s_infinite_alternate] rounded-full bg-slate-100" />
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { DotsLoader };
|
package/primitives/drawer.tsx
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Drawer as DrawerPrimitive } from 'vaul';
|
|
2
3
|
|
|
3
|
-
import
|
|
4
|
-
import { Drawer as DrawerPrimitive } from '@hanzo/react-drawer'
|
|
5
|
-
|
|
6
|
-
import { cn } from '../util'
|
|
4
|
+
import { cn } from '../src/utils';
|
|
7
5
|
|
|
8
6
|
const Drawer = ({
|
|
9
7
|
shouldScaleBackground = false,
|
|
@@ -13,74 +11,54 @@ const Drawer = ({
|
|
|
13
11
|
shouldScaleBackground={shouldScaleBackground}
|
|
14
12
|
{...props}
|
|
15
13
|
/>
|
|
16
|
-
)
|
|
17
|
-
Drawer.displayName = 'Drawer'
|
|
14
|
+
);
|
|
15
|
+
Drawer.displayName = 'Drawer';
|
|
16
|
+
|
|
17
|
+
const DrawerTrigger = DrawerPrimitive.Trigger;
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
const DrawerPortal = DrawerPrimitive.Portal
|
|
21
|
-
const DrawerHandle = DrawerPrimitive.Handle
|
|
22
|
-
const DrawerClose = DrawerPrimitive.Close
|
|
19
|
+
const DrawerPortal = DrawerPrimitive.Portal;
|
|
23
20
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const DrawerClose = DrawerPrimitive.Close;
|
|
22
|
+
|
|
23
|
+
const DrawerOverlay = ({
|
|
24
|
+
className,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) => (
|
|
28
27
|
<DrawerPrimitive.Overlay
|
|
29
|
-
|
|
30
|
-
className={cn('fixed inset-0 bg-overlay backdrop-blur-sm', className)}
|
|
28
|
+
className={cn('fixed inset-0 z-50 bg-black/70', className)}
|
|
31
29
|
{...props}
|
|
32
30
|
/>
|
|
33
|
-
)
|
|
34
|
-
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
|
35
|
-
|
|
36
|
-
const DrawerContent = React.forwardRef<
|
|
37
|
-
React.ElementRef<typeof DrawerPrimitive.Content>,
|
|
38
|
-
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
|
|
39
|
-
overlayClx?: string
|
|
40
|
-
defaultHandle?: boolean
|
|
41
|
-
}
|
|
42
|
-
>(({
|
|
43
|
-
className,
|
|
44
|
-
children,
|
|
45
|
-
overlayClx='',
|
|
46
|
-
defaultHandle=true,
|
|
47
|
-
...props
|
|
48
|
-
}, ref) => {
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<DrawerPortal>
|
|
52
|
-
{/* If no or same z index, overlay should precede content */}
|
|
53
|
-
<DrawerOverlay className={cn('z-below-modal', overlayClx)}/>
|
|
54
|
-
<DrawerPrimitive.Content
|
|
55
|
-
ref={ref}
|
|
56
|
-
className={cn('fixed left-0 right-0 bottom-0 z-modal',
|
|
57
|
-
'mt-24 flex flex-col rounded-t-[10px] pt-6 border bg-background',
|
|
58
|
-
// 'h-[80%]'
|
|
59
|
-
className
|
|
60
|
-
)}
|
|
61
|
-
{...props}
|
|
62
|
-
>
|
|
63
|
-
{defaultHandle && (
|
|
64
|
-
<div className='absolute left-0 right-0 mx-auto top-2 h-2 w-[100px] rounded-full bg-level-3 shrink-0' />
|
|
65
|
-
)}
|
|
66
|
-
{children}
|
|
67
|
-
</DrawerPrimitive.Content>
|
|
68
|
-
</DrawerPortal>
|
|
69
|
-
)
|
|
70
|
-
})
|
|
31
|
+
);
|
|
32
|
+
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
|
71
33
|
|
|
72
|
-
DrawerContent
|
|
34
|
+
const DrawerContent = ({
|
|
35
|
+
className,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Content>) => (
|
|
39
|
+
<DrawerPortal>
|
|
40
|
+
<DrawerOverlay />
|
|
41
|
+
<DrawerPrimitive.Content
|
|
42
|
+
className={cn(
|
|
43
|
+
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-gray-500 px-6 pb-6 focus:outline-hidden',
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-gray-300" />
|
|
49
|
+
{children}
|
|
50
|
+
</DrawerPrimitive.Content>
|
|
51
|
+
</DrawerPortal>
|
|
52
|
+
);
|
|
53
|
+
DrawerContent.displayName = 'DrawerContent';
|
|
73
54
|
|
|
74
55
|
const DrawerHeader = ({
|
|
75
56
|
className,
|
|
76
57
|
...props
|
|
77
58
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
78
|
-
<div
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/>
|
|
82
|
-
)
|
|
83
|
-
DrawerHeader.displayName = 'DrawerHeader'
|
|
59
|
+
<div className={cn('pt-5 pb-0', className)} {...props} />
|
|
60
|
+
);
|
|
61
|
+
DrawerHeader.displayName = 'DrawerHeader';
|
|
84
62
|
|
|
85
63
|
const DrawerFooter = ({
|
|
86
64
|
className,
|
|
@@ -90,49 +68,43 @@ const DrawerFooter = ({
|
|
|
90
68
|
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
|
|
91
69
|
{...props}
|
|
92
70
|
/>
|
|
93
|
-
)
|
|
94
|
-
DrawerFooter.displayName = 'DrawerFooter'
|
|
71
|
+
);
|
|
72
|
+
DrawerFooter.displayName = 'DrawerFooter';
|
|
95
73
|
|
|
96
|
-
const DrawerTitle =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
74
|
+
const DrawerTitle = ({
|
|
75
|
+
className,
|
|
76
|
+
...props
|
|
77
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) => (
|
|
100
78
|
<DrawerPrimitive.Title
|
|
101
|
-
ref={ref}
|
|
102
79
|
className={cn(
|
|
103
|
-
'text-lg font-semibold
|
|
104
|
-
className
|
|
80
|
+
'text-lg leading-none font-semibold tracking-tight',
|
|
81
|
+
className,
|
|
105
82
|
)}
|
|
106
83
|
{...props}
|
|
107
84
|
/>
|
|
108
|
-
)
|
|
109
|
-
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
|
85
|
+
);
|
|
86
|
+
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
|
110
87
|
|
|
111
|
-
const DrawerDescription =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
88
|
+
const DrawerDescription = ({
|
|
89
|
+
className,
|
|
90
|
+
...props
|
|
91
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) => (
|
|
115
92
|
<DrawerPrimitive.Description
|
|
116
|
-
|
|
117
|
-
className={cn('text-sm text-muted', className)}
|
|
93
|
+
className={cn('text-text-secondary text-sm', className)}
|
|
118
94
|
{...props}
|
|
119
95
|
/>
|
|
120
|
-
)
|
|
121
|
-
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
|
122
|
-
|
|
123
|
-
type DrawerProps = React.ComponentProps<typeof DrawerPrimitive.Root>
|
|
96
|
+
);
|
|
97
|
+
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
|
124
98
|
|
|
125
99
|
export {
|
|
126
|
-
type DrawerProps,
|
|
127
100
|
Drawer,
|
|
128
101
|
DrawerPortal,
|
|
129
102
|
DrawerOverlay,
|
|
130
103
|
DrawerTrigger,
|
|
131
104
|
DrawerClose,
|
|
132
105
|
DrawerContent,
|
|
133
|
-
DrawerHandle,
|
|
134
106
|
DrawerHeader,
|
|
135
107
|
DrawerFooter,
|
|
136
108
|
DrawerTitle,
|
|
137
|
-
DrawerDescription
|
|
138
|
-
}
|
|
109
|
+
DrawerDescription,
|
|
110
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import { Check, ChevronRight } from 'lucide-react';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../src/utils';
|
|
6
|
+
|
|
7
|
+
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
12
|
+
|
|
13
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
14
|
+
|
|
15
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
16
|
+
|
|
17
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
18
|
+
|
|
19
|
+
const DropdownMenuSubTrigger = ({
|
|
20
|
+
className,
|
|
21
|
+
inset,
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
25
|
+
inset?: boolean;
|
|
26
|
+
}) => (
|
|
27
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
28
|
+
className={cn(
|
|
29
|
+
'focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center rounded-xs px-2 py-1.5 text-sm outline-hidden select-none',
|
|
30
|
+
inset && 'pl-8',
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
<ChevronRight className="ml-auto h-4 w-4" />
|
|
37
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
38
|
+
);
|
|
39
|
+
DropdownMenuSubTrigger.displayName =
|
|
40
|
+
DropdownMenuPrimitive.SubTrigger.displayName;
|
|
41
|
+
|
|
42
|
+
const DropdownMenuSubContent = ({
|
|
43
|
+
className,
|
|
44
|
+
...props
|
|
45
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) => (
|
|
46
|
+
<DropdownMenuPrimitive.SubContent
|
|
47
|
+
className={cn(
|
|
48
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 z-[2000000000] min-w-[8rem] space-y-3 overflow-hidden rounded-md bg-gray-300 text-white shadow-lg',
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
DropdownMenuSubContent.displayName =
|
|
55
|
+
DropdownMenuPrimitive.SubContent.displayName;
|
|
56
|
+
|
|
57
|
+
const DropdownMenuContent = ({
|
|
58
|
+
className,
|
|
59
|
+
sideOffset = 4,
|
|
60
|
+
...props
|
|
61
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) => (
|
|
62
|
+
<DropdownMenuPrimitive.Portal>
|
|
63
|
+
<DropdownMenuPrimitive.Content
|
|
64
|
+
className={cn(
|
|
65
|
+
'font-inter border-divider data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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 bg-bg-dark z-[2000000000] min-w-[8rem] overflow-hidden rounded-lg border px-4 py-6 text-white shadow-md',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
sideOffset={sideOffset}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
</DropdownMenuPrimitive.Portal>
|
|
72
|
+
);
|
|
73
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
74
|
+
|
|
75
|
+
const DropdownMenuItem = ({
|
|
76
|
+
className,
|
|
77
|
+
inset,
|
|
78
|
+
...props
|
|
79
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
|
80
|
+
inset?: boolean;
|
|
81
|
+
}) => (
|
|
82
|
+
<DropdownMenuPrimitive.Item
|
|
83
|
+
className={cn(
|
|
84
|
+
'focus:bg-bg-secondary relative flex cursor-default items-center rounded-lg px-2 py-2 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
85
|
+
inset && 'pl-8',
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
92
|
+
|
|
93
|
+
const DropdownMenuCheckboxItem = ({
|
|
94
|
+
className,
|
|
95
|
+
children,
|
|
96
|
+
checked,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) => (
|
|
99
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
100
|
+
checked={checked}
|
|
101
|
+
className={cn(
|
|
102
|
+
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
103
|
+
className,
|
|
104
|
+
)}
|
|
105
|
+
{...props}
|
|
106
|
+
>
|
|
107
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center rounded-xs bg-gray-300">
|
|
108
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
109
|
+
<Check className="h-3 w-3" />
|
|
110
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
111
|
+
</span>
|
|
112
|
+
{children}
|
|
113
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
114
|
+
);
|
|
115
|
+
DropdownMenuCheckboxItem.displayName =
|
|
116
|
+
DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
117
|
+
|
|
118
|
+
const DropdownMenuRadioItem = ({
|
|
119
|
+
className,
|
|
120
|
+
children,
|
|
121
|
+
...props
|
|
122
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) => (
|
|
123
|
+
<DropdownMenuPrimitive.RadioItem
|
|
124
|
+
className={cn(
|
|
125
|
+
'focus:bg-bg-secondary relative flex cursor-default items-center rounded-xs py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none focus:text-white data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
126
|
+
'hover:bg-bg-secondary data-[state=checked]:bg-bg-secondary',
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
>
|
|
131
|
+
{/*<span className="absolute left-2 flex items-center justify-center">*/}
|
|
132
|
+
{/* <DropdownMenuPrimitive.ItemIndicator>*/}
|
|
133
|
+
{/* <Check className="h-4 w-4 text-inherit" />*/}
|
|
134
|
+
{/* </DropdownMenuPrimitive.ItemIndicator>*/}
|
|
135
|
+
{/*</span>*/}
|
|
136
|
+
{children}
|
|
137
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
138
|
+
);
|
|
139
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
140
|
+
|
|
141
|
+
const DropdownMenuLabel = ({
|
|
142
|
+
className,
|
|
143
|
+
inset,
|
|
144
|
+
...props
|
|
145
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
|
146
|
+
inset?: boolean;
|
|
147
|
+
}) => (
|
|
148
|
+
<DropdownMenuPrimitive.Label
|
|
149
|
+
className={cn(
|
|
150
|
+
'text-text-secondary text-xs tracking-[2px] uppercase',
|
|
151
|
+
inset && 'pl-8',
|
|
152
|
+
className,
|
|
153
|
+
)}
|
|
154
|
+
{...props}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
158
|
+
|
|
159
|
+
const DropdownMenuSeparator = ({
|
|
160
|
+
className,
|
|
161
|
+
...props
|
|
162
|
+
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) => (
|
|
163
|
+
<DropdownMenuPrimitive.Separator
|
|
164
|
+
className={cn('bg-divider -mx-1 my-1 h-px', className)}
|
|
165
|
+
{...props}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
169
|
+
|
|
170
|
+
const DropdownMenuShortcut = ({
|
|
171
|
+
className,
|
|
172
|
+
...props
|
|
173
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
174
|
+
return (
|
|
175
|
+
<span
|
|
176
|
+
className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
|
|
177
|
+
{...props}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
DropdownMenu,
|
|
185
|
+
DropdownMenuTrigger,
|
|
186
|
+
DropdownMenuContent,
|
|
187
|
+
DropdownMenuItem,
|
|
188
|
+
DropdownMenuCheckboxItem,
|
|
189
|
+
DropdownMenuRadioItem,
|
|
190
|
+
DropdownMenuLabel,
|
|
191
|
+
DropdownMenuSeparator,
|
|
192
|
+
DropdownMenuShortcut,
|
|
193
|
+
DropdownMenuGroup,
|
|
194
|
+
DropdownMenuPortal,
|
|
195
|
+
DropdownMenuSub,
|
|
196
|
+
DropdownMenuSubContent,
|
|
197
|
+
DropdownMenuSubTrigger,
|
|
198
|
+
DropdownMenuRadioGroup,
|
|
199
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ErrorMessageProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const ErrorMessage = ({ message, ...props }: ErrorMessageProps) => {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
className="rounded-sm bg-red-500/10 px-4 py-2 text-sm text-red-700"
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
<strong className="font-bold">Error: </strong>
|
|
14
|
+
<span className="block sm:inline"> {message} </span>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export { ErrorMessage };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { useTranslation } from '@hanzo_network/hanzo-i18n';
|
|
2
|
+
import { partial } from 'filesize';
|
|
3
|
+
import { Loader2, Trash, Upload } from 'lucide-react';
|
|
4
|
+
import React, { useEffect, useState } from 'react';
|
|
5
|
+
import { useDropzone } from 'react-dropzone';
|
|
6
|
+
|
|
7
|
+
import { fileIconMap, FileTypeIcon, PaperClipIcon } from '../assets';
|
|
8
|
+
import { getFileExt } from '../helpers/file';
|
|
9
|
+
import { cn } from '../src/utils';
|
|
10
|
+
import { Button } from './button';
|
|
11
|
+
import { ScrollArea } from './scroll-area';
|
|
12
|
+
|
|
13
|
+
const openFile = (file: File): void => {
|
|
14
|
+
const fileURL = window.URL.createObjectURL(file);
|
|
15
|
+
window.open(fileURL, '_blank');
|
|
16
|
+
URL.revokeObjectURL(fileURL);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const FileItem = ({
|
|
20
|
+
file,
|
|
21
|
+
actions,
|
|
22
|
+
}: {
|
|
23
|
+
file: File;
|
|
24
|
+
actions?: {
|
|
25
|
+
label: string;
|
|
26
|
+
icon: React.ReactNode;
|
|
27
|
+
onClick: (file: File) => void;
|
|
28
|
+
}[];
|
|
29
|
+
}) => {
|
|
30
|
+
const size = partial({ standard: 'jedec' });
|
|
31
|
+
const hasPreviewImage = file?.type?.includes('image/');
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="bg-bg-quaternary relative flex items-center gap-2 rounded-xl px-3 py-1.5 pr-2">
|
|
35
|
+
<span className="flex w-[30px] items-center justify-center">
|
|
36
|
+
{hasPreviewImage ? (
|
|
37
|
+
<FileImagePreview
|
|
38
|
+
className="h-full rounded-md object-cover"
|
|
39
|
+
file={file}
|
|
40
|
+
/>
|
|
41
|
+
) : fileIconMap[getFileExt(file.name)] ? (
|
|
42
|
+
<FileTypeIcon
|
|
43
|
+
className="text-text-secondary"
|
|
44
|
+
type={getFileExt(file.name)}
|
|
45
|
+
/>
|
|
46
|
+
) : (
|
|
47
|
+
<PaperClipIcon className="text-text-secondary h-4 w-4" />
|
|
48
|
+
)}
|
|
49
|
+
</span>
|
|
50
|
+
<div className="line-clamp-1 flex flex-1 flex-col gap-1">
|
|
51
|
+
<button
|
|
52
|
+
className="text-left hover:underline"
|
|
53
|
+
onClick={() => openFile(file)}
|
|
54
|
+
type="button"
|
|
55
|
+
>
|
|
56
|
+
<span className="text-text-default line-clamp-1 text-sm">
|
|
57
|
+
{decodeURIComponent(file.name)}
|
|
58
|
+
</span>
|
|
59
|
+
</button>
|
|
60
|
+
{file.size && (
|
|
61
|
+
<span className="text-text-tertiary shrink-0 text-xs">
|
|
62
|
+
{size(file.size)}
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
{!!actions?.length && (
|
|
67
|
+
<div className="shrink-0">
|
|
68
|
+
{actions?.map((action) => (
|
|
69
|
+
<Button
|
|
70
|
+
className="h-8 w-8 border-0 bg-transparent"
|
|
71
|
+
key={action.label}
|
|
72
|
+
onClick={() => action.onClick(file)}
|
|
73
|
+
size="icon"
|
|
74
|
+
type="button"
|
|
75
|
+
variant="outline"
|
|
76
|
+
>
|
|
77
|
+
<span className="sr-only">{action.label}</span>
|
|
78
|
+
{action.icon}
|
|
79
|
+
</Button>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const FileUploader = ({
|
|
88
|
+
value,
|
|
89
|
+
onChange,
|
|
90
|
+
maxFiles,
|
|
91
|
+
accept,
|
|
92
|
+
allowMultiple,
|
|
93
|
+
disabled,
|
|
94
|
+
descriptionText,
|
|
95
|
+
shouldDisableScrolling,
|
|
96
|
+
}: {
|
|
97
|
+
value: File[];
|
|
98
|
+
onChange: (files: File[]) => void;
|
|
99
|
+
maxFiles?: number;
|
|
100
|
+
accept?: string;
|
|
101
|
+
allowMultiple?: boolean;
|
|
102
|
+
disabled?: boolean;
|
|
103
|
+
descriptionText?: string;
|
|
104
|
+
shouldDisableScrolling?: boolean;
|
|
105
|
+
}) => {
|
|
106
|
+
const { t } = useTranslation();
|
|
107
|
+
const { getRootProps: getRootFileProps, getInputProps: getInputFileProps } =
|
|
108
|
+
useDropzone({
|
|
109
|
+
multiple: allowMultiple,
|
|
110
|
+
maxFiles: maxFiles,
|
|
111
|
+
onDrop: (acceptedFiles) => {
|
|
112
|
+
onChange(acceptedFiles);
|
|
113
|
+
},
|
|
114
|
+
disabled: disabled,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex w-full flex-col gap-2.5">
|
|
119
|
+
<div
|
|
120
|
+
{...getRootFileProps({
|
|
121
|
+
className:
|
|
122
|
+
'dropzone py-4 bg-bg-secondary group relative mt-3 flex cursor-pointer items-center justify-center overflow-hidden rounded-lg border border-dashed border-divider transition-colors hover:border-border-input-focus',
|
|
123
|
+
})}
|
|
124
|
+
>
|
|
125
|
+
<div className="flex flex-col items-center justify-center space-y-1 px-2">
|
|
126
|
+
<div className="bg-bg-tertiary rounded-full p-2 shadow-xs">
|
|
127
|
+
<Upload className="h-4 w-4" />
|
|
128
|
+
</div>
|
|
129
|
+
<p className="text-sm text-white">{t('common.clickToUpload')}</p>
|
|
130
|
+
{descriptionText && (
|
|
131
|
+
<p className="text-text-tertiary line-clamp-1 text-xs">
|
|
132
|
+
{descriptionText}
|
|
133
|
+
</p>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<input
|
|
138
|
+
{...getInputFileProps({
|
|
139
|
+
accept: accept,
|
|
140
|
+
})}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{!!value?.length && (
|
|
145
|
+
<ScrollArea
|
|
146
|
+
className={cn(
|
|
147
|
+
'max-h-[40vh] flex-1 grow overflow-y-scroll pr-1 [&>div>div]:!block',
|
|
148
|
+
shouldDisableScrolling && 'max-h-full overflow-y-auto pr-0',
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
<div className="flex flex-col gap-2">
|
|
152
|
+
{value?.map((file, idx) => (
|
|
153
|
+
<FileItem
|
|
154
|
+
actions={[
|
|
155
|
+
{
|
|
156
|
+
label: 'Delete',
|
|
157
|
+
icon: <Trash className="text-text-tertiary h-4 w-4" />,
|
|
158
|
+
onClick: (file) => {
|
|
159
|
+
const newFiles = [...value];
|
|
160
|
+
newFiles.splice(newFiles.indexOf(file), 1);
|
|
161
|
+
onChange(newFiles);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
]}
|
|
165
|
+
file={file}
|
|
166
|
+
key={file.name + idx}
|
|
167
|
+
/>
|
|
168
|
+
))}
|
|
169
|
+
</div>
|
|
170
|
+
</ScrollArea>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
interface FileImagePreview extends React.HTMLAttributes<HTMLDivElement> {
|
|
177
|
+
file: File;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const FileImagePreview = ({ file, ...props }: FileImagePreview) => {
|
|
181
|
+
const [imageSrc, setImageSrc] = useState('');
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
const reader = new FileReader();
|
|
184
|
+
reader.addEventListener(
|
|
185
|
+
'load',
|
|
186
|
+
function () {
|
|
187
|
+
setImageSrc(reader.result as string);
|
|
188
|
+
},
|
|
189
|
+
false,
|
|
190
|
+
);
|
|
191
|
+
if (file) {
|
|
192
|
+
reader.readAsDataURL(file);
|
|
193
|
+
}
|
|
194
|
+
}, [file]);
|
|
195
|
+
return imageSrc ? (
|
|
196
|
+
<img alt="preview" src={imageSrc} {...props} />
|
|
197
|
+
) : (
|
|
198
|
+
<Loader2 />
|
|
199
|
+
);
|
|
200
|
+
};
|