Skip to content

Commit

Permalink
[Feat]: Add generic CustomCombobox component with props for dynamic (#…
Browse files Browse the repository at this point in the history
…2983)

* feat: Add generic CustomCombobox component with props for dynamic behavio

* fix: resolve issue with PopoverContent and Command integration
  • Loading branch information
Innocent-Akim authored Sep 4, 2024
1 parent fa84bdc commit cf02d9b
Showing 1 changed file with 132 additions and 0 deletions.
132 changes: 132 additions & 0 deletions apps/web/lib/components/combobox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"use client"
import * as React from "react";
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { cn } from "lib/utils";
import { Button } from "@components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@components/ui/popover";

interface ComboboxProps<T> {
items: T[]
itemToString: (item: T) => string
itemToValue: (item: T) => string
placeholder?: string
buttonWidth?: string
popoverWidth?: string
commandInputHeight?: string
noResultsText?: string
onChangeValue?: (value: T | null) => void
className?: string
popoverClassName?: string
}
/**
*
*
* @export
* @template T
* @param {ComboboxProps<T>} {
* items,
* itemToString,
* itemToValue,
* placeholder = "Select item...",
* buttonWidth = "w-[200px]",
* commandInputHeight = "h-9",
* noResultsText = "No item found.",
* onChangeValue,
* className,
* popoverClassName
* }
* @return {*}
*/
export function CustomCombobox<T>({
items,
itemToString,
itemToValue,
placeholder = "Select item...",
buttonWidth = "w-[200px]",
commandInputHeight = "h-9",
noResultsText = "No item found.",
onChangeValue,
className,
popoverClassName
}: ComboboxProps<T>) {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState<T | null>(null)
const [popoverWidth, setPopoverWidth] = React.useState<number | null>(null);
const triggerRef = React.useRef<HTMLButtonElement>(null);

const handleSelect = (currentValue: string) => {
const selectedItem = items.find(item => itemToValue(item) === currentValue) || null
setValue(selectedItem)
setOpen(false)
if (onChangeValue) {
onChangeValue(selectedItem)
}
}

React.useEffect(() => {
if (triggerRef.current) {
setPopoverWidth(triggerRef.current.offsetWidth);
}
}, [triggerRef.current]);
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
ref={triggerRef}
variant="outline"
role="combobox"
aria-expanded={open}
className={cn(
'w-full justify-between text-left font-normal h-10 rounded-lg dark:bg-dark--theme-light',
buttonWidth
)}
>
{value ? itemToString(value) : placeholder}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className={cn(
'w-full max-w-full max-h-[80vh] border border-transparent dark:bg-dark--theme-light',
popoverClassName
)}
style={{ width: popoverWidth || 'auto', overflow: 'auto' }}>
<Command className="dark:bg-dark--theme-light">
<CommandInput placeholder="Search item..." className={commandInputHeight} />
<CommandList>
<CommandEmpty>{noResultsText}</CommandEmpty>
<CommandGroup>
{items.map((item) => (
<CommandItem
className="w-full dark:bg-dark--theme-light"
key={itemToValue(item)}
value={itemToValue(item)}
onSelect={() => handleSelect(itemToValue(item))}
>
{itemToString(item)}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
value && itemToValue(value) === itemToValue(item) ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}

0 comments on commit cf02d9b

Please sign in to comment.