@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,196 @@
|
|
|
1
|
+
import { save } from '@tauri-apps/plugin-dialog';
|
|
2
|
+
import * as fs from '@tauri-apps/plugin-fs';
|
|
3
|
+
import { RotateCcw, Download } from 'lucide-react';
|
|
4
|
+
import mermaid from 'mermaid';
|
|
5
|
+
import { FC, useEffect, useRef, useState, useCallback } from 'react';
|
|
6
|
+
import { toast } from 'sonner';
|
|
7
|
+
import svgPanZoom from 'svg-pan-zoom';
|
|
8
|
+
import { cn } from '../src/utils';
|
|
9
|
+
import { Button } from './button';
|
|
10
|
+
import { SyntaxHighlighterProps } from './markdown-preview';
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
|
|
12
|
+
|
|
13
|
+
export type MermaidDiagramProps = SyntaxHighlighterProps & {
|
|
14
|
+
className?: string;
|
|
15
|
+
isRunning?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
mermaid.initialize({
|
|
19
|
+
startOnLoad: true,
|
|
20
|
+
mindmap: {
|
|
21
|
+
useWidth: 800,
|
|
22
|
+
},
|
|
23
|
+
theme: 'dark',
|
|
24
|
+
fontSize: 18,
|
|
25
|
+
darkMode: true,
|
|
26
|
+
fontFamily: 'Inter',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const generateId = () => `mermaid-${Math.random().toString(36).slice(2)}`;
|
|
30
|
+
|
|
31
|
+
const downloadBlob = async (blob: Blob, filename: string) => {
|
|
32
|
+
const filePath = await save({
|
|
33
|
+
title: 'Save Mermaid Diagram',
|
|
34
|
+
filters: [
|
|
35
|
+
{
|
|
36
|
+
name: 'SVG Files',
|
|
37
|
+
extensions: ['svg'],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
defaultPath: filename,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (filePath) {
|
|
44
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
45
|
+
await fs.writeFile(filePath, new Uint8Array(arrayBuffer), {
|
|
46
|
+
baseDir: fs.BaseDirectory.Download,
|
|
47
|
+
});
|
|
48
|
+
toast.success('Mermaid diagram saved successfully');
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const MermaidDiagram: FC<MermaidDiagramProps> = ({
|
|
53
|
+
code,
|
|
54
|
+
className,
|
|
55
|
+
isRunning,
|
|
56
|
+
}) => {
|
|
57
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
58
|
+
const lastCode = useRef<string | null>(null);
|
|
59
|
+
const [instance, setInstance] = useState<any>(null);
|
|
60
|
+
|
|
61
|
+
const isComplete = !isRunning;
|
|
62
|
+
|
|
63
|
+
const resetZoom = useCallback(() => {
|
|
64
|
+
instance?.fit();
|
|
65
|
+
instance?.center();
|
|
66
|
+
}, [instance]);
|
|
67
|
+
|
|
68
|
+
const downloadSVG = useCallback(() => {
|
|
69
|
+
if (ref.current) {
|
|
70
|
+
const svg = ref.current.innerHTML;
|
|
71
|
+
const blob = new Blob([svg], { type: 'image/svg+xml' });
|
|
72
|
+
void downloadBlob(blob, 'mermaid-diagram.svg');
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const enableZoom = useCallback(() => {
|
|
77
|
+
instance?.enablePan();
|
|
78
|
+
instance?.enableZoom();
|
|
79
|
+
}, [instance]);
|
|
80
|
+
|
|
81
|
+
const disableZoom = useCallback(() => {
|
|
82
|
+
instance?.disablePan();
|
|
83
|
+
instance?.disableZoom();
|
|
84
|
+
}, [instance]);
|
|
85
|
+
|
|
86
|
+
const handleFocus = useCallback(() => {
|
|
87
|
+
enableZoom();
|
|
88
|
+
}, [enableZoom]);
|
|
89
|
+
|
|
90
|
+
const handleBlur = useCallback(() => {
|
|
91
|
+
disableZoom();
|
|
92
|
+
}, [disableZoom]);
|
|
93
|
+
|
|
94
|
+
const handleClick = useCallback(() => {
|
|
95
|
+
if (ref.current) {
|
|
96
|
+
ref.current.focus();
|
|
97
|
+
}
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!isComplete || !ref.current || !code) return;
|
|
102
|
+
|
|
103
|
+
if (lastCode.current === code) return;
|
|
104
|
+
|
|
105
|
+
void (async () => {
|
|
106
|
+
try {
|
|
107
|
+
const id = generateId();
|
|
108
|
+
const { svg } = await mermaid.render(id, code);
|
|
109
|
+
|
|
110
|
+
if (ref.current) {
|
|
111
|
+
ref.current.innerHTML = svg;
|
|
112
|
+
|
|
113
|
+
const svgElement = ref.current.querySelector('svg');
|
|
114
|
+
if (svgElement) {
|
|
115
|
+
svgElement.setAttribute('height', '100%');
|
|
116
|
+
svgElement.setAttribute('width', '100%');
|
|
117
|
+
svgElement.style.height = '100%';
|
|
118
|
+
svgElement.style.width = '100%';
|
|
119
|
+
svgElement.style.position = 'absolute';
|
|
120
|
+
svgElement.style.top = '0';
|
|
121
|
+
svgElement.style.left = '0';
|
|
122
|
+
|
|
123
|
+
const panZoomInstance = svgPanZoom(svgElement);
|
|
124
|
+
panZoomInstance.fit();
|
|
125
|
+
panZoomInstance.center();
|
|
126
|
+
panZoomInstance.zoomAtPoint(1, { x: 0, y: 0 });
|
|
127
|
+
panZoomInstance.disablePan();
|
|
128
|
+
panZoomInstance.disableZoom();
|
|
129
|
+
setInstance(panZoomInstance);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
lastCode.current = code;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.warn('Failed to render Mermaid diagram:', e);
|
|
136
|
+
}
|
|
137
|
+
})();
|
|
138
|
+
}, [code, isComplete]);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div className="relative">
|
|
142
|
+
<div className="absolute top-2 right-2 z-10 flex gap-1">
|
|
143
|
+
<Tooltip>
|
|
144
|
+
<TooltipTrigger asChild>
|
|
145
|
+
<Button
|
|
146
|
+
variant="tertiary"
|
|
147
|
+
size="icon"
|
|
148
|
+
onClick={resetZoom}
|
|
149
|
+
className="text-text-secondary"
|
|
150
|
+
>
|
|
151
|
+
<RotateCcw size={14} />
|
|
152
|
+
</Button>
|
|
153
|
+
</TooltipTrigger>
|
|
154
|
+
<TooltipContent>
|
|
155
|
+
<p>Reset Zoom</p>
|
|
156
|
+
</TooltipContent>
|
|
157
|
+
</Tooltip>
|
|
158
|
+
<Tooltip>
|
|
159
|
+
<TooltipTrigger asChild>
|
|
160
|
+
<Button
|
|
161
|
+
variant="tertiary"
|
|
162
|
+
size="icon"
|
|
163
|
+
onClick={downloadSVG}
|
|
164
|
+
className="text-text-secondary"
|
|
165
|
+
>
|
|
166
|
+
<Download size={14} />
|
|
167
|
+
</Button>
|
|
168
|
+
</TooltipTrigger>
|
|
169
|
+
<TooltipContent>
|
|
170
|
+
<p>Download SVG</p>
|
|
171
|
+
</TooltipContent>
|
|
172
|
+
</Tooltip>
|
|
173
|
+
</div>
|
|
174
|
+
<div
|
|
175
|
+
onClick={handleClick}
|
|
176
|
+
className="text-text-secondary bg-bg-quaternary absolute right-2 bottom-2 z-10 rounded px-2 py-1 text-xs"
|
|
177
|
+
>
|
|
178
|
+
Click to focus, then scroll to zoom & drag to pan
|
|
179
|
+
</div>
|
|
180
|
+
<div
|
|
181
|
+
ref={ref}
|
|
182
|
+
tabIndex={0}
|
|
183
|
+
onFocus={handleFocus}
|
|
184
|
+
onBlur={handleBlur}
|
|
185
|
+
onClick={handleClick}
|
|
186
|
+
className={cn(
|
|
187
|
+
'text-text-secondary bg-bg-quaternary focus:ring-opacity-50 relative w-full cursor-grab overflow-hidden rounded-b-lg p-4 py-10 text-center text-sm focus:cursor-grabbing focus:ring-2 focus:ring-blue-500 focus:outline-none',
|
|
188
|
+
className,
|
|
189
|
+
)}
|
|
190
|
+
style={{ width: '100%', minHeight: '400px' }}
|
|
191
|
+
>
|
|
192
|
+
Drawing diagram...
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { MoreHorizontal, RedoIcon, UndoIcon } from 'lucide-react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../src/utils';
|
|
5
|
+
import { ButtonProps, buttonVariants } from './button';
|
|
6
|
+
|
|
7
|
+
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
|
|
8
|
+
<nav
|
|
9
|
+
aria-label="pagination"
|
|
10
|
+
className={cn('mx-auto flex w-full justify-center', className)}
|
|
11
|
+
role="navigation"
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
Pagination.displayName = 'Pagination';
|
|
16
|
+
|
|
17
|
+
const PaginationContent = ({
|
|
18
|
+
className,
|
|
19
|
+
...props
|
|
20
|
+
}: React.ComponentProps<'ul'>) => (
|
|
21
|
+
<ul
|
|
22
|
+
className={cn('flex flex-row items-center gap-1', className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
PaginationContent.displayName = 'PaginationContent';
|
|
27
|
+
|
|
28
|
+
const PaginationItem = ({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<'li'>) => (
|
|
32
|
+
<li className={cn('', className)} {...props} />
|
|
33
|
+
);
|
|
34
|
+
PaginationItem.displayName = 'PaginationItem';
|
|
35
|
+
|
|
36
|
+
type PaginationLinkProps = {
|
|
37
|
+
isActive?: boolean;
|
|
38
|
+
} & Pick<ButtonProps, 'size'> &
|
|
39
|
+
React.ComponentProps<'a'>;
|
|
40
|
+
|
|
41
|
+
const PaginationLink = ({
|
|
42
|
+
className,
|
|
43
|
+
isActive,
|
|
44
|
+
size = 'icon',
|
|
45
|
+
...props
|
|
46
|
+
}: PaginationLinkProps) => (
|
|
47
|
+
<a
|
|
48
|
+
aria-current={isActive ? 'page' : undefined}
|
|
49
|
+
className={cn(
|
|
50
|
+
buttonVariants({
|
|
51
|
+
variant: isActive ? 'outline' : 'tertiary',
|
|
52
|
+
size,
|
|
53
|
+
}),
|
|
54
|
+
'size-[30px] rounded-lg p-0.5',
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
PaginationLink.displayName = 'PaginationLink';
|
|
61
|
+
|
|
62
|
+
const PaginationPrevious = ({
|
|
63
|
+
className,
|
|
64
|
+
...props
|
|
65
|
+
}: React.ComponentProps<typeof PaginationLink>) => (
|
|
66
|
+
<PaginationLink
|
|
67
|
+
aria-label="Go to previous page"
|
|
68
|
+
className={cn('gap-1', className)}
|
|
69
|
+
size="sm"
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
<UndoIcon className="h-4 w-4" />
|
|
73
|
+
<span className="sr-only">Previous</span>
|
|
74
|
+
</PaginationLink>
|
|
75
|
+
);
|
|
76
|
+
PaginationPrevious.displayName = 'PaginationPrevious';
|
|
77
|
+
|
|
78
|
+
const PaginationNext = ({
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}: React.ComponentProps<typeof PaginationLink>) => (
|
|
82
|
+
<PaginationLink
|
|
83
|
+
aria-label="Go to next page"
|
|
84
|
+
className={cn('gap-1', className)}
|
|
85
|
+
size="sm"
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
<span className="sr-only">Next</span>
|
|
89
|
+
<RedoIcon className="h-4 w-4" />
|
|
90
|
+
</PaginationLink>
|
|
91
|
+
);
|
|
92
|
+
PaginationNext.displayName = 'PaginationNext';
|
|
93
|
+
|
|
94
|
+
const PaginationEllipsis = ({
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<'span'>) => (
|
|
98
|
+
<span
|
|
99
|
+
aria-hidden
|
|
100
|
+
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
104
|
+
<span className="sr-only">More pages</span>
|
|
105
|
+
</span>
|
|
106
|
+
);
|
|
107
|
+
PaginationEllipsis.displayName = 'PaginationEllipsis';
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
Pagination,
|
|
111
|
+
PaginationContent,
|
|
112
|
+
PaginationLink,
|
|
113
|
+
PaginationItem,
|
|
114
|
+
PaginationPrevious,
|
|
115
|
+
PaginationNext,
|
|
116
|
+
PaginationEllipsis,
|
|
117
|
+
};
|
package/primitives/popover.tsx
CHANGED
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
|
1
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
2
|
+
import * as React from 'react';
|
|
4
3
|
|
|
5
|
-
import { cn } from '../
|
|
4
|
+
import { cn } from '../src/utils';
|
|
6
5
|
|
|
7
|
-
const Popover = PopoverPrimitive.Root
|
|
8
|
-
const PopoverTrigger = PopoverPrimitive.Trigger
|
|
9
|
-
const PopoverClose = PopoverPrimitive.Close
|
|
10
|
-
const PopoverAnchor = PopoverPrimitive.Anchor
|
|
11
|
-
const PopoverArrow = PopoverPrimitive.Arrow
|
|
6
|
+
const Popover = PopoverPrimitive.Root;
|
|
12
7
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
10
|
+
|
|
11
|
+
const PopoverContent = ({
|
|
12
|
+
className,
|
|
13
|
+
align = 'center',
|
|
14
|
+
sideOffset = 4,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) => (
|
|
18
17
|
<PopoverPrimitive.Portal>
|
|
19
18
|
<PopoverPrimitive.Content
|
|
20
|
-
ref={ref}
|
|
21
19
|
align={align}
|
|
22
|
-
sideOffset={sideOffset}
|
|
23
20
|
className={cn(
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
|
|
27
|
-
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 ' +
|
|
28
|
-
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
29
|
-
className
|
|
21
|
+
'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',
|
|
22
|
+
'bg-bg-dark border-divider z-50 w-72 rounded-md border p-4 text-gray-50 shadow-lg shadow-md outline-hidden',
|
|
23
|
+
className,
|
|
30
24
|
)}
|
|
25
|
+
sideOffset={sideOffset}
|
|
31
26
|
{...props}
|
|
32
27
|
/>
|
|
33
28
|
</PopoverPrimitive.Portal>
|
|
34
|
-
)
|
|
35
|
-
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
|
29
|
+
);
|
|
30
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
36
31
|
|
|
37
|
-
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor
|
|
32
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { cn } from '../src/utils';
|
|
3
|
+
|
|
4
|
+
export const PrettyJsonPrint = ({
|
|
5
|
+
json,
|
|
6
|
+
className,
|
|
7
|
+
}: {
|
|
8
|
+
json: string | object;
|
|
9
|
+
className?: string;
|
|
10
|
+
}) => {
|
|
11
|
+
const formattedJson = useMemo(() => {
|
|
12
|
+
let formattedValue = `Unparseable JSON: String(${json})`;
|
|
13
|
+
if (typeof json === 'object') {
|
|
14
|
+
formattedValue = JSON.stringify(json, null, 2);
|
|
15
|
+
} else if (typeof json === 'string') {
|
|
16
|
+
try {
|
|
17
|
+
const parsedJson = JSON.parse(json);
|
|
18
|
+
formattedValue = JSON.stringify(parsedJson, null, 2);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('error parsing json', error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return formattedValue;
|
|
24
|
+
}, [json]);
|
|
25
|
+
return (
|
|
26
|
+
<pre className={cn('overflow-x-scroll', className)}>{formattedJson}</pre>
|
|
27
|
+
);
|
|
28
|
+
};
|
package/primitives/progress.tsx
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import * as ProgressPrimitive from '@radix-ui/react-progress'
|
|
1
|
+
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|
2
|
+
import * as React from 'react';
|
|
4
3
|
|
|
5
|
-
import { cn } from '../
|
|
4
|
+
import { cn } from '../src/utils';
|
|
6
5
|
|
|
7
|
-
const Progress =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
const Progress = ({
|
|
7
|
+
className,
|
|
8
|
+
value,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof ProgressPrimitive.Root>) => (
|
|
11
11
|
<ProgressPrimitive.Root
|
|
12
|
-
ref={ref}
|
|
13
12
|
className={cn(
|
|
14
|
-
'relative h-4 w-full overflow-hidden
|
|
15
|
-
className
|
|
13
|
+
'bg-bg-quaternary relative h-4 w-full overflow-hidden',
|
|
14
|
+
className,
|
|
16
15
|
)}
|
|
17
16
|
{...props}
|
|
18
17
|
>
|
|
19
18
|
<ProgressPrimitive.Indicator
|
|
20
|
-
className=
|
|
19
|
+
className="h-full w-full flex-1 bg-green-500 transition-all"
|
|
21
20
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
22
21
|
/>
|
|
23
22
|
</ProgressPrimitive.Root>
|
|
24
|
-
)
|
|
25
|
-
Progress.displayName = ProgressPrimitive.Root.displayName
|
|
23
|
+
);
|
|
24
|
+
Progress.displayName = ProgressPrimitive.Root.displayName;
|
|
26
25
|
|
|
27
|
-
export
|
|
26
|
+
export { Progress };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../src/utils';
|
|
4
|
+
import { DotsLoader } from './dots-loader';
|
|
5
|
+
import { FormLabel } from './form';
|
|
6
|
+
|
|
7
|
+
const MIN_TEXTAREA_HEIGHT = 32;
|
|
8
|
+
export interface PromptTextareaProps
|
|
9
|
+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
10
|
+
isLoading?: boolean;
|
|
11
|
+
label?: string;
|
|
12
|
+
field: {
|
|
13
|
+
onChange: (...event: any[]) => void;
|
|
14
|
+
onBlur: () => void;
|
|
15
|
+
value: any;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
name: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PromptTextarea = ({ className, ...props }: PromptTextareaProps) => {
|
|
23
|
+
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
|
24
|
+
|
|
25
|
+
React.useLayoutEffect(() => {
|
|
26
|
+
// Reset height - important to shrink on delete
|
|
27
|
+
|
|
28
|
+
if (!textareaRef.current) return;
|
|
29
|
+
|
|
30
|
+
textareaRef.current.style.height = '60px';
|
|
31
|
+
// Set height
|
|
32
|
+
textareaRef.current.style.height = `${Math.max(
|
|
33
|
+
textareaRef.current.scrollHeight + 2,
|
|
34
|
+
MIN_TEXTAREA_HEIGHT,
|
|
35
|
+
)}px`;
|
|
36
|
+
|
|
37
|
+
if (props.autoFocus !== undefined && props.autoFocus) {
|
|
38
|
+
textareaRef.current.focus();
|
|
39
|
+
}
|
|
40
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
|
+
}, [props.value]);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<textarea
|
|
46
|
+
className={cn(
|
|
47
|
+
'flex w-full rounded-md border border-gray-200 bg-gray-400 px-4 py-2 pt-4 text-sm break-words placeholder-gray-100 placeholder:text-base focus-visible:ring-1 focus-visible:ring-gray-100 focus-visible:outline-hidden focus-visible:ring-inset disabled:cursor-not-allowed disabled:opacity-50',
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
style={{
|
|
51
|
+
minHeight: MIN_TEXTAREA_HEIGHT,
|
|
52
|
+
resize: 'none',
|
|
53
|
+
maxHeight: '300px',
|
|
54
|
+
}}
|
|
55
|
+
{...props.field}
|
|
56
|
+
onKeyDown={props.onKeyDown}
|
|
57
|
+
ref={textareaRef}
|
|
58
|
+
{...(!props.isLoading && {
|
|
59
|
+
placeholder: props.label,
|
|
60
|
+
})}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
{props.isLoading ? (
|
|
64
|
+
<DotsLoader className="absolute top-6 left-4" />
|
|
65
|
+
) : null}
|
|
66
|
+
<FormLabel className="sr-only">{props.label}</FormLabel>
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
PromptTextarea.displayName = 'PromptTextarea';
|
|
71
|
+
|
|
72
|
+
export { PromptTextarea };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { DownloadIcon } from '@radix-ui/react-icons';
|
|
2
|
+
import { Check } from 'lucide-react';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { IProps, QRCode as ReactQRCode } from 'react-qrcode-logo';
|
|
5
|
+
|
|
6
|
+
import hanzoLogo from '../assets/images/app-icon.png';
|
|
7
|
+
import { Button } from './button';
|
|
8
|
+
import { Dialog, DialogContent } from './dialog';
|
|
9
|
+
|
|
10
|
+
export function QRCode({
|
|
11
|
+
value,
|
|
12
|
+
size,
|
|
13
|
+
id,
|
|
14
|
+
...props
|
|
15
|
+
}: {
|
|
16
|
+
value: IProps['value'];
|
|
17
|
+
size: IProps['size'];
|
|
18
|
+
id?: IProps['id'];
|
|
19
|
+
} & React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
|
20
|
+
return (
|
|
21
|
+
<div {...props}>
|
|
22
|
+
<ReactQRCode
|
|
23
|
+
ecLevel={'M'}
|
|
24
|
+
eyeColor="black"
|
|
25
|
+
eyeRadius={10}
|
|
26
|
+
fgColor="black"
|
|
27
|
+
id={id}
|
|
28
|
+
logoHeight={size ? size * 0.2 : undefined}
|
|
29
|
+
logoImage={hanzoLogo}
|
|
30
|
+
logoPaddingStyle="circle"
|
|
31
|
+
logoWidth={size ? size * 0.2 : undefined}
|
|
32
|
+
qrStyle="dots"
|
|
33
|
+
removeQrCodeBehindLogo
|
|
34
|
+
size={size ?? 300}
|
|
35
|
+
value={value}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function QrCodeModal({
|
|
42
|
+
open,
|
|
43
|
+
onOpenChange,
|
|
44
|
+
value,
|
|
45
|
+
onSave,
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
modalClassName,
|
|
49
|
+
}: {
|
|
50
|
+
open: boolean;
|
|
51
|
+
onOpenChange: (open: boolean) => void;
|
|
52
|
+
value: IProps['value'];
|
|
53
|
+
onSave: (key: string) => void;
|
|
54
|
+
title: React.ReactNode;
|
|
55
|
+
description: React.ReactNode;
|
|
56
|
+
modalClassName?: string;
|
|
57
|
+
}) {
|
|
58
|
+
const [saved, setSaved] = useState(false);
|
|
59
|
+
const downloadCode = async () => {
|
|
60
|
+
const canvas = document.querySelector('#registration-code-qr');
|
|
61
|
+
if (canvas instanceof HTMLCanvasElement) {
|
|
62
|
+
try {
|
|
63
|
+
const pngUrl = canvas.toDataURL();
|
|
64
|
+
onSave(pngUrl);
|
|
65
|
+
setSaved(true);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(error);
|
|
68
|
+
} finally {
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
setSaved(false);
|
|
71
|
+
}, 3000);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Dialog onOpenChange={onOpenChange} open={open}>
|
|
78
|
+
<DialogContent className={modalClassName}>
|
|
79
|
+
<div className="flex flex-col items-center py-4">
|
|
80
|
+
<h2 className="mb-1 text-lg font-semibold">{title}</h2>
|
|
81
|
+
<p className="text-text-secondary mb-5 text-center text-xs">
|
|
82
|
+
{description}
|
|
83
|
+
</p>
|
|
84
|
+
<div className="mb-7 overflow-hidden rounded-lg shadow-2xl">
|
|
85
|
+
<QRCode size={190} value={value} />
|
|
86
|
+
<QRCode
|
|
87
|
+
className="hidden"
|
|
88
|
+
id="registration-code-qr"
|
|
89
|
+
size={1024}
|
|
90
|
+
value={value}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex flex-col gap-4">
|
|
94
|
+
<Button className="flex gap-1" onClick={downloadCode}>
|
|
95
|
+
{saved ? <Check /> : <DownloadIcon className="h-4 w-4" />}
|
|
96
|
+
{saved ? 'Saved' : 'Download'}
|
|
97
|
+
</Button>
|
|
98
|
+
<Button
|
|
99
|
+
className="flex gap-1"
|
|
100
|
+
onClick={() => {
|
|
101
|
+
onOpenChange(false);
|
|
102
|
+
}}
|
|
103
|
+
variant="tertiary"
|
|
104
|
+
>
|
|
105
|
+
I saved it, close
|
|
106
|
+
</Button>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</DialogContent>
|
|
110
|
+
</Dialog>
|
|
111
|
+
);
|
|
112
|
+
}
|