first commit

This commit is contained in:
dwindown
2025-10-09 12:52:41 +07:00
commit 0da6071eb3
205 changed files with 30980 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
"use client"
import * as React from "react"
import { X } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"
import { cn } from "@/lib/utils"
export interface Option {
label: string
value: string
}
interface MultiSelectProps {
options: Option[]
selected: string[]
onChange: (selected: string[]) => void
placeholder?: string
className?: string
disabled?: boolean
}
function MultiSelect({
options,
selected,
onChange,
placeholder = "Select items...",
className,
disabled = false,
}: MultiSelectProps) {
const [open, setOpen] = React.useState(false)
const [inputValue, setInputValue] = React.useState("")
const handleUnselect = (item: string) => {
onChange(selected.filter((i) => i !== item))
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
const input = e.target as HTMLInputElement
if (input.value === "") {
if (e.key === "Backspace") {
onChange(selected.slice(0, -1))
}
}
}
const selectables = options.filter((option) => !selected.includes(option.value))
// Handle creating new option when user types something not in the list
const handleSelect = (value: string) => {
if (value === inputValue && !options.find(option => option.value === value)) {
// Create new option
onChange([...selected, value])
} else {
onChange([...selected, value])
}
setInputValue("")
}
return (
<Command
onKeyDown={handleKeyDown}
className={cn("overflow-visible bg-transparent", className)}
>
<div className="group rounded-md border border-input px-3 py-2 text-sm ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
<div className="flex flex-wrap gap-1">
{selected.map((item) => {
const option = options.find((opt) => opt.value === item)
return (
<Badge key={item} variant="secondary">
{option?.label || item}
<button
className="ml-1 rounded-full outline-none ring-offset-background focus:ring-2 focus:ring-ring focus:ring-offset-2"
onKeyDown={(e) => {
if (e.key === "Enter") {
handleUnselect(item)
}
}}
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onClick={() => handleUnselect(item)}
disabled={disabled}
>
<X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
</button>
</Badge>
)
})}
<CommandInput
value={inputValue}
onValueChange={setInputValue}
onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
placeholder={placeholder}
disabled={disabled}
className="ml-2 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
/>
</div>
</div>
<div className="relative mt-2">
<CommandList>
{open && (inputValue.length > 0 || selectables.length > 0) ? (
<div className="absolute top-0 z-10 w-full rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in">
<CommandGroup className="h-full overflow-auto">
{/* Show option to create new category if input doesn't match existing options */}
{inputValue.length > 0 && !options.find(option =>
option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
option.value.toLowerCase().includes(inputValue.toLowerCase())
) && (
<CommandItem
key={inputValue}
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onSelect={() => {
handleSelect(inputValue)
setOpen(false)
}}
className="cursor-pointer"
>
Create "{inputValue}"
</CommandItem>
)}
{/* Show existing options that match the search */}
{selectables
.filter(option =>
option.label.toLowerCase().includes(inputValue.toLowerCase()) ||
option.value.toLowerCase().includes(inputValue.toLowerCase())
)
.map((option) => (
<CommandItem
key={option.value}
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onSelect={() => {
handleSelect(option.value)
setOpen(false)
}}
className="cursor-pointer"
>
{option.label}
</CommandItem>
))}
{selectables.length === 0 && inputValue.length === 0 && (
<CommandEmpty>No more options available.</CommandEmpty>
)}
</CommandGroup>
</div>
) : null}
</CommandList>
</div>
</Command>
)
}
export { MultiSelect }