Render CommandList in Popover only #221
-
What is the proper way to only display the CommandList in a popover and not the entire Command? Looking to build an AutoComplete component instead of a DropDown component. Attempting to port the CommandList inside a Radix Popover, I am losing the functionality of command. While this preserves the up/down arrow functionality it seems like a work-around. Didn't know if there was a more "proper" way. I currently have the following:
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 9 replies
-
Hey, if you upgrade to |
Beta Was this translation helpful? Give feedback.
-
@msickpaler I have found a way to have it working with keyboard as well. Not beautiful, but working, except for a slight flicker after selecting an item. Modified from a version here This supports supplying items with value, label structure. It is also working in a ShadCN/Radix dialog. Using cmdk version 1.0.4. import { IconCheck } from '@tabler/icons-react';
import { Command as CommandPrimitive } from 'cmdk';
import { useEffect, useState } from 'react';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '~/components/ui/command';
import { cn } from '~/lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Skeleton } from '../ui/skeleton';
export type Option = Record<'value' | 'label', string> & Record<string, string>;
type AutoCompleteProps = {
id: string;
options: Option[];
emptyMessage: string;
value?: Option;
onValueChange?: (value: Option) => void;
isLoading?: boolean;
disabled?: boolean;
placeholder?: string;
};
export const AutoComplete = ({
id,
options,
placeholder,
emptyMessage,
value,
onValueChange,
disabled,
isLoading = false,
}: AutoCompleteProps) => {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState<string | undefined>(undefined);
const [valueLocal, setValueLocal] = useState<Option | undefined>(value);
const [valueLocalBackup, setValueLocalBackup] = useState<Option | undefined>(
value,
);
useEffect(() => {
if (value) {
setValueLocal(value);
setValueLocalBackup(value);
setSearch(value.label);
}
}, [value]);
return (
<div className="flex items-center">
<Popover open={open}>
<Command loop>
<PopoverTrigger asChild>
<CommandInput
id={id}
value={search}
placeholder={placeholder}
disabled={disabled}
onValueChange={setSearch}
onKeyDown={(e) => setOpen(e.key !== 'Escape')}
onMouseDown={() => {
setSearch('');
setOpen(true);
setValueLocal(undefined);
}}
onFocus={() => {
setSearch('');
setValueLocal(undefined);
setOpen(true);
}}
onBlur={(e) => {
setTimeout(() => {
if (!e.relatedTarget?.hasAttribute('cmdk-list')) {
if (valueLocal) {
setTimeout(() => {
onValueChange?.(valueLocal);
}, 0);
setSearch(
valueLocal
? (options.find(
(option) => option.value === value?.value,
)?.label ?? '')
: '',
);
}
}
}, 0);
}}
></CommandInput>
</PopoverTrigger>
{!open && <CommandList aria-hidden="true" className="hidden" />}
<PopoverContent
asChild
align="start"
onOpenAutoFocus={(e) => e.preventDefault()}
onInteractOutside={(e) => {
if (
e.target instanceof Element &&
e.target.hasAttribute('cmdk-input')
) {
e.preventDefault();
} else {
setOpen(false);
if (value) {
setTimeout(() => {
setSearch(valueLocalBackup?.label ?? '');
setValueLocal(valueLocalBackup);
}, 0);
}
}
}}
className="w-[calc(--radix-popover-trigger-width + 36px)] -ml-9 p-0"
>
<CommandList>
{isLoading ? (
<CommandPrimitive.Loading>
<div className="p-1">
<Skeleton className="h-8 w-full" />
</div>
</CommandPrimitive.Loading>
) : null}
<CommandEmpty>{emptyMessage}</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
key={option.value}
value={option.label}
onMouseDown={(e) => e.preventDefault()}
onSelect={(currentValue) => {
const val = options.find(
(option) => option.label === currentValue,
);
setSearch(
currentValue === value?.value
? ''
: (options.find(
(option) => option.label === currentValue,
)?.label ?? ''),
);
setOpen(false);
setValueLocal(val);
if (val) {
setTimeout(() => {
onValueChange?.(val);
}, 0);
}
}}
>
<IconCheck
className={cn(
'mr-2 h-4 w-4',
value?.value === option.value
? 'opacity-100'
: 'opacity-0',
)}
/>
{option.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</PopoverContent>
</Command>
</Popover>
</div>
);
}; |
Beta Was this translation helpful? Give feedback.
Hey, if you upgrade to
cmdk@1.0.0
this is now possible. You can even render theCommand.List
part inside of a portal if needed.