@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.
Files changed (156) hide show
  1. package/README-MCP.md +3 -3
  2. package/README.md +229 -0
  3. package/assets/ai-icons.tsx +207 -0
  4. package/assets/crypto.tsx +33 -0
  5. package/assets/file-type-icon.tsx +66 -0
  6. package/assets/file.tsx +45 -0
  7. package/assets/general.tsx +2318 -0
  8. package/assets/hanzo-logo.svg +9 -0
  9. package/assets/hanzo-logo.tsx +15 -0
  10. package/assets/index.ts +8 -0
  11. package/assets/index.tsx +4 -0
  12. package/assets/llm-provider.tsx +1094 -0
  13. package/bin/create-registry.js +1 -1
  14. package/bin/test-mcp.sh +1 -1
  15. package/bin/update-registry.js +2 -2
  16. package/blocks/components/content.tsx +1 -1
  17. package/blocks/components/grid-block/index.tsx +1 -1
  18. package/blocks/components/screenful-block/content.tsx +1 -1
  19. package/blocks/components/screenful-block/poster-background.tsx +1 -1
  20. package/components/index.ts +56 -0
  21. package/dist/button.d.ts +1 -0
  22. package/dist/button.js +1 -0
  23. package/dist/hooks/index.d.ts +7 -0
  24. package/dist/hooks/index.js +7 -0
  25. package/dist/hooks/use-click-away.d.ts +2 -0
  26. package/dist/hooks/use-click-away.js +23 -0
  27. package/dist/hooks/use-combined-refs.d.ts +3 -0
  28. package/dist/hooks/use-combined-refs.js +18 -0
  29. package/dist/hooks/use-copy-clipboard.d.ts +9 -0
  30. package/dist/hooks/use-copy-clipboard.js +21 -0
  31. package/dist/hooks/use-debounce.d.ts +1 -0
  32. package/dist/hooks/use-debounce.js +13 -0
  33. package/dist/hooks/use-fill-ids.d.ts +8 -0
  34. package/dist/hooks/use-fill-ids.js +20 -0
  35. package/dist/hooks/use-map.d.ts +1 -0
  36. package/dist/hooks/use-map.js +20 -0
  37. package/dist/hooks/use-measure.d.ts +8 -0
  38. package/dist/hooks/use-measure.js +25 -0
  39. package/dist/hooks/use-reverse-video-playback.d.ts +1 -0
  40. package/dist/hooks/use-reverse-video-playback.js +41 -0
  41. package/dist/hooks/use-scroll-restoration.d.ts +8 -0
  42. package/dist/hooks/use-scroll-restoration.js +36 -0
  43. package/dist/mcp/enhanced-server.js +2 -2
  44. package/dist/registry/api.d.ts +1 -1
  45. package/dist/registry/api.js +3 -3
  46. package/dist/registry/index.d.ts +48 -48
  47. package/dist/registry/index.js +3 -3
  48. package/dist/utils.d.ts +1 -0
  49. package/dist/utils.js +1 -0
  50. package/helpers/file.ts +33 -0
  51. package/helpers/memoization.ts +40 -0
  52. package/package.json +27 -6
  53. package/primitives/accordion.tsx +53 -45
  54. package/primitives/alert-dialog.tsx +185 -0
  55. package/primitives/alert.tsx +74 -0
  56. package/primitives/apply-typography.tsx +1 -1
  57. package/primitives/avatar.tsx +37 -29
  58. package/primitives/background-beams.tsx +142 -0
  59. package/primitives/badge.tsx +27 -19
  60. package/primitives/breadcrumb.tsx +77 -62
  61. package/primitives/button.tsx +69 -72
  62. package/primitives/card.tsx +73 -59
  63. package/primitives/chat/chat-input-area.tsx +87 -0
  64. package/primitives/chat/chat-input.tsx +71 -0
  65. package/primitives/chat/files-preview.tsx +330 -0
  66. package/primitives/chat/index.ts +6 -0
  67. package/primitives/chat/json-form.tsx +8 -0
  68. package/primitives/chat/message-list.tsx +307 -0
  69. package/primitives/chat/message.tsx +569 -0
  70. package/primitives/chat/sqlite-preview.tsx +215 -0
  71. package/primitives/checkbox.tsx +18 -19
  72. package/primitives/collapsible.tsx +9 -0
  73. package/primitives/command.tsx +75 -83
  74. package/primitives/context-menu.tsx +115 -109
  75. package/primitives/copy-to-clipboard-icon.tsx +60 -0
  76. package/primitives/dialog-video-controller.tsx +1 -1
  77. package/primitives/dialog.tsx +111 -145
  78. package/primitives/dot-pattern.tsx +57 -0
  79. package/primitives/dots-loader.tsx +13 -0
  80. package/primitives/drawer.tsx +59 -87
  81. package/primitives/dropdown-menu.tsx +199 -0
  82. package/primitives/error-message.tsx +19 -0
  83. package/primitives/file-uploader.tsx +200 -0
  84. package/primitives/form.tsx +92 -87
  85. package/primitives/hover-card.tsx +28 -0
  86. package/primitives/icons/github.tsx +1 -1
  87. package/primitives/icons/youtube-logo.tsx +1 -1
  88. package/primitives/index-common.ts +121 -42
  89. package/primitives/index-next.ts +3 -1
  90. package/primitives/input.tsx +115 -20
  91. package/primitives/label.tsx +15 -23
  92. package/primitives/loading-spinner.tsx +1 -1
  93. package/primitives/markdown-preview.tsx +609 -0
  94. package/primitives/mermaid.tsx +196 -0
  95. package/primitives/next/link-element.tsx +1 -1
  96. package/primitives/next/mdx-link.tsx +1 -1
  97. package/primitives/pagination.tsx +117 -0
  98. package/primitives/popover.tsx +20 -25
  99. package/primitives/pretty-json-print.tsx +28 -0
  100. package/primitives/progress.tsx +14 -15
  101. package/primitives/prompt-textarea.tsx +72 -0
  102. package/primitives/qr-code.tsx +112 -0
  103. package/primitives/radio-group.tsx +25 -39
  104. package/primitives/resizable.tsx +47 -0
  105. package/primitives/scroll-area.tsx +35 -25
  106. package/primitives/search-input.tsx +66 -0
  107. package/primitives/select.tsx +62 -109
  108. package/primitives/separator.tsx +22 -26
  109. package/primitives/sheet.tsx +78 -117
  110. package/primitives/skeleton.tsx +13 -16
  111. package/primitives/slider.tsx +50 -60
  112. package/primitives/stepper.tsx +272 -0
  113. package/primitives/switch.tsx +14 -23
  114. package/primitives/table.tsx +65 -77
  115. package/primitives/tabs.tsx +29 -39
  116. package/primitives/text-link.tsx +25 -0
  117. package/primitives/textarea.tsx +61 -0
  118. package/primitives/textfield.tsx +75 -0
  119. package/primitives/toast.tsx +30 -0
  120. package/primitives/toggle-group.tsx +33 -33
  121. package/primitives/toggle.tsx +22 -51
  122. package/primitives/tooltip.tsx +37 -38
  123. package/registry.json +1 -1
  124. package/src/button.ts +1 -0
  125. package/src/hooks/index.ts +7 -0
  126. package/src/hooks/use-click-away.ts +31 -0
  127. package/src/hooks/use-combined-refs.ts +22 -0
  128. package/src/hooks/use-copy-clipboard.ts +30 -0
  129. package/src/hooks/use-debounce.ts +17 -0
  130. package/src/hooks/use-fill-ids.ts +25 -0
  131. package/src/hooks/use-map.ts +26 -0
  132. package/src/hooks/use-measure.ts +42 -0
  133. package/src/hooks/use-reverse-video-playback.ts +43 -0
  134. package/src/hooks/use-scroll-restoration.ts +50 -0
  135. package/src/mcp/README.md +1 -1
  136. package/src/mcp/enhanced-server.ts +2 -2
  137. package/src/registry/api.ts +3 -3
  138. package/src/registry/index.ts +3 -3
  139. package/src/utils.ts +1 -0
  140. package/style/theme-provider.tsx +1 -1
  141. package/test-imports.mjs +19 -0
  142. package/types/animation-def.ts +1 -1
  143. package/types/button-def.ts +1 -1
  144. package/types/index.ts +1 -1
  145. package/util/blob.ts +28 -0
  146. package/util/copy-to-clipboard.ts +17 -0
  147. package/util/create-shadow-root.ts +22 -0
  148. package/util/date.ts +83 -0
  149. package/util/debounce.ts +11 -0
  150. package/util/file.ts +15 -0
  151. package/util/format-and-abbreviate-as-currency.ts +1 -1
  152. package/util/format-text.ts +33 -0
  153. package/util/index.ts +9 -78
  154. package/util/timing.ts +3 -0
  155. package/util/toasts.tsx +17 -0
  156. 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
+ };
@@ -1,4 +1,4 @@
1
- import React, { type PropsWithChildren } from 'react'
1
+ import React, { PropsWithChildren } from 'react'
2
2
  import Link from 'next/link'
3
3
 
4
4
  import type { LinkDef } from '../../types'
@@ -1,4 +1,4 @@
1
- import React, { type AnchorHTMLAttributes, type PropsWithChildren } from 'react'
1
+ import React, { AnchorHTMLAttributes, type PropsWithChildren } from 'react'
2
2
  import Link from 'next/link'
3
3
 
4
4
  const MDXLink: React.FC<AnchorHTMLAttributes<HTMLAnchorElement> > = ({
@@ -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
+ };
@@ -1,37 +1,32 @@
1
- 'use client'
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 '../util'
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 PopoverContent = React.forwardRef<
14
- React.ElementRef<typeof PopoverPrimitive.Content>,
15
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
16
- >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
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
- 'z-above-modal-2 w-72 rounded-md border bg-level-1 p-4 text-foreground shadow-md outline-none ' +
25
- 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 ' +
26
- 'data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 ' +
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, PopoverClose, PopoverArrow }
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
+ };
@@ -1,27 +1,26 @@
1
- 'use client'
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 '../util'
4
+ import { cn } from '../src/utils';
6
5
 
7
- const Progress = React.forwardRef<
8
- React.ElementRef<typeof ProgressPrimitive.Root>,
9
- React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
10
- >(({ className, value, ...props }, ref) => (
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 rounded-full bg-level-2',
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='h-full w-full flex-1 bg-accent transition-all'
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 default Progress
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
+ }