@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
@@ -1,108 +1,123 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { ChevronRight, MoreHorizontal } from "lucide-react"
4
-
5
- import { cn } from '../util'
6
-
7
- const Breadcrumb = React.forwardRef<
8
- HTMLElement,
9
- React.ComponentPropsWithoutRef<"nav"> & {
10
- separator?: React.ReactNode
11
- }
12
- >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
- Breadcrumb.displayName = "Breadcrumb"
14
-
15
- const BreadcrumbList = React.forwardRef<
16
- HTMLOListElement,
17
- React.ComponentPropsWithoutRef<"ol">
18
- >(({ className, ...props }, ref) => (
1
+ import { Slot } from '@radix-ui/react-slot';
2
+ import { ChevronRight, MoreHorizontal } from 'lucide-react';
3
+ import * as React from 'react';
4
+
5
+ import { cn } from '../src/utils';
6
+
7
+ type BreadcrumbProps = React.ComponentPropsWithoutRef<'nav'> & {
8
+ separator?: React.ReactNode;
9
+ ref?: React.RefObject<HTMLDivElement>;
10
+ };
11
+
12
+ const Breadcrumb = ({ ref, ...props }: BreadcrumbProps) => (
13
+ <nav aria-label="breadcrumb" ref={ref} {...props} />
14
+ );
15
+
16
+ Breadcrumb.displayName = 'Breadcrumb';
17
+
18
+ type BreadcrumbListProps = React.ComponentPropsWithoutRef<'ol'> & {
19
+ ref?: React.RefObject<HTMLOListElement>;
20
+ };
21
+
22
+ const BreadcrumbList = ({ className, ref, ...props }: BreadcrumbListProps) => (
19
23
  <ol
20
- ref={ref}
21
24
  className={cn(
22
- "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-2 sm:gap-2.5",
23
- className
25
+ 'text-text-secondary flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
26
+ className,
24
27
  )}
28
+ ref={ref}
25
29
  {...props}
26
30
  />
27
- ))
28
- BreadcrumbList.displayName = "BreadcrumbList"
31
+ );
32
+ BreadcrumbList.displayName = 'BreadcrumbList';
33
+
34
+ type BreadcrumbItemProps = React.ComponentPropsWithoutRef<'li'> & {
35
+ ref?: React.RefObject<HTMLLIElement>;
36
+ };
29
37
 
30
- const BreadcrumbItem = React.forwardRef<
31
- HTMLLIElement,
32
- React.ComponentPropsWithoutRef<"li">
33
- >(({ className, ...props }, ref) => (
38
+ const BreadcrumbItem = ({ className, ref, ...props }: BreadcrumbItemProps) => (
34
39
  <li
40
+ className={cn('inline-flex items-center gap-1.5', className)}
35
41
  ref={ref}
36
- className={cn("inline-flex items-center gap-1.5", className)}
37
42
  {...props}
38
43
  />
39
- ))
40
- BreadcrumbItem.displayName = "BreadcrumbItem"
44
+ );
45
+ BreadcrumbItem.displayName = 'BreadcrumbItem';
41
46
 
42
- const BreadcrumbLink = React.forwardRef<
43
- HTMLAnchorElement,
44
- React.ComponentPropsWithoutRef<"a"> & {
45
- asChild?: boolean
46
- }
47
- >(({ asChild, className, ...props }, ref) => {
48
- const Comp = asChild ? Slot : "a"
47
+ type BreadcrumbLinkProps = React.ComponentPropsWithoutRef<'a'> & {
48
+ asChild?: boolean;
49
+ ref?: React.RefObject<HTMLAnchorElement>;
50
+ };
51
+
52
+ const BreadcrumbLink = ({
53
+ asChild,
54
+ className,
55
+ ref,
56
+ ...props
57
+ }: BreadcrumbLinkProps) => {
58
+ const Comp = asChild ? Slot : 'a';
49
59
 
50
60
  return (
51
61
  <Comp
62
+ className={cn('hover:text-text-default transition-colors', className)}
52
63
  ref={ref}
53
- className={cn("transition-colors hover:text-foreground", className)}
54
64
  {...props}
55
65
  />
56
- )
57
- })
58
- BreadcrumbLink.displayName = "BreadcrumbLink"
59
-
60
- const BreadcrumbPage = React.forwardRef<
61
- HTMLSpanElement,
62
- React.ComponentPropsWithoutRef<"span">
63
- >(({ className, ...props }, ref) => (
66
+ );
67
+ };
68
+ BreadcrumbLink.displayName = 'BreadcrumbLink';
69
+
70
+ type BreadcrumbPageProps = React.ComponentPropsWithoutRef<'span'> & {
71
+ ref?: React.RefObject<HTMLSpanElement>;
72
+ };
73
+
74
+ const BreadcrumbPage = ({ className, ref, ...props }: BreadcrumbPageProps) => (
64
75
  <span
76
+ aria-current="page"
77
+ aria-disabled="true"
78
+ className={cn('text-text-default font-normal', className)}
65
79
  ref={ref}
66
80
  role="link"
67
- aria-disabled="true"
68
- aria-current="page"
69
- className={cn("font-normal text-foreground", className)}
70
81
  {...props}
71
82
  />
72
- ))
73
- BreadcrumbPage.displayName = "BreadcrumbPage"
83
+ );
84
+ BreadcrumbPage.displayName = 'BreadcrumbPage';
85
+
86
+ type BreadcrumbSeparatorProps = React.ComponentProps<'li'> & {
87
+ ref?: React.RefObject<HTMLLIElement>;
88
+ };
74
89
 
75
90
  const BreadcrumbSeparator = ({
76
91
  children,
77
92
  className,
78
93
  ...props
79
- }: React.ComponentProps<"li">) => (
94
+ }: BreadcrumbSeparatorProps) => (
80
95
  <li
81
- role="presentation"
82
96
  aria-hidden="true"
83
- className={cn("[&>svg]:size-3.5", className)}
97
+ className={cn('[&>svg]:h-4 [&>svg]:w-4', className)}
98
+ role="presentation"
84
99
  {...props}
85
100
  >
86
101
  {children ?? <ChevronRight />}
87
102
  </li>
88
- )
89
- BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
103
+ );
104
+ BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
90
105
 
91
106
  const BreadcrumbEllipsis = ({
92
107
  className,
93
108
  ...props
94
- }: React.ComponentProps<"span">) => (
109
+ }: React.ComponentProps<'span'>) => (
95
110
  <span
96
- role="presentation"
97
111
  aria-hidden="true"
98
- className={cn("flex h-9 w-9 items-center justify-center", className)}
112
+ className={cn('flex h-9 w-9 items-center justify-center', className)}
113
+ role="presentation"
99
114
  {...props}
100
115
  >
101
116
  <MoreHorizontal className="h-4 w-4" />
102
117
  <span className="sr-only">More</span>
103
118
  </span>
104
- )
105
- BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
119
+ );
120
+ BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis';
106
121
 
107
122
  export {
108
123
  Breadcrumb,
@@ -112,4 +127,4 @@ export {
112
127
  BreadcrumbPage,
113
128
  BreadcrumbSeparator,
114
129
  BreadcrumbEllipsis,
115
- }
130
+ };
@@ -1,85 +1,82 @@
1
- import * as React from "react"
2
- import { Slot } from "@radix-ui/react-slot"
3
- import { cva, type VariantProps } from "class-variance-authority"
1
+ import { Slot } from '@radix-ui/react-slot';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { Loader2 } from 'lucide-react';
4
+ import * as React from 'react';
4
5
 
5
- import { cn } from "../util"
6
+ import { cn } from '../src/utils';
6
7
 
7
- // Define all variants
8
- const variant = {
9
- primary: "bg-primary text-primary-fg sm:hover:bg-primary-hover font-nav whitespace-nowrap not-typography",
10
- secondary: "bg-secondary text-secondary-fg sm:hover:bg-secondary-hover font-nav whitespace-nowrap not-typography",
11
- outline: "text-foreground bg-background border border-muted-4 sm:hover:bg-level-1 sm:hover:text-accent sm:hover:border-accent font-nav whitespace-nowrap not-typography",
12
- destructive: "bg-destructive text-destructive-fg font-sans whitespace-nowrap sm:hover:bg-destructive-hover",
13
- ghost: "text-foreground sm:hover:bg-level-1 sm:hover:text-accent whitespace-nowrap font-sans ",
14
- link: "text-foreground sm:hover:text-muted-1 font-sans ",
15
- linkFG: "text-foreground sm:hover:text-muted-1 font-sans ", // marker to style nav as regular link
16
- linkMuted: "text-muted-1 sm:hover:text-foreground font-sans ",
17
- }
18
-
19
- // Define all sizes
20
- const size = {
21
- link: '',
22
- xs: "h-8 px-2 text-xs",
23
- sm: "h-9 px-3 text-xs",
24
- square: 'h-9 py-2 px-2 text-sm aspect-square',
25
- default: "h-9 py-2 px-4 text-sm md:text-base font-semibold min-w-0",
26
- lg: "h-10 px-8 text-sm md:text-base font-semibold min-w-0 md:min-w-[260px] lg:min-w-[300px]",
27
- icon: "h-10 w-10",
28
- }
29
-
30
- // Define rounded options
31
- const rounded = {
32
- full: 'rounded-full',
33
- sm: 'rounded-sm',
34
- md: 'rounded-md',
35
- lg: 'rounded-lg',
36
- xl: 'rounded-xl',
37
- none: ''
38
- }
39
-
40
- // Updated buttonVariants with the latest Shadcn patterns
41
8
  const buttonVariants = cva(
42
- "inline-flex items-center justify-center font-medium ring-offset-background transition-colors " +
43
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 " +
44
- "disabled:opacity-50 disabled:pointer-events-none",
9
+ 'inline-flex items-center justify-center gap-2 border border-transparent text-sm font-semibold transition-colors focus-visible:ring-1 focus-visible:ring-gray-50 focus-visible:outline-hidden focus-visible:ring-inset',
45
10
  {
46
11
  variants: {
47
- variant,
48
- size,
49
- rounded
12
+ variant: {
13
+ default:
14
+ 'bg-brand hover:bg-brand-500 text-black disabled:bg-gray-800 disabled:text-gray-400 disabled:opacity-60',
15
+ destructive:
16
+ 'disabled:text-text-secondary bg-red-500 text-white hover:bg-red-500/90 disabled:bg-gray-600',
17
+ outline:
18
+ 'border border-gray-600 bg-transparent text-white hover:border-gray-400 hover:bg-gray-900 hover:text-white',
19
+ tertiary:
20
+ 'bg-transparent text-gray-300 hover:bg-gray-900 hover:text-white',
21
+ link: 'text-white underline-offset-4 hover:underline',
22
+ },
23
+ size: {
24
+ default: 'h-[50px] px-8 text-sm',
25
+ xs: 'h-[34px] gap-1.5 px-3 text-xs',
26
+ sm: 'h-[36px] gap-1.5 px-3 text-xs',
27
+ md: 'h-[40px] gap-1.5 px-3 text-xs',
28
+ lg: 'h-[48px] gap-2 px-2 text-sm',
29
+ icon: 'h-9 w-9',
30
+ auto: 'h-auto p-4',
31
+ },
32
+ rounded: {
33
+ full: 'rounded-full',
34
+ lg: 'rounded-lg',
35
+ },
50
36
  },
51
37
  defaultVariants: {
52
- variant: "primary",
53
- size: "default",
54
- rounded: 'md'
38
+ variant: 'default',
39
+ size: 'default',
40
+ rounded: 'full',
55
41
  },
56
- }
57
- )
42
+ },
43
+ );
58
44
 
59
- interface ButtonProps extends
60
- React.ButtonHTMLAttributes<HTMLButtonElement>,
61
- VariantProps<typeof buttonVariants>
62
- {
63
- asChild?: boolean
45
+ export interface ButtonProps
46
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
47
+ VariantProps<typeof buttonVariants> {
48
+ asChild?: boolean;
49
+ isLoading?: boolean;
50
+ ref?: React.RefObject<HTMLButtonElement>;
64
51
  }
65
52
 
66
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
67
- ({ className, variant, size, rounded, asChild = false, ...props }, ref) => {
68
- const Comp = asChild ? Slot : "button"
69
- return (
70
- <Comp
71
- className={cn(buttonVariants({ variant, size, rounded }), className )}
72
- ref={ref}
73
- {...props}
74
- />
75
- )
76
- }
77
- )
53
+ const Button = ({
54
+ className,
55
+ variant,
56
+ size,
57
+ rounded,
58
+ asChild = false,
59
+ isLoading = false,
60
+ ref,
61
+ ...props
62
+ }: ButtonProps) => {
63
+ const Comp = asChild ? Slot : 'button';
64
+ return (
65
+ <Comp
66
+ className={cn(buttonVariants({ variant, size, className }))}
67
+ ref={ref}
68
+ {...props}
69
+ >
70
+ {isLoading ? (
71
+ <Loader2
72
+ className={cn('h-4 w-4 animate-spin', size !== 'icon' && 'mr-2')}
73
+ />
74
+ ) : null}
75
+ {isLoading && size === 'icon' ? null : props.children}
76
+ </Comp>
77
+ );
78
+ };
78
79
 
79
- Button.displayName = "Button"
80
+ Button.displayName = 'Button';
80
81
 
81
- export {
82
- Button as default,
83
- type ButtonProps,
84
- buttonVariants,
85
- }
82
+ export { Button, buttonVariants };
@@ -1,83 +1,97 @@
1
- 'use client'
2
- import * as React from "react"
1
+ import * as React from 'react';
3
2
 
4
- import { cn } from "../util"
3
+ import { cn } from '../src/utils';
5
4
 
6
- const Card = React.forwardRef<
7
- HTMLDivElement,
8
- React.HTMLAttributes<HTMLDivElement>
9
- >(({ className, ...props }, ref) => {
10
- return(
11
- <div
12
- ref={ref}
13
- className={cn(
14
- "rounded-lg border overflow-hidden shadow-sm",
15
- className
16
- )}
17
- {...props}
18
- />
19
- )
20
- })
5
+ type CardProps = React.HTMLAttributes<HTMLDivElement> & {
6
+ ref?: React.RefObject<HTMLDivElement>;
7
+ };
21
8
 
22
- Card.displayName = "Card"
23
-
24
- const CardHeader = React.forwardRef<
25
- HTMLDivElement,
26
- React.HTMLAttributes<HTMLDivElement>
27
- >(({ className, ...props }, ref) => (
9
+ const Card = ({ className, ref, ...props }: CardProps) => (
28
10
  <div
11
+ className={cn(
12
+ 'bg-card text-card-foreground rounded-lg border shadow-xs',
13
+ className,
14
+ )}
29
15
  ref={ref}
30
- className={cn("flex flex-col p-4 lg:p-5 xl:px-6 xl:py-5 border-b", className)}
31
16
  {...props}
32
17
  />
33
- ))
34
- CardHeader.displayName = "CardHeader"
18
+ );
19
+ Card.displayName = 'Card';
20
+
21
+ type CardHeaderProps = React.HTMLAttributes<HTMLDivElement> & {
22
+ ref?: React.RefObject<HTMLDivElement>;
23
+ };
35
24
 
36
- const CardTitle = React.forwardRef<
37
- HTMLParagraphElement,
38
- React.HTMLAttributes<HTMLHeadingElement>
39
- >(({ className, ...props }, ref) => (
40
- <h5
25
+ const CardHeader = ({ className, ref, ...props }: CardHeaderProps) => (
26
+ <div
27
+ className={cn('flex flex-col space-y-1.5 p-6', className)}
41
28
  ref={ref}
29
+ {...props}
30
+ />
31
+ );
32
+
33
+ CardHeader.displayName = 'CardHeader';
34
+
35
+ type CardTitleProps = React.HTMLAttributes<HTMLHeadingElement> & {
36
+ ref?: React.RefObject<HTMLHeadingElement>;
37
+ };
38
+
39
+ const CardTitle = ({ className, ref, ...props }: CardTitleProps) => (
40
+ <h3
42
41
  className={cn(
43
- "leading-none tracking-tight",
44
- className
42
+ 'text-2xl leading-none font-semibold tracking-tight',
43
+ className,
45
44
  )}
45
+ ref={ref}
46
46
  {...props}
47
47
  />
48
- ))
49
- CardTitle.displayName = "CardTitle"
48
+ );
49
+ CardTitle.displayName = 'CardTitle';
50
50
 
51
- const CardDescription = React.forwardRef<
52
- HTMLParagraphElement,
53
- React.HTMLAttributes<HTMLParagraphElement>
54
- >(({ className, ...props }, ref) => (
55
- <h6
51
+ type CardDescriptionProps = React.HTMLAttributes<HTMLParagraphElement> & {
52
+ ref?: React.RefObject<HTMLParagraphElement>;
53
+ };
54
+
55
+ const CardDescription = ({
56
+ className,
57
+ ref,
58
+ ...props
59
+ }: CardDescriptionProps) => (
60
+ <p
61
+ className={cn('text-text-secondary text-sm', className)}
56
62
  ref={ref}
57
- className={className}
58
63
  {...props}
59
64
  />
60
- ))
61
- CardDescription.displayName = "CardDescription"
65
+ );
66
+ CardDescription.displayName = 'CardDescription';
67
+
68
+ type CardContentProps = React.HTMLAttributes<HTMLDivElement> & {
69
+ ref?: React.RefObject<HTMLDivElement>;
70
+ };
71
+
72
+ const CardContent = ({ className, ref, ...props }: CardContentProps) => (
73
+ <div className={cn('p-6 pt-0', className)} ref={ref} {...props} />
74
+ );
75
+ CardContent.displayName = 'CardContent';
62
76
 
63
- const CardContent = React.forwardRef<
64
- HTMLDivElement,
65
- React.HTMLAttributes<HTMLDivElement>
66
- >(({ className, ...props }, ref) => (
67
- <div ref={ref} className={cn("p-6", className)} {...props} />
68
- ))
69
- CardContent.displayName = "CardContent"
77
+ type CardFooterProps = React.HTMLAttributes<HTMLDivElement> & {
78
+ ref?: React.RefObject<HTMLDivElement>;
79
+ };
70
80
 
71
- const CardFooter = React.forwardRef<
72
- HTMLDivElement,
73
- React.HTMLAttributes<HTMLDivElement>
74
- >(({ className, ...props }, ref) => (
81
+ const CardFooter = ({ className, ref, ...props }: CardFooterProps) => (
75
82
  <div
83
+ className={cn('flex items-center p-6 pt-0', className)}
76
84
  ref={ref}
77
- className={cn("flex items-center px-6 py-3 border-t", className)}
78
85
  {...props}
79
86
  />
80
- ))
81
- CardFooter.displayName = "CardFooter"
87
+ );
88
+ CardFooter.displayName = 'CardFooter';
82
89
 
83
- export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
90
+ export {
91
+ Card,
92
+ CardHeader,
93
+ CardFooter,
94
+ CardTitle,
95
+ CardDescription,
96
+ CardContent,
97
+ };
@@ -0,0 +1,87 @@
1
+ import { useTranslation } from '@hanzo_network/hanzo-i18n';
2
+ import * as React from 'react';
3
+
4
+ import { useCombinedRefs } from '../../hooks/use-combined-refs';
5
+ import { cn } from '../src/utils';
6
+ import { ChatInput } from './chat-input';
7
+
8
+ type ChatInputAreaProps = {
9
+ value: string;
10
+ onChange: (value: string) => void;
11
+ onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
12
+ onPaste?: (e: React.ClipboardEvent<HTMLTextAreaElement>) => void;
13
+ onSubmit: () => void;
14
+ disabled?: boolean;
15
+ autoFocus?: boolean;
16
+ isLoading?: boolean;
17
+ placeholder?: string;
18
+ topAddons?: React.ReactNode;
19
+ bottomAddons?: React.ReactNode;
20
+ textareaClassName?: string;
21
+ className?: string;
22
+ alternateElement?: React.ReactNode;
23
+ ref?: React.RefObject<HTMLTextAreaElement | null>;
24
+ };
25
+ export const ChatInputArea = ({
26
+ value,
27
+ onChange,
28
+ onPaste,
29
+ onKeyDown,
30
+ autoFocus,
31
+ onSubmit,
32
+ disabled,
33
+ isLoading,
34
+ placeholder,
35
+ topAddons,
36
+ bottomAddons,
37
+ textareaClassName,
38
+ alternateElement,
39
+ className,
40
+ ref,
41
+ }: ChatInputAreaProps) => {
42
+ const { t } = useTranslation();
43
+ const textareaRef = useCombinedRefs<HTMLTextAreaElement>(
44
+ ref as React.RefObject<HTMLTextAreaElement>,
45
+ );
46
+
47
+ return (
48
+ <div
49
+ className={cn(
50
+ 'bg-bg-secondary flex w-full max-w-full flex-col rounded-xl text-sm aria-disabled:cursor-not-allowed aria-disabled:opacity-50',
51
+ 'shadow-border-input focus-within:shadow-border-input-focus overflow-hidden shadow-[0_0_0_1px_currentColor] transition-shadow',
52
+ className,
53
+ )}
54
+ >
55
+ {topAddons}
56
+ <div
57
+ aria-disabled={disabled}
58
+ className="flex cursor-text flex-col aria-disabled:cursor-not-allowed"
59
+ onClick={(e) => {
60
+ if (e.target === e.currentTarget) {
61
+ textareaRef?.current?.focus();
62
+ }
63
+ }}
64
+ >
65
+ {alternateElement ? (
66
+ alternateElement
67
+ ) : (
68
+ <ChatInput
69
+ autoFocus={autoFocus}
70
+ className={textareaClassName}
71
+ disabled={disabled || isLoading}
72
+ onChange={(e) => onChange(e.target.value)}
73
+ onKeyDown={onKeyDown}
74
+ onPaste={onPaste}
75
+ onSend={onSubmit}
76
+ placeholder={placeholder ?? t('chat.sendMessagePlaceholder')}
77
+ ref={textareaRef}
78
+ value={value}
79
+ />
80
+ )}
81
+ {bottomAddons}
82
+ </div>
83
+ </div>
84
+ );
85
+ };
86
+
87
+ ChatInputArea.displayName = 'ChatInputArea';
@@ -0,0 +1,71 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '../src/utils';
4
+
5
+ export interface ChatInputProps
6
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
7
+ onSend?: () => void;
8
+ ref: React.RefObject<HTMLTextAreaElement | null>;
9
+ }
10
+
11
+ export const useAutoResizeTextarea = (
12
+ ref: React.RefObject<HTMLTextAreaElement | null>,
13
+ value: string | number | readonly string[] | undefined,
14
+ autoResize = true, //later to unify
15
+ ) => {
16
+ const textAreaRef = React.useRef<HTMLTextAreaElement>(null);
17
+
18
+ React.useImperativeHandle(ref, () => textAreaRef.current!);
19
+
20
+ React.useEffect(() => {
21
+ const ref = textAreaRef?.current;
22
+
23
+ const updateTextareaHeight = () => {
24
+ if (ref && autoResize) {
25
+ ref.style.height = 'auto';
26
+ ref.style.height = ref?.scrollHeight + 'px';
27
+ }
28
+ };
29
+
30
+ updateTextareaHeight();
31
+
32
+ ref?.addEventListener('input', updateTextareaHeight);
33
+ return () => ref?.removeEventListener('input', updateTextareaHeight);
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, [value]);
36
+
37
+ return { textAreaRef };
38
+ };
39
+
40
+ const ChatInputBase = ({
41
+ className,
42
+ onSend,
43
+ onKeyDown,
44
+ ref,
45
+ ...props
46
+ }: ChatInputProps) => {
47
+ const { textAreaRef } = useAutoResizeTextarea(ref, props.value);
48
+
49
+ return (
50
+ <textarea
51
+ className={cn(
52
+ 'placeholder:!text-text-placeholder flex max-h-[40vh] min-h-[80px] w-full resize-none overflow-y-auto border-none bg-transparent px-3 py-2 text-base leading-normal break-words focus:outline-hidden focus-visible:ring-0 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
53
+ className,
54
+ )}
55
+ id="chat-input"
56
+ onKeyDown={(event) => {
57
+ onKeyDown?.(event);
58
+ if (event.key === 'Enter' && !event.shiftKey) {
59
+ event.preventDefault();
60
+ onSend?.();
61
+ }
62
+ }}
63
+ ref={textAreaRef}
64
+ spellCheck={false}
65
+ {...props}
66
+ />
67
+ );
68
+ };
69
+
70
+ ChatInputBase.displayName = 'ChatInput';
71
+ export const ChatInput = React.memo(ChatInputBase);