@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.
- package/README.md +151 -151
- package/dist/index.d.ts +0 -0
- package/dist/index.js +55 -1
- package/package.json +7 -2
- package/src/registry/analytics/google-analytics.tsx +36 -39
- package/src/registry/analytics/google-tag-manager.tsx +62 -65
- package/src/registry/analytics/meta-pixel.tsx +44 -47
- package/src/registry/analytics/microsoft-clarity.tsx +31 -34
- package/src/registry/analytics/tiktok-pixel.tsx +34 -37
- package/src/registry/lib/utils.ts +0 -0
- package/src/registry/themes/v3/blue.css +157 -157
- package/src/registry/themes/v3/glass.css +153 -153
- package/src/registry/themes/v3/gray.css +157 -157
- package/src/registry/themes/v3/green.css +157 -157
- package/src/registry/themes/v3/neutral.css +157 -157
- package/src/registry/themes/v3/orange.css +157 -157
- package/src/registry/themes/v3/rose.css +157 -157
- package/src/registry/themes/v3/slate.css +157 -157
- package/src/registry/themes/v3/stone.css +157 -157
- package/src/registry/themes/v3/violet.css +186 -186
- package/src/registry/themes/v3/zinc.css +157 -157
- package/src/registry/themes/v4/blue.css +184 -184
- package/src/registry/themes/v4/glass.css +180 -180
- package/src/registry/themes/v4/gray.css +184 -184
- package/src/registry/themes/v4/green.css +184 -184
- package/src/registry/themes/v4/neutral.css +184 -184
- package/src/registry/themes/v4/orange.css +184 -184
- package/src/registry/themes/v4/rose.css +184 -184
- package/src/registry/themes/v4/slate.css +184 -184
- package/src/registry/themes/v4/stone.css +184 -184
- package/src/registry/themes/v4/violet.css +184 -184
- package/src/registry/themes/v4/zinc.css +184 -184
- package/src/registry/ui/accordion.tsx +164 -165
- package/src/registry/ui/alert-dialog.tsx +213 -214
- package/src/registry/ui/alert.tsx +73 -76
- package/src/registry/ui/aspect-ratio.tsx +44 -47
- package/src/registry/ui/avatar.tsx +96 -97
- package/src/registry/ui/badge.tsx +52 -55
- package/src/registry/ui/breadcrumb.tsx +147 -150
- package/src/registry/ui/button-group.tsx +64 -67
- package/src/registry/ui/button.tsx +71 -72
- package/src/registry/ui/calendar.tsx +514 -515
- package/src/registry/ui/card.tsx +88 -91
- package/src/registry/ui/carousel.tsx +214 -214
- package/src/registry/ui/chart.tsx +373 -373
- package/src/registry/ui/chatbot.tsx +86 -13
- package/src/registry/ui/checkbox.tsx +93 -94
- package/src/registry/ui/collapsible.tsx +107 -108
- package/src/registry/ui/combobox.tsx +171 -171
- package/src/registry/ui/command.tsx +300 -300
- package/src/registry/ui/container.tsx +44 -47
- package/src/registry/ui/context-menu.tsx +221 -221
- package/src/registry/ui/date-picker.tsx +228 -228
- package/src/registry/ui/dialog.tsx +269 -270
- package/src/registry/ui/drawer.tsx +10 -4
- package/src/registry/ui/dropdown-menu.tsx +529 -530
- package/src/registry/ui/empty-state.tsx +0 -2
- package/src/registry/ui/file-upload.tsx +0 -0
- package/src/registry/ui/floating-dock.tsx +0 -0
- package/src/registry/ui/form-field.tsx +91 -94
- package/src/registry/ui/google-analytics.tsx +38 -0
- package/src/registry/ui/google-tag-manager.tsx +64 -0
- package/src/registry/ui/hover-card.tsx +223 -223
- package/src/registry/ui/image.tsx +144 -147
- package/src/registry/ui/input-group.tsx +82 -85
- package/src/registry/ui/input.tsx +125 -125
- package/src/registry/ui/kbd.tsx +60 -63
- package/src/registry/ui/label.tsx +36 -37
- package/src/registry/ui/loading-spinner.tsx +108 -111
- package/src/registry/ui/map.tsx +0 -0
- package/src/registry/ui/marquee.tsx +2 -0
- package/src/registry/ui/menubar.tsx +246 -246
- package/src/registry/ui/meta-pixel.tsx +46 -0
- package/src/registry/ui/microsoft-clarity.tsx +33 -0
- package/src/registry/ui/native-select.tsx +49 -52
- package/src/registry/ui/otp-input.tsx +152 -155
- package/src/registry/ui/pagination.tsx +149 -152
- package/src/registry/ui/patterns.tsx +28 -0
- package/src/registry/ui/popover.tsx +226 -227
- package/src/registry/ui/progress.tsx +51 -52
- package/src/registry/ui/radio.tsx +99 -102
- package/src/registry/ui/resizable.tsx +314 -314
- package/src/registry/ui/scroll-animation.tsx +45 -0
- package/src/registry/ui/scroll-area.tsx +121 -122
- package/src/registry/ui/scroll-to-top.tsx +0 -0
- package/src/registry/ui/search.tsx +147 -150
- package/src/registry/ui/select.tsx +292 -293
- package/src/registry/ui/separator.tsx +46 -47
- package/src/registry/ui/sheet.tsx +6 -3
- package/src/registry/ui/sidebar.tsx +628 -628
- package/src/registry/ui/skeleton.tsx +26 -29
- package/src/registry/ui/slider.tsx +196 -197
- package/src/registry/ui/slot.tsx +69 -72
- package/src/registry/ui/star-rating.tsx +131 -134
- package/src/registry/ui/switch.tsx +72 -73
- package/src/registry/ui/table-of-contents.tsx +96 -96
- package/src/registry/ui/table.tsx +138 -139
- package/src/registry/ui/tabs.tsx +124 -125
- package/src/registry/ui/text.tsx +61 -64
- package/src/registry/ui/textarea.tsx +41 -42
- package/src/registry/ui/theme-switcher.tsx +66 -66
- package/src/registry/ui/tiktok-pixel.tsx +36 -0
- package/src/registry/ui/toast.tsx +97 -98
- package/src/registry/ui/toggle-group.tsx +129 -129
- package/src/registry/ui/toggle.tsx +72 -72
- package/src/registry/ui/tooltip.tsx +143 -144
- package/src/registry/ui/whatsapp.tsx +0 -0
|
@@ -1,300 +1,300 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { LuSearch } from "react-icons/lu"
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
|
7
|
-
|
|
8
|
-
interface CommandContextValue {
|
|
9
|
-
search: string
|
|
10
|
-
setSearch: (search: string) => void
|
|
11
|
-
selectedIndex: number
|
|
12
|
-
setSelectedIndex: (index: number) => void
|
|
13
|
-
items: string[]
|
|
14
|
-
registerItem: (value: string) => void
|
|
15
|
-
unregisterItem: (value: string) => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const CommandContext = React.createContext<CommandContextValue | null>(null)
|
|
19
|
-
|
|
20
|
-
function useCommandContext() {
|
|
21
|
-
const context = React.useContext(CommandContext)
|
|
22
|
-
if (!context) {
|
|
23
|
-
throw new Error("Command components must be used within Command")
|
|
24
|
-
}
|
|
25
|
-
return context
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface CommandProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
29
|
-
children: React.ReactNode
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const Command = React.forwardRef<HTMLDivElement, CommandProps>(
|
|
33
|
-
({ className, children, ...props }, ref) => {
|
|
34
|
-
const [search, setSearch] = React.useState("")
|
|
35
|
-
const [selectedIndex, setSelectedIndex] = React.useState(0)
|
|
36
|
-
const [items, setItems] = React.useState<string[]>([])
|
|
37
|
-
|
|
38
|
-
const registerItem = React.useCallback((value: string) => {
|
|
39
|
-
setItems((prev) => [...prev, value])
|
|
40
|
-
}, [])
|
|
41
|
-
|
|
42
|
-
const unregisterItem = React.useCallback((value: string) => {
|
|
43
|
-
setItems((prev) => prev.filter((item) => item !== value))
|
|
44
|
-
}, [])
|
|
45
|
-
|
|
46
|
-
// Handle keyboard navigation
|
|
47
|
-
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
48
|
-
switch (e.key) {
|
|
49
|
-
case "ArrowDown":
|
|
50
|
-
e.preventDefault()
|
|
51
|
-
setSelectedIndex((prev) => Math.min(prev + 1, items.length - 1))
|
|
52
|
-
break
|
|
53
|
-
case "ArrowUp":
|
|
54
|
-
e.preventDefault()
|
|
55
|
-
setSelectedIndex((prev) => Math.max(prev - 1, 0))
|
|
56
|
-
break
|
|
57
|
-
case "Enter":
|
|
58
|
-
e.preventDefault()
|
|
59
|
-
const selectedItem = document.querySelector('[data-selected="true"]') as HTMLElement
|
|
60
|
-
selectedItem?.click()
|
|
61
|
-
break
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<CommandContext.Provider
|
|
67
|
-
value={{
|
|
68
|
-
search,
|
|
69
|
-
setSearch,
|
|
70
|
-
selectedIndex,
|
|
71
|
-
setSelectedIndex,
|
|
72
|
-
items,
|
|
73
|
-
registerItem,
|
|
74
|
-
unregisterItem,
|
|
75
|
-
}}
|
|
76
|
-
>
|
|
77
|
-
<div
|
|
78
|
-
ref={ref}
|
|
79
|
-
className={cn(
|
|
80
|
-
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
81
|
-
className
|
|
82
|
-
)}
|
|
83
|
-
onKeyDown={handleKeyDown}
|
|
84
|
-
{...props}
|
|
85
|
-
>
|
|
86
|
-
{children}
|
|
87
|
-
</div>
|
|
88
|
-
</CommandContext.Provider>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
)
|
|
92
|
-
Command.displayName = "Command"
|
|
93
|
-
|
|
94
|
-
interface CommandDialogProps {
|
|
95
|
-
children: React.ReactNode
|
|
96
|
-
open?: boolean
|
|
97
|
-
onOpenChange?: (open: boolean) => void
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|
101
|
-
return (
|
|
102
|
-
<Dialog {...props}>
|
|
103
|
-
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
|
104
|
-
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground">
|
|
105
|
-
{children}
|
|
106
|
-
</Command>
|
|
107
|
-
</DialogContent>
|
|
108
|
-
</Dialog>
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
interface CommandInputProps extends React.InputHTMLAttributes<HTMLInputElement> { }
|
|
113
|
-
|
|
114
|
-
const CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(
|
|
115
|
-
({ className, ...props }, ref) => {
|
|
116
|
-
const { search, setSearch, setSelectedIndex } = useCommandContext()
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div className="flex items-center border-b px-3">
|
|
120
|
-
<LuSearch className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
121
|
-
<input
|
|
122
|
-
ref={ref}
|
|
123
|
-
value={search}
|
|
124
|
-
onChange={(e) => {
|
|
125
|
-
setSearch(e.target.value)
|
|
126
|
-
setSelectedIndex(0)
|
|
127
|
-
}}
|
|
128
|
-
className={cn(
|
|
129
|
-
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
130
|
-
className
|
|
131
|
-
)}
|
|
132
|
-
{...props}
|
|
133
|
-
/>
|
|
134
|
-
</div>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
)
|
|
138
|
-
CommandInput.displayName = "CommandInput"
|
|
139
|
-
|
|
140
|
-
interface CommandListProps extends React.HTMLAttributes<HTMLDivElement> { }
|
|
141
|
-
|
|
142
|
-
const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>(
|
|
143
|
-
({ className, ...props }, ref) => (
|
|
144
|
-
<div
|
|
145
|
-
ref={ref}
|
|
146
|
-
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
147
|
-
{...props}
|
|
148
|
-
/>
|
|
149
|
-
)
|
|
150
|
-
)
|
|
151
|
-
CommandList.displayName = "CommandList"
|
|
152
|
-
|
|
153
|
-
interface CommandEmptyProps extends React.HTMLAttributes<HTMLDivElement> { }
|
|
154
|
-
|
|
155
|
-
const CommandEmpty = React.forwardRef<HTMLDivElement, CommandEmptyProps>(
|
|
156
|
-
({ className, children, ...props }, ref) => {
|
|
157
|
-
const { search, items } = useCommandContext()
|
|
158
|
-
|
|
159
|
-
// Count how many items would be visible with the current search
|
|
160
|
-
const visibleCount = search
|
|
161
|
-
? items.filter(item => item.toLowerCase().includes(search.toLowerCase())).length
|
|
162
|
-
: items.length
|
|
163
|
-
|
|
164
|
-
// Only show empty message when user has typed something but no results match
|
|
165
|
-
if (!search || visibleCount > 0) {
|
|
166
|
-
return null
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<div
|
|
171
|
-
ref={ref}
|
|
172
|
-
className={cn("py-6 text-center text-sm", className)}
|
|
173
|
-
{...props}
|
|
174
|
-
>
|
|
175
|
-
{children}
|
|
176
|
-
</div>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
)
|
|
180
|
-
CommandEmpty.displayName = "CommandEmpty"
|
|
181
|
-
|
|
182
|
-
interface CommandGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
183
|
-
heading?: string
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const CommandGroup = React.forwardRef<HTMLDivElement, CommandGroupProps>(
|
|
187
|
-
({ className, heading, children, ...props }, ref) => (
|
|
188
|
-
<div
|
|
189
|
-
ref={ref}
|
|
190
|
-
className={cn(
|
|
191
|
-
"overflow-hidden p-1 text-foreground",
|
|
192
|
-
className
|
|
193
|
-
)}
|
|
194
|
-
{...props}
|
|
195
|
-
>
|
|
196
|
-
{heading && (
|
|
197
|
-
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
|
198
|
-
{heading}
|
|
199
|
-
</div>
|
|
200
|
-
)}
|
|
201
|
-
{children}
|
|
202
|
-
</div>
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
CommandGroup.displayName = "CommandGroup"
|
|
206
|
-
|
|
207
|
-
interface CommandSeparatorProps extends React.HTMLAttributes<HTMLDivElement> { }
|
|
208
|
-
|
|
209
|
-
const CommandSeparator = React.forwardRef<HTMLDivElement, CommandSeparatorProps>(
|
|
210
|
-
({ className, ...props }, ref) => (
|
|
211
|
-
<div
|
|
212
|
-
ref={ref}
|
|
213
|
-
className={cn("-mx-1 h-px bg-border", className)}
|
|
214
|
-
{...props}
|
|
215
|
-
/>
|
|
216
|
-
)
|
|
217
|
-
)
|
|
218
|
-
CommandSeparator.displayName = "CommandSeparator"
|
|
219
|
-
|
|
220
|
-
interface CommandItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
221
|
-
value?: string
|
|
222
|
-
onSelect?: () => void
|
|
223
|
-
disabled?: boolean
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(
|
|
227
|
-
({ className, value, onSelect, disabled, children, ...props }, ref) => {
|
|
228
|
-
const { search, selectedIndex, setSelectedIndex, registerItem, unregisterItem, items } = useCommandContext()
|
|
229
|
-
const itemValue = value || (typeof children === "string" ? children : "")
|
|
230
|
-
|
|
231
|
-
// Register/unregister on mount
|
|
232
|
-
React.useEffect(() => {
|
|
233
|
-
registerItem(itemValue)
|
|
234
|
-
return () => unregisterItem(itemValue)
|
|
235
|
-
}, [itemValue, registerItem, unregisterItem])
|
|
236
|
-
|
|
237
|
-
// Filter based on search
|
|
238
|
-
const isVisible = !search || itemValue.toLowerCase().includes(search.toLowerCase())
|
|
239
|
-
|
|
240
|
-
if (!isVisible) return null
|
|
241
|
-
|
|
242
|
-
const itemIndex = items.indexOf(itemValue)
|
|
243
|
-
const isSelected = itemIndex === selectedIndex
|
|
244
|
-
|
|
245
|
-
return (
|
|
246
|
-
<div
|
|
247
|
-
ref={ref}
|
|
248
|
-
role="option"
|
|
249
|
-
aria-selected={isSelected}
|
|
250
|
-
data-selected={isSelected}
|
|
251
|
-
data-disabled={disabled}
|
|
252
|
-
className={cn(
|
|
253
|
-
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
254
|
-
isSelected && "bg-accent text-accent-foreground",
|
|
255
|
-
disabled && "pointer-events-none opacity-50",
|
|
256
|
-
!disabled && "cursor-pointer",
|
|
257
|
-
className
|
|
258
|
-
)}
|
|
259
|
-
onClick={() => {
|
|
260
|
-
if (!disabled) {
|
|
261
|
-
onSelect?.()
|
|
262
|
-
}
|
|
263
|
-
}}
|
|
264
|
-
onMouseEnter={() => setSelectedIndex(itemIndex)}
|
|
265
|
-
{...props}
|
|
266
|
-
>
|
|
267
|
-
{children}
|
|
268
|
-
</div>
|
|
269
|
-
)
|
|
270
|
-
}
|
|
271
|
-
)
|
|
272
|
-
CommandItem.displayName = "CommandItem"
|
|
273
|
-
|
|
274
|
-
const CommandShortcut = ({
|
|
275
|
-
className,
|
|
276
|
-
...props
|
|
277
|
-
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
278
|
-
return (
|
|
279
|
-
<span
|
|
280
|
-
className={cn(
|
|
281
|
-
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
282
|
-
className
|
|
283
|
-
)}
|
|
284
|
-
{...props}
|
|
285
|
-
/>
|
|
286
|
-
)
|
|
287
|
-
}
|
|
288
|
-
CommandShortcut.displayName = "CommandShortcut"
|
|
289
|
-
|
|
290
|
-
export {
|
|
291
|
-
Command,
|
|
292
|
-
CommandDialog,
|
|
293
|
-
CommandInput,
|
|
294
|
-
CommandList,
|
|
295
|
-
CommandEmpty,
|
|
296
|
-
CommandGroup,
|
|
297
|
-
CommandItem,
|
|
298
|
-
CommandShortcut,
|
|
299
|
-
CommandSeparator,
|
|
300
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { LuSearch } from "react-icons/lu"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
|
7
|
+
|
|
8
|
+
interface CommandContextValue {
|
|
9
|
+
search: string
|
|
10
|
+
setSearch: (search: string) => void
|
|
11
|
+
selectedIndex: number
|
|
12
|
+
setSelectedIndex: (index: number) => void
|
|
13
|
+
items: string[]
|
|
14
|
+
registerItem: (value: string) => void
|
|
15
|
+
unregisterItem: (value: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CommandContext = React.createContext<CommandContextValue | null>(null)
|
|
19
|
+
|
|
20
|
+
function useCommandContext() {
|
|
21
|
+
const context = React.useContext(CommandContext)
|
|
22
|
+
if (!context) {
|
|
23
|
+
throw new Error("Command components must be used within Command")
|
|
24
|
+
}
|
|
25
|
+
return context
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface CommandProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
29
|
+
children: React.ReactNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const Command = React.forwardRef<HTMLDivElement, CommandProps>(
|
|
33
|
+
({ className, children, ...props }, ref) => {
|
|
34
|
+
const [search, setSearch] = React.useState("")
|
|
35
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0)
|
|
36
|
+
const [items, setItems] = React.useState<string[]>([])
|
|
37
|
+
|
|
38
|
+
const registerItem = React.useCallback((value: string) => {
|
|
39
|
+
setItems((prev) => [...prev, value])
|
|
40
|
+
}, [])
|
|
41
|
+
|
|
42
|
+
const unregisterItem = React.useCallback((value: string) => {
|
|
43
|
+
setItems((prev) => prev.filter((item) => item !== value))
|
|
44
|
+
}, [])
|
|
45
|
+
|
|
46
|
+
// Handle keyboard navigation
|
|
47
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
48
|
+
switch (e.key) {
|
|
49
|
+
case "ArrowDown":
|
|
50
|
+
e.preventDefault()
|
|
51
|
+
setSelectedIndex((prev) => Math.min(prev + 1, items.length - 1))
|
|
52
|
+
break
|
|
53
|
+
case "ArrowUp":
|
|
54
|
+
e.preventDefault()
|
|
55
|
+
setSelectedIndex((prev) => Math.max(prev - 1, 0))
|
|
56
|
+
break
|
|
57
|
+
case "Enter":
|
|
58
|
+
e.preventDefault()
|
|
59
|
+
const selectedItem = document.querySelector('[data-selected="true"]') as HTMLElement
|
|
60
|
+
selectedItem?.click()
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<CommandContext.Provider
|
|
67
|
+
value={{
|
|
68
|
+
search,
|
|
69
|
+
setSearch,
|
|
70
|
+
selectedIndex,
|
|
71
|
+
setSelectedIndex,
|
|
72
|
+
items,
|
|
73
|
+
registerItem,
|
|
74
|
+
unregisterItem,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
ref={ref}
|
|
79
|
+
className={cn(
|
|
80
|
+
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
|
81
|
+
className
|
|
82
|
+
)}
|
|
83
|
+
onKeyDown={handleKeyDown}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
{children}
|
|
87
|
+
</div>
|
|
88
|
+
</CommandContext.Provider>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
Command.displayName = "Command"
|
|
93
|
+
|
|
94
|
+
interface CommandDialogProps {
|
|
95
|
+
children: React.ReactNode
|
|
96
|
+
open?: boolean
|
|
97
|
+
onOpenChange?: (open: boolean) => void
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|
101
|
+
return (
|
|
102
|
+
<Dialog {...props}>
|
|
103
|
+
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
|
104
|
+
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground">
|
|
105
|
+
{children}
|
|
106
|
+
</Command>
|
|
107
|
+
</DialogContent>
|
|
108
|
+
</Dialog>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface CommandInputProps extends React.InputHTMLAttributes<HTMLInputElement> { }
|
|
113
|
+
|
|
114
|
+
const CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(
|
|
115
|
+
({ className, ...props }, ref) => {
|
|
116
|
+
const { search, setSearch, setSelectedIndex } = useCommandContext()
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className="flex items-center border-b px-3">
|
|
120
|
+
<LuSearch className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
|
121
|
+
<input
|
|
122
|
+
ref={ref}
|
|
123
|
+
value={search}
|
|
124
|
+
onChange={(e) => {
|
|
125
|
+
setSearch(e.target.value)
|
|
126
|
+
setSelectedIndex(0)
|
|
127
|
+
}}
|
|
128
|
+
className={cn(
|
|
129
|
+
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
130
|
+
className
|
|
131
|
+
)}
|
|
132
|
+
{...props}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
CommandInput.displayName = "CommandInput"
|
|
139
|
+
|
|
140
|
+
interface CommandListProps extends React.HTMLAttributes<HTMLDivElement> { }
|
|
141
|
+
|
|
142
|
+
const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>(
|
|
143
|
+
({ className, ...props }, ref) => (
|
|
144
|
+
<div
|
|
145
|
+
ref={ref}
|
|
146
|
+
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
CommandList.displayName = "CommandList"
|
|
152
|
+
|
|
153
|
+
interface CommandEmptyProps extends React.HTMLAttributes<HTMLDivElement> { }
|
|
154
|
+
|
|
155
|
+
const CommandEmpty = React.forwardRef<HTMLDivElement, CommandEmptyProps>(
|
|
156
|
+
({ className, children, ...props }, ref) => {
|
|
157
|
+
const { search, items } = useCommandContext()
|
|
158
|
+
|
|
159
|
+
// Count how many items would be visible with the current search
|
|
160
|
+
const visibleCount = search
|
|
161
|
+
? items.filter(item => item.toLowerCase().includes(search.toLowerCase())).length
|
|
162
|
+
: items.length
|
|
163
|
+
|
|
164
|
+
// Only show empty message when user has typed something but no results match
|
|
165
|
+
if (!search || visibleCount > 0) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<div
|
|
171
|
+
ref={ref}
|
|
172
|
+
className={cn("py-6 text-center text-sm", className)}
|
|
173
|
+
{...props}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
CommandEmpty.displayName = "CommandEmpty"
|
|
181
|
+
|
|
182
|
+
interface CommandGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
183
|
+
heading?: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const CommandGroup = React.forwardRef<HTMLDivElement, CommandGroupProps>(
|
|
187
|
+
({ className, heading, children, ...props }, ref) => (
|
|
188
|
+
<div
|
|
189
|
+
ref={ref}
|
|
190
|
+
className={cn(
|
|
191
|
+
"overflow-hidden p-1 text-foreground",
|
|
192
|
+
className
|
|
193
|
+
)}
|
|
194
|
+
{...props}
|
|
195
|
+
>
|
|
196
|
+
{heading && (
|
|
197
|
+
<div className="px-2 py-1.5 text-xs font-medium text-muted-foreground">
|
|
198
|
+
{heading}
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
{children}
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
CommandGroup.displayName = "CommandGroup"
|
|
206
|
+
|
|
207
|
+
interface CommandSeparatorProps extends React.HTMLAttributes<HTMLDivElement> { }
|
|
208
|
+
|
|
209
|
+
const CommandSeparator = React.forwardRef<HTMLDivElement, CommandSeparatorProps>(
|
|
210
|
+
({ className, ...props }, ref) => (
|
|
211
|
+
<div
|
|
212
|
+
ref={ref}
|
|
213
|
+
className={cn("-mx-1 h-px bg-border", className)}
|
|
214
|
+
{...props}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
CommandSeparator.displayName = "CommandSeparator"
|
|
219
|
+
|
|
220
|
+
interface CommandItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
221
|
+
value?: string
|
|
222
|
+
onSelect?: () => void
|
|
223
|
+
disabled?: boolean
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(
|
|
227
|
+
({ className, value, onSelect, disabled, children, ...props }, ref) => {
|
|
228
|
+
const { search, selectedIndex, setSelectedIndex, registerItem, unregisterItem, items } = useCommandContext()
|
|
229
|
+
const itemValue = value || (typeof children === "string" ? children : "")
|
|
230
|
+
|
|
231
|
+
// Register/unregister on mount
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
registerItem(itemValue)
|
|
234
|
+
return () => unregisterItem(itemValue)
|
|
235
|
+
}, [itemValue, registerItem, unregisterItem])
|
|
236
|
+
|
|
237
|
+
// Filter based on search
|
|
238
|
+
const isVisible = !search || itemValue.toLowerCase().includes(search.toLowerCase())
|
|
239
|
+
|
|
240
|
+
if (!isVisible) return null
|
|
241
|
+
|
|
242
|
+
const itemIndex = items.indexOf(itemValue)
|
|
243
|
+
const isSelected = itemIndex === selectedIndex
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div
|
|
247
|
+
ref={ref}
|
|
248
|
+
role="option"
|
|
249
|
+
aria-selected={isSelected}
|
|
250
|
+
data-selected={isSelected}
|
|
251
|
+
data-disabled={disabled}
|
|
252
|
+
className={cn(
|
|
253
|
+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none",
|
|
254
|
+
isSelected && "bg-accent text-accent-foreground",
|
|
255
|
+
disabled && "pointer-events-none opacity-50",
|
|
256
|
+
!disabled && "cursor-pointer",
|
|
257
|
+
className
|
|
258
|
+
)}
|
|
259
|
+
onClick={() => {
|
|
260
|
+
if (!disabled) {
|
|
261
|
+
onSelect?.()
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
264
|
+
onMouseEnter={() => setSelectedIndex(itemIndex)}
|
|
265
|
+
{...props}
|
|
266
|
+
>
|
|
267
|
+
{children}
|
|
268
|
+
</div>
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
CommandItem.displayName = "CommandItem"
|
|
273
|
+
|
|
274
|
+
const CommandShortcut = ({
|
|
275
|
+
className,
|
|
276
|
+
...props
|
|
277
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
278
|
+
return (
|
|
279
|
+
<span
|
|
280
|
+
className={cn(
|
|
281
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
282
|
+
className
|
|
283
|
+
)}
|
|
284
|
+
{...props}
|
|
285
|
+
/>
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
CommandShortcut.displayName = "CommandShortcut"
|
|
289
|
+
|
|
290
|
+
export {
|
|
291
|
+
Command,
|
|
292
|
+
CommandDialog,
|
|
293
|
+
CommandInput,
|
|
294
|
+
CommandList,
|
|
295
|
+
CommandEmpty,
|
|
296
|
+
CommandGroup,
|
|
297
|
+
CommandItem,
|
|
298
|
+
CommandShortcut,
|
|
299
|
+
CommandSeparator,
|
|
300
|
+
}
|