@srcroot/ui 0.0.54 → 0.0.56

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 (107) hide show
  1. package/README.md +151 -151
  2. package/dist/index.d.ts +0 -0
  3. package/dist/index.js +55 -1
  4. package/package.json +7 -2
  5. package/src/registry/analytics/google-analytics.tsx +36 -39
  6. package/src/registry/analytics/google-tag-manager.tsx +62 -65
  7. package/src/registry/analytics/meta-pixel.tsx +44 -47
  8. package/src/registry/analytics/microsoft-clarity.tsx +31 -34
  9. package/src/registry/analytics/tiktok-pixel.tsx +34 -37
  10. package/src/registry/lib/utils.ts +0 -0
  11. package/src/registry/themes/v3/blue.css +157 -157
  12. package/src/registry/themes/v3/glass.css +153 -153
  13. package/src/registry/themes/v3/gray.css +157 -157
  14. package/src/registry/themes/v3/green.css +157 -157
  15. package/src/registry/themes/v3/neutral.css +157 -157
  16. package/src/registry/themes/v3/orange.css +157 -157
  17. package/src/registry/themes/v3/rose.css +157 -157
  18. package/src/registry/themes/v3/slate.css +157 -157
  19. package/src/registry/themes/v3/stone.css +157 -157
  20. package/src/registry/themes/v3/violet.css +186 -186
  21. package/src/registry/themes/v3/zinc.css +157 -157
  22. package/src/registry/themes/v4/blue.css +184 -184
  23. package/src/registry/themes/v4/glass.css +180 -180
  24. package/src/registry/themes/v4/gray.css +184 -184
  25. package/src/registry/themes/v4/green.css +184 -184
  26. package/src/registry/themes/v4/neutral.css +184 -184
  27. package/src/registry/themes/v4/orange.css +184 -184
  28. package/src/registry/themes/v4/rose.css +184 -184
  29. package/src/registry/themes/v4/slate.css +184 -184
  30. package/src/registry/themes/v4/stone.css +184 -184
  31. package/src/registry/themes/v4/violet.css +184 -184
  32. package/src/registry/themes/v4/zinc.css +184 -184
  33. package/src/registry/ui/accordion.tsx +164 -165
  34. package/src/registry/ui/alert-dialog.tsx +213 -214
  35. package/src/registry/ui/alert.tsx +73 -76
  36. package/src/registry/ui/aspect-ratio.tsx +44 -47
  37. package/src/registry/ui/avatar.tsx +96 -97
  38. package/src/registry/ui/badge.tsx +52 -55
  39. package/src/registry/ui/breadcrumb.tsx +147 -150
  40. package/src/registry/ui/button-group.tsx +64 -67
  41. package/src/registry/ui/button.tsx +71 -72
  42. package/src/registry/ui/calendar.tsx +514 -515
  43. package/src/registry/ui/card.tsx +88 -91
  44. package/src/registry/ui/carousel.tsx +214 -214
  45. package/src/registry/ui/chart.tsx +373 -373
  46. package/src/registry/ui/chatbot.tsx +86 -13
  47. package/src/registry/ui/checkbox.tsx +93 -94
  48. package/src/registry/ui/collapsible.tsx +107 -108
  49. package/src/registry/ui/combobox.tsx +171 -171
  50. package/src/registry/ui/command.tsx +300 -300
  51. package/src/registry/ui/container.tsx +44 -47
  52. package/src/registry/ui/context-menu.tsx +221 -221
  53. package/src/registry/ui/date-picker.tsx +228 -228
  54. package/src/registry/ui/dialog.tsx +269 -270
  55. package/src/registry/ui/drawer.tsx +10 -4
  56. package/src/registry/ui/dropdown-menu.tsx +529 -530
  57. package/src/registry/ui/empty-state.tsx +0 -2
  58. package/src/registry/ui/file-upload.tsx +0 -0
  59. package/src/registry/ui/floating-dock.tsx +0 -0
  60. package/src/registry/ui/form-field.tsx +91 -94
  61. package/src/registry/ui/google-analytics.tsx +38 -0
  62. package/src/registry/ui/google-tag-manager.tsx +64 -0
  63. package/src/registry/ui/hover-card.tsx +223 -223
  64. package/src/registry/ui/image.tsx +144 -147
  65. package/src/registry/ui/input-group.tsx +82 -85
  66. package/src/registry/ui/input.tsx +125 -125
  67. package/src/registry/ui/kbd.tsx +60 -63
  68. package/src/registry/ui/label.tsx +36 -37
  69. package/src/registry/ui/loading-spinner.tsx +108 -111
  70. package/src/registry/ui/map.tsx +0 -0
  71. package/src/registry/ui/marquee.tsx +2 -0
  72. package/src/registry/ui/menubar.tsx +246 -246
  73. package/src/registry/ui/meta-pixel.tsx +46 -0
  74. package/src/registry/ui/microsoft-clarity.tsx +33 -0
  75. package/src/registry/ui/native-select.tsx +49 -52
  76. package/src/registry/ui/otp-input.tsx +152 -155
  77. package/src/registry/ui/pagination.tsx +149 -152
  78. package/src/registry/ui/patterns.tsx +28 -0
  79. package/src/registry/ui/popover.tsx +226 -227
  80. package/src/registry/ui/progress.tsx +51 -52
  81. package/src/registry/ui/radio.tsx +99 -102
  82. package/src/registry/ui/resizable.tsx +314 -314
  83. package/src/registry/ui/scroll-animation.tsx +45 -0
  84. package/src/registry/ui/scroll-area.tsx +121 -122
  85. package/src/registry/ui/scroll-to-top.tsx +0 -0
  86. package/src/registry/ui/search.tsx +147 -150
  87. package/src/registry/ui/select.tsx +292 -293
  88. package/src/registry/ui/separator.tsx +46 -47
  89. package/src/registry/ui/sheet.tsx +6 -3
  90. package/src/registry/ui/sidebar.tsx +628 -628
  91. package/src/registry/ui/skeleton.tsx +26 -29
  92. package/src/registry/ui/slider.tsx +196 -197
  93. package/src/registry/ui/slot.tsx +69 -72
  94. package/src/registry/ui/star-rating.tsx +131 -134
  95. package/src/registry/ui/switch.tsx +72 -73
  96. package/src/registry/ui/table-of-contents.tsx +96 -96
  97. package/src/registry/ui/table.tsx +138 -139
  98. package/src/registry/ui/tabs.tsx +124 -125
  99. package/src/registry/ui/text.tsx +61 -64
  100. package/src/registry/ui/textarea.tsx +41 -42
  101. package/src/registry/ui/theme-switcher.tsx +66 -66
  102. package/src/registry/ui/tiktok-pixel.tsx +36 -0
  103. package/src/registry/ui/toast.tsx +97 -98
  104. package/src/registry/ui/toggle-group.tsx +129 -129
  105. package/src/registry/ui/toggle.tsx +72 -72
  106. package/src/registry/ui/tooltip.tsx +143 -144
  107. package/src/registry/ui/whatsapp.tsx +0 -0
@@ -0,0 +1,45 @@
1
+ "use client"
2
+
3
+ import { ReactNode, useRef } from "react"
4
+ import gsap from "gsap"
5
+ import { useGSAP } from "@gsap/react"
6
+ import { ScrollTrigger } from "gsap/ScrollTrigger"
7
+
8
+ gsap.registerPlugin(ScrollTrigger)
9
+
10
+ interface ScrollAnimationProps {
11
+ children: ReactNode
12
+ className?: string
13
+ delay?: number
14
+ }
15
+
16
+ export function ScrollAnimation({ children, className = "", delay = 0 }: ScrollAnimationProps) {
17
+ const el = useRef<HTMLDivElement>(null)
18
+
19
+ useGSAP(() => {
20
+ gsap.fromTo(el.current,
21
+ {
22
+ opacity: 0,
23
+ y: 50
24
+ },
25
+ {
26
+ opacity: 1,
27
+ y: 0,
28
+ duration: 0.8,
29
+ delay: delay,
30
+ ease: "circ.out",
31
+ scrollTrigger: {
32
+ trigger: el.current,
33
+ start: "top 85%", // Trigger when top of element hits 85% of viewport height
34
+ toggleActions: "play none none reverse"
35
+ }
36
+ }
37
+ )
38
+ }, { scope: el })
39
+
40
+ return (
41
+ <div ref={el} className={className}>
42
+ {children}
43
+ </div>
44
+ )
45
+ }
@@ -1,122 +1,121 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
-
6
- interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
7
- /** Orientation of scrollbar */
8
- orientation?: "vertical" | "horizontal"
9
- /** Scrollbar size: "thin" (4px), "default" (8px), "thick" (12px) */
10
- scrollbarSize?: "thin" | "default" | "thick"
11
- /** Hide scrollbar until hover */
12
- hideScrollbar?: boolean
13
- }
14
-
15
- // CSS for custom scrollbar styling
16
- const scrollbarStyles = {
17
- thin: {
18
- width: "4px",
19
- height: "4px",
20
- },
21
- default: {
22
- width: "8px",
23
- height: "8px",
24
- },
25
- thick: {
26
- width: "12px",
27
- height: "12px",
28
- },
29
- }
30
-
31
- /**
32
- * ScrollArea - Custom scrollbar container
33
- *
34
- * Provides a styled scrollbar that is thin and consistent across browsers.
35
- * Supports customizable scrollbar size and orientation.
36
- */
37
- const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
38
- ({ className, children, orientation = "vertical", scrollbarSize = "thin", hideScrollbar = false, style, ...props }, ref) => {
39
- const sizes = scrollbarStyles[scrollbarSize]
40
-
41
- const scrollbarCSS: React.CSSProperties = {
42
- ...style,
43
- // Webkit browsers (Chrome, Safari, Edge)
44
- // @ts-ignore - CSS custom properties for scrollbar
45
- "--scrollbar-width": sizes.width,
46
- "--scrollbar-height": sizes.height,
47
- }
48
-
49
- return (
50
- <div
51
- ref={ref}
52
- className={cn(
53
- "relative",
54
- // Container overflow based on orientation
55
- orientation === "vertical" && "overflow-y-auto overflow-x-hidden",
56
- orientation === "horizontal" && "overflow-x-auto overflow-y-hidden",
57
- // Custom scrollbar classes
58
- "scrollbar-custom",
59
- hideScrollbar && "scrollbar-hide hover:scrollbar-show",
60
- className
61
- )}
62
- style={scrollbarCSS}
63
- {...props}
64
- >
65
- <style>{`
66
- .scrollbar-custom::-webkit-scrollbar {
67
- width: var(--scrollbar-width, 4px);
68
- height: var(--scrollbar-height, 4px);
69
- }
70
- .scrollbar-custom::-webkit-scrollbar-track {
71
- background: transparent;
72
- border-radius: 9999px;
73
- }
74
- .scrollbar-custom::-webkit-scrollbar-thumb {
75
- background: hsl(var(--muted-foreground) / 0.3);
76
- border-radius: 9999px;
77
- }
78
- .scrollbar-custom::-webkit-scrollbar-thumb:hover {
79
- background: hsl(var(--muted-foreground) / 0.5);
80
- }
81
- .scrollbar-custom {
82
- scrollbar-width: thin;
83
- scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
84
- }
85
- .scrollbar-hide::-webkit-scrollbar {
86
- opacity: 0;
87
- }
88
- .scrollbar-hide:hover::-webkit-scrollbar,
89
- .scrollbar-show::-webkit-scrollbar {
90
- opacity: 1;
91
- }
92
- `}</style>
93
- {children}
94
- </div>
95
- )
96
- }
97
- )
98
- ScrollArea.displayName = "ScrollArea"
99
-
100
- // ScrollBar component for explicit scrollbar styling reference (optional usage)
101
- interface ScrollBarProps extends React.HTMLAttributes<HTMLDivElement> {
102
- orientation?: "vertical" | "horizontal"
103
- }
104
-
105
- const ScrollBar = React.forwardRef<HTMLDivElement, ScrollBarProps>(
106
- ({ className, orientation = "vertical", ...props }, ref) => (
107
- <div
108
- ref={ref}
109
- className={cn(
110
- "flex touch-none select-none transition-colors",
111
- orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
112
- orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
113
- className
114
- )}
115
- {...props}
116
- />
117
- )
118
- )
119
- ScrollBar.displayName = "ScrollBar"
120
-
121
- export { ScrollArea, ScrollBar }
122
-
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ /** Orientation of scrollbar */
8
+ orientation?: "vertical" | "horizontal"
9
+ /** Scrollbar size: "thin" (4px), "default" (8px), "thick" (12px) */
10
+ scrollbarSize?: "thin" | "default" | "thick"
11
+ /** Hide scrollbar until hover */
12
+ hideScrollbar?: boolean
13
+ }
14
+
15
+ // CSS for custom scrollbar styling
16
+ const scrollbarStyles = {
17
+ thin: {
18
+ width: "4px",
19
+ height: "4px",
20
+ },
21
+ default: {
22
+ width: "8px",
23
+ height: "8px",
24
+ },
25
+ thick: {
26
+ width: "12px",
27
+ height: "12px",
28
+ },
29
+ }
30
+
31
+ /**
32
+ * ScrollArea - Custom scrollbar container
33
+ *
34
+ * Provides a styled scrollbar that is thin and consistent across browsers.
35
+ * Supports customizable scrollbar size and orientation.
36
+ */
37
+ const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
38
+ ({ className, children, orientation = "vertical", scrollbarSize = "thin", hideScrollbar = false, style, ...props }, ref) => {
39
+ const sizes = scrollbarStyles[scrollbarSize]
40
+
41
+ const scrollbarCSS: React.CSSProperties = {
42
+ ...style,
43
+ // Webkit browsers (Chrome, Safari, Edge)
44
+ // @ts-ignore - CSS custom properties for scrollbar
45
+ "--scrollbar-width": sizes.width,
46
+ "--scrollbar-height": sizes.height,
47
+ }
48
+
49
+ return (
50
+ <div
51
+ ref={ref}
52
+ className={cn(
53
+ "relative",
54
+ // Container overflow based on orientation
55
+ orientation === "vertical" && "overflow-y-auto overflow-x-hidden",
56
+ orientation === "horizontal" && "overflow-x-auto overflow-y-hidden",
57
+ // Custom scrollbar classes
58
+ "scrollbar-custom",
59
+ hideScrollbar && "scrollbar-hide hover:scrollbar-show",
60
+ className
61
+ )}
62
+ style={scrollbarCSS}
63
+ {...props}
64
+ >
65
+ <style>{`
66
+ .scrollbar-custom::-webkit-scrollbar {
67
+ width: var(--scrollbar-width, 4px);
68
+ height: var(--scrollbar-height, 4px);
69
+ }
70
+ .scrollbar-custom::-webkit-scrollbar-track {
71
+ background: transparent;
72
+ border-radius: 9999px;
73
+ }
74
+ .scrollbar-custom::-webkit-scrollbar-thumb {
75
+ background: hsl(var(--muted-foreground) / 0.3);
76
+ border-radius: 9999px;
77
+ }
78
+ .scrollbar-custom::-webkit-scrollbar-thumb:hover {
79
+ background: hsl(var(--muted-foreground) / 0.5);
80
+ }
81
+ .scrollbar-custom {
82
+ scrollbar-width: thin;
83
+ scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
84
+ }
85
+ .scrollbar-hide::-webkit-scrollbar {
86
+ opacity: 0;
87
+ }
88
+ .scrollbar-hide:hover::-webkit-scrollbar,
89
+ .scrollbar-show::-webkit-scrollbar {
90
+ opacity: 1;
91
+ }
92
+ `}</style>
93
+ {children}
94
+ </div>
95
+ )
96
+ }
97
+ )
98
+ ScrollArea.displayName = "ScrollArea"
99
+
100
+ // ScrollBar component for explicit scrollbar styling reference (optional usage)
101
+ interface ScrollBarProps extends React.HTMLAttributes<HTMLDivElement> {
102
+ orientation?: "vertical" | "horizontal"
103
+ }
104
+
105
+ const ScrollBar = React.forwardRef<HTMLDivElement, ScrollBarProps>(
106
+ ({ className, orientation = "vertical", ...props }, ref) => (
107
+ <div
108
+ ref={ref}
109
+ className={cn(
110
+ "flex touch-none select-none transition-colors",
111
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
112
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
113
+ className
114
+ )}
115
+ {...props}
116
+ />
117
+ )
118
+ )
119
+ ScrollBar.displayName = "ScrollBar"
120
+
121
+ export { ScrollArea, ScrollBar }
File without changes
@@ -1,150 +1,147 @@
1
- "use client"
2
-
3
- import * as React from "react"
4
- import { cn } from "@/lib/utils"
5
-
6
- interface SearchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> {
7
- /** Callback when search value changes */
8
- onSearch?: (value: string) => void
9
- /** Debounce delay in ms */
10
- debounceMs?: number
11
- /** Show clear button */
12
- showClear?: boolean
13
- /** Loading state */
14
- loading?: boolean
15
- }
16
-
17
- /**
18
- * Search input with optional debounce and clear button
19
- *
20
- * @example
21
- * <Search
22
- * placeholder="Search..."
23
- * onSearch={(value) => fetchResults(value)}
24
- * debounceMs={300}
25
- * />
26
- */
27
- const Search = React.forwardRef<HTMLInputElement, SearchProps>(
28
- (
29
- {
30
- className,
31
- onSearch,
32
- debounceMs = 0,
33
- showClear = true,
34
- loading,
35
- defaultValue = "",
36
- ...props
37
- },
38
- ref
39
- ) => {
40
- const [value, setValue] = React.useState(String(defaultValue))
41
- const debounceRef = React.useRef<NodeJS.Timeout | null>(null)
42
-
43
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
44
- const newValue = e.target.value
45
- setValue(newValue)
46
-
47
- if (debounceMs > 0) {
48
- if (debounceRef.current) clearTimeout(debounceRef.current)
49
- debounceRef.current = setTimeout(() => {
50
- onSearch?.(newValue)
51
- }, debounceMs)
52
- } else {
53
- onSearch?.(newValue)
54
- }
55
- }
56
-
57
- const handleClear = () => {
58
- setValue("")
59
- onSearch?.("")
60
- }
61
-
62
- const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
63
- if (e.key === "Escape") {
64
- handleClear()
65
- }
66
- }
67
-
68
- React.useEffect(() => {
69
- return () => {
70
- if (debounceRef.current) clearTimeout(debounceRef.current)
71
- }
72
- }, [])
73
-
74
- return (
75
- <div className={cn("relative", className)}>
76
- {/* Search Icon */}
77
- <svg
78
- className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
79
- fill="none"
80
- viewBox="0 0 24 24"
81
- stroke="currentColor"
82
- strokeWidth={2}
83
- >
84
- <path
85
- strokeLinecap="round"
86
- strokeLinejoin="round"
87
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
88
- />
89
- </svg>
90
-
91
- <input
92
- ref={ref}
93
- type="search"
94
- role="searchbox"
95
- value={value}
96
- onChange={handleChange}
97
- onKeyDown={handleKeyDown}
98
- className={cn(
99
- "flex h-10 w-full rounded-md border border-input bg-transparent pl-10 pr-10 py-2 text-sm shadow-sm transition-colors",
100
- "placeholder:text-muted-foreground",
101
- "focus:outline-none focus:ring-1 focus:ring-ring",
102
- "disabled:cursor-not-allowed disabled:opacity-50",
103
- "[&::-webkit-search-cancel-button]:appearance-none"
104
- )}
105
- {...props}
106
- />
107
-
108
- {/* Loading or Clear */}
109
- <div className="absolute right-3 top-1/2 -translate-y-1/2">
110
- {loading ? (
111
- <svg
112
- className="h-4 w-4 animate-spin text-muted-foreground"
113
- fill="none"
114
- viewBox="0 0 24 24"
115
- >
116
- <circle
117
- className="opacity-25"
118
- cx="12"
119
- cy="12"
120
- r="10"
121
- stroke="currentColor"
122
- strokeWidth="4"
123
- />
124
- <path
125
- className="opacity-75"
126
- fill="currentColor"
127
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
128
- />
129
- </svg>
130
- ) : showClear && value ? (
131
- <button
132
- type="button"
133
- onClick={handleClear}
134
- className="text-muted-foreground hover:text-foreground"
135
- aria-label="Clear search"
136
- >
137
- <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
138
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
139
- </svg>
140
- </button>
141
- ) : null}
142
- </div>
143
- </div>
144
- )
145
- }
146
- )
147
- Search.displayName = "Search"
148
-
149
- export { Search }
150
-
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface SearchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> {
5
+ /** Callback when search value changes */
6
+ onSearch?: (value: string) => void
7
+ /** Debounce delay in ms */
8
+ debounceMs?: number
9
+ /** Show clear button */
10
+ showClear?: boolean
11
+ /** Loading state */
12
+ loading?: boolean
13
+ }
14
+
15
+ /**
16
+ * Search input with optional debounce and clear button
17
+ *
18
+ * @example
19
+ * <Search
20
+ * placeholder="Search..."
21
+ * onSearch={(value) => fetchResults(value)}
22
+ * debounceMs={300}
23
+ * />
24
+ */
25
+ const Search = React.forwardRef<HTMLInputElement, SearchProps>(
26
+ (
27
+ {
28
+ className,
29
+ onSearch,
30
+ debounceMs = 0,
31
+ showClear = true,
32
+ loading,
33
+ defaultValue = "",
34
+ ...props
35
+ },
36
+ ref
37
+ ) => {
38
+ const [value, setValue] = React.useState(String(defaultValue))
39
+ const debounceRef = React.useRef<NodeJS.Timeout | null>(null)
40
+
41
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
42
+ const newValue = e.target.value
43
+ setValue(newValue)
44
+
45
+ if (debounceMs > 0) {
46
+ if (debounceRef.current) clearTimeout(debounceRef.current)
47
+ debounceRef.current = setTimeout(() => {
48
+ onSearch?.(newValue)
49
+ }, debounceMs)
50
+ } else {
51
+ onSearch?.(newValue)
52
+ }
53
+ }
54
+
55
+ const handleClear = () => {
56
+ setValue("")
57
+ onSearch?.("")
58
+ }
59
+
60
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
61
+ if (e.key === "Escape") {
62
+ handleClear()
63
+ }
64
+ }
65
+
66
+ React.useEffect(() => {
67
+ return () => {
68
+ if (debounceRef.current) clearTimeout(debounceRef.current)
69
+ }
70
+ }, [])
71
+
72
+ return (
73
+ <div className={cn("relative", className)}>
74
+ {/* Search Icon */}
75
+ <svg
76
+ className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
77
+ fill="none"
78
+ viewBox="0 0 24 24"
79
+ stroke="currentColor"
80
+ strokeWidth={2}
81
+ >
82
+ <path
83
+ strokeLinecap="round"
84
+ strokeLinejoin="round"
85
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
86
+ />
87
+ </svg>
88
+
89
+ <input
90
+ ref={ref}
91
+ type="search"
92
+ role="searchbox"
93
+ value={value}
94
+ onChange={handleChange}
95
+ onKeyDown={handleKeyDown}
96
+ className={cn(
97
+ "flex h-10 w-full rounded-md border border-input bg-transparent pl-10 pr-10 py-2 text-sm shadow-sm transition-colors",
98
+ "placeholder:text-muted-foreground",
99
+ "focus:outline-none focus:ring-1 focus:ring-ring",
100
+ "disabled:cursor-not-allowed disabled:opacity-50",
101
+ "[&::-webkit-search-cancel-button]:appearance-none"
102
+ )}
103
+ {...props}
104
+ />
105
+
106
+ {/* Loading or Clear */}
107
+ <div className="absolute right-3 top-1/2 -translate-y-1/2">
108
+ {loading ? (
109
+ <svg
110
+ className="h-4 w-4 animate-spin text-muted-foreground"
111
+ fill="none"
112
+ viewBox="0 0 24 24"
113
+ >
114
+ <circle
115
+ className="opacity-25"
116
+ cx="12"
117
+ cy="12"
118
+ r="10"
119
+ stroke="currentColor"
120
+ strokeWidth="4"
121
+ />
122
+ <path
123
+ className="opacity-75"
124
+ fill="currentColor"
125
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
126
+ />
127
+ </svg>
128
+ ) : showClear && value ? (
129
+ <button
130
+ type="button"
131
+ onClick={handleClear}
132
+ className="text-muted-foreground hover:text-foreground"
133
+ aria-label="Clear search"
134
+ >
135
+ <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
136
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
137
+ </svg>
138
+ </button>
139
+ ) : null}
140
+ </div>
141
+ </div>
142
+ )
143
+ }
144
+ )
145
+ Search.displayName = "Search"
146
+
147
+ export { Search }