ui
ui copied to clipboard
QUESTION: Change PopoverTrigger to Input or CommandInput
I'm using Combobox, currently trigger is a button, i want replace trigger element with CommandInput or custom Input. Moving CommandInput out of Command seem like doesn't work.
Expection: My expectation is to have a component similar to Select, click on Input and enter and then there will be a suggestion option, it's similar to AutoComplete
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,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
const frameworks = [
{
value: "next.js",
label: "Next.js",
},
{
value: "sveltekit",
label: "SvelteKit",
},
{
value: "nuxt.js",
label: "Nuxt.js",
},
{
value: "remix",
label: "Remix",
},
{
value: "astro",
label: "Astro",
},
]
export function ComboboxDemo() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
return (
<Popover open={open} onOpenChange={setOpen}>
{/* <PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[200px] justify-between"
>
{value
? frameworks.find((framework) => framework.value === value)?.label
: "Select framework..."}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button> */}
{/* Replace with Input or CommandInput */}
<CommandInput placeholder="Search framework..." className="h-9" />
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks.map((framework) => (
<CommandItem
key={framework.value}
onSelect={(currentValue) => {
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
>
{framework.label}
<CheckIcon
className={cn(
"ml-auto h-4 w-4",
value === framework.value ? "opacity-100" : "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
)
}
Hey, you can find an example here 🤌 I hope it will helps you
https://www.armand-salle.fr/post/autocomplete-select-shadcn-ui
@armandsalle thank you, it's useful
@armandsalle been searching for this everywhere. Thank you so much for saving me hours of work ahah
Hey, you can find an example here 🤌 I hope it will helps you
https://www.armand-salle.fr/post/autocomplete-select-shadcn-ui
Originally posted by @armandsalle in https://github.com/shadcn-ui/ui/issues/1069#issuecomment-1657198760
@datnmqti were you able to move CommandInput under PopoverTrigger while using popover for multiselect. If so can you please share your code how you did that?
This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.
@zahidiqbalnbs FYI, I managed to use a normal Input outside a Popover and "bind" it to the Command & its CommandInput which lives inside the Popover.
The key is that the Input is inside a PopoverAnchor (not a PopoverTrigger) and that it relays all onKeyDown events, as well as its input to the Command & CommandInput (which is hidden in my case).
The onKeyDown & value relays are necessary for up/down navigation & filters of the Command to work.
I needed to wrap the Command inside a Popover b/c @armandsalle solution outlined above was giving me problems when being used inside a Sheet or Dialog. E.g. pressing Esc with the command open & focused, caused the whole dialog to close instead of just the command. Those issues seem to be solved by wrapping the Command inside a Popover.
My solution looks something like this:
type Option = Record<'value' | 'label', string> & Record<string, string>;
type AutocompleteProps = {
options: Option[];
openSuggestionsAtInputLength?: number;
};
export const Autocomplete: React.FC<AutocompleteProps> = ({ options, openSuggestionsAtInputLength = 3 }) => {
const [input, setInput] = useState('');
const cmdInputRef = useRef<HTMLInputElement>(null);
const [popoverIsOpen, setPopoverIsOpen] = useState(false);
const onInputValueChange = (e: ChangeEvent<HTMLInputElement>): void => {
const value = e.target.value;
setInput(value);
if (!popoverIsOpen && value.length >= openSuggestionsAtInputLength) {
setPopoverIsOpen(true);
}
};
/**
* Pass all keydown events from the input to the `CommandInput` to provide navigation using up/down arrow keys etc.
*/
const relayInputKeyDownToCommand = (e: React.KeyboardEvent<HTMLInputElement>): void => {
const { key, code, bubbles } = e;
cmdInputRef.current?.dispatchEvent(new KeyboardEvent('keydown', { key, code, bubbles }));
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault();
}
};
return (
<>
<Popover open={popoverIsOpen} onOpenChange={setPopoverIsOpen}>
<PopoverAnchor>
<Input onKeyDown={relayInputKeyDownToCommand} value={input} onChange={onInputValueChange} />
</PopoverAnchor>
<PopoverContent onOpenAutoFocus={e => e.preventDefault()} className="p-1">
<Command>
<CommandInput ref={cmdInputRef} value={input} wrapperClassName={tw`hidden`} />
<CommandList>
<CommandEmpty>{`No suggetsions...`}</CommandEmpty>
<CommandGroup>
{options.map(x => (
<CommandItem
key={x.value}
onSelect={() => { /* TODO */ }}
>
{x.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</>
);
};
FYI, this is just a POC implementation which seems to be working just fine for my use cases but it might need some more adjustments down the road.
Also, @zahidiqbalnbs, I'm not sure whether this actually matches your requirements or not. E.g. you mentioned something about multi select which is no use case for me.
hey all and @CombeeMike I find an easier solution to this problem by using Aria Kit https://ariakit.org/examples/combobox-radix with Radix 👍 works very well and need a lot less code