WARNING: This component is in
Draft
status. This means that it is still in development and may have bugs or missing features. It is not intended to be used in production. You may use it for testing purposes.Dropdown
A vertically stacked set of interactive headings that each reveal a section of content.
import { component$ } from '@builder.io/qwik';
import { Dropdown } from '@qwik-ui/styled';
import {
LuCloud,
LuCreditCard,
LuGithub,
LuKeyboard,
LuLifeBuoy,
LuLogOut,
LuPlus,
LuSettings,
LuUser,
LuUserPlus,
LuUsers,
} from '@qwikest/icons/lucide';
export default component$(() => {
return (
<Dropdown.Root>
<Dropdown.Trigger>Git Settings</Dropdown.Trigger>
<Dropdown.Popover gutter={8}>
<Dropdown.Group>
<Dropdown.GroupLabel>My Account</Dropdown.GroupLabel>
<Dropdown.Separator />
{[
{ label: 'Profile', disabled: false, shortcut: '⇧⌘P', icon: <LuUser /> },
{ label: 'Billing', disabled: true, shortcut: '⌘B', icon: <LuCreditCard /> },
{ label: 'Settings', disabled: false, shortcut: '⌘S', icon: <LuSettings /> },
{
label: 'Keyboard shortcuts',
disabled: false,
shortcut: '⌘K',
icon: <LuKeyboard />,
},
].map((action) => (
<Dropdown.Item key={action.label} disabled={action.disabled}>
{action.icon}
{action.label}
<Dropdown.Shortcut>{action.shortcut}</Dropdown.Shortcut>
</Dropdown.Item>
))}
</Dropdown.Group>
<Dropdown.Separator />
<Dropdown.Group>
{[
{ label: 'Team', disabled: false, shortcut: '⌘+T', icon: <LuUsers /> },
{
label: 'Invite users',
disabled: false,
shortcut: '⌘+I',
icon: <LuUserPlus />,
},
{ label: 'New Team', disabled: false, shortcut: '⌘+N', icon: <LuPlus /> },
].map((action) => (
<Dropdown.Item key={action.label} disabled={action.disabled}>
{action.icon}
{action.label}
<Dropdown.Shortcut>{action.shortcut}</Dropdown.Shortcut>
</Dropdown.Item>
))}
</Dropdown.Group>
<Dropdown.Separator />
<Dropdown.Group>
{[
{ label: 'GitHub', disabled: false, shortcut: '⌘+G', icon: <LuGithub /> },
{ label: 'Support', disabled: false, shortcut: '⌘+S', icon: <LuLifeBuoy /> },
{ label: 'API', disabled: true, shortcut: '⌘+A', icon: <LuCloud /> },
].map((action) => (
<Dropdown.Item key={action.label} disabled={action.disabled}>
{action.icon}
{action.label}
<Dropdown.Shortcut>{action.shortcut}</Dropdown.Shortcut>
</Dropdown.Item>
))}
</Dropdown.Group>
<Dropdown.Separator />
<Dropdown.Item>
<LuLogOut />
<span>Log out</span>
<Dropdown.Shortcut>⇧⌘Q</Dropdown.Shortcut>
</Dropdown.Item>
</Dropdown.Popover>
</Dropdown.Root>
);
});
Installation
Run the following cli command or copy/paste the component code into your project
import { component$, PropsOf, Slot } from '@builder.io/qwik';
import { Dropdown as HDropdown } from '@qwik-ui/headless';
import { cn } from '@qwik-ui/utils';
import { buttonVariants } from '../button/button';
import { VariantProps } from 'class-variance-authority';
type RootProps = PropsOf<typeof HDropdown.Root>;
const Root = (props: RootProps) => {
return (
<HDropdown.Root dropdownItemComponent={Item} {...props}>
{props.children}
</HDropdown.Root>
);
};
type TriggerProps = PropsOf<typeof HDropdown.Trigger> &
VariantProps<typeof buttonVariants>;
const Trigger = component$<TriggerProps>(({ size, look, ...props }) => {
return (
<HDropdown.Trigger {...props} class={cn(buttonVariants({ size, look }), props.class)}>
<Slot />
</HDropdown.Trigger>
);
});
type PopoverProps = PropsOf<typeof HDropdown.Popover>;
const Popover = component$((props: PopoverProps) => {
return (
<HDropdown.Popover
{...props}
class={cn(
'min-w-32 overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
'data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
props.class,
)}
>
<Slot />
</HDropdown.Popover>
);
});
type ItemProps = PropsOf<typeof HDropdown.Item> & {
inset?: boolean;
};
const Item = component$(({ inset, ...props }: ItemProps) => {
return (
<HDropdown.Item
{...props}
class={cn(
'relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors select-none',
'focus:bg-accent focus:text-accent-foreground',
'data-disabled:pointer-events-none data-disabled:opacity-50',
'[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
inset && 'pl-8',
props.class,
)}
>
<Slot />
</HDropdown.Item>
);
});
type ItemIndicatorProps = PropsOf<typeof HDropdown.ItemIndicator>;
const ItemIndicator = component$((props: ItemIndicatorProps) => {
return (
<HDropdown.ItemIndicator {...props}>
<Slot />
</HDropdown.ItemIndicator>
);
});
type SeparatorProps = PropsOf<typeof HDropdown.Separator>;
const Separator = component$((props: SeparatorProps) => {
return (
<HDropdown.Separator {...props} class={cn('-mx-1 my-1 h-px bg-muted', props.class)} />
);
});
type GroupProps = PropsOf<typeof HDropdown.Group>;
export const Group = component$((props: GroupProps) => {
return (
<HDropdown.Group {...props}>
<Slot />
</HDropdown.Group>
);
});
type GroupLabelProps = PropsOf<typeof HDropdown.GroupLabel> & {
inset?: boolean;
};
const GroupLabel = component$(({ inset, ...props }: GroupLabelProps) => {
return (
<HDropdown.GroupLabel
{...props}
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)}
>
<Slot />
</HDropdown.GroupLabel>
);
});
const Shortcut = component$((props: PropsOf<'span'>) => {
return (
<span
{...props}
class={cn('ml-auto text-xs tracking-widest opacity-60', props.class)}
>
<Slot />
</span>
);
});
export const Dropdown = {
Root,
Trigger,
Popover,
Separator,
Item,
ItemIndicator,
Group,
GroupLabel,
Shortcut,
};