Base Components
Dropdown
Displays a menu to the user—such as a set of actions or functions—triggered by a button.
Installation
Install the following dependencies:
terminal
npm install @radix-ui/react-dropdown-menu
Create a dropdown.tsx
file and paste the following code into it.
/components/ui/dropdown.tsx
// AlignUI Dropdown v0.0.0
'use client';
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { RiArrowRightSLine } from '@remixicon/react';
import { cn } from '@/utils/cn';
import { PolymorphicComponentProps } from '@/utils/polymorphic';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuCheckboxItem = DropdownMenuPrimitive.CheckboxItem;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuRadioItem = DropdownMenuPrimitive.RadioItem;
const DropdownMenuSeparator = DropdownMenuPrimitive.Separator;
const DropdownMenuArrow = DropdownMenuPrimitive.Arrow;
const DropdownMenuContent = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 8, ...rest }, forwardedRef) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={forwardedRef}
sideOffset={sideOffset}
className={cn(
'z-50 w-[300px] overflow-hidden rounded-2xl bg-bg-white-0 p-2 shadow-regular-md ring-1 ring-inset ring-stroke-soft-200',
'flex flex-col gap-1',
// origin
'data-[side=bottom]:origin-top data-[side=left]:origin-right data-[side=right]:origin-left data-[side=top]:origin-bottom',
// animation
'data-[state=open]:animate-in data-[state=open]:fade-in-0',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
'data-[state=closed]:zoom-out-95 data-[state=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',
className,
)}
{...rest}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = 'DropdownMenuContent';
const DropdownMenuItem = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...rest }, forwardedRef) => (
<DropdownMenuPrimitive.Item
ref={forwardedRef}
className={cn(
// base
'group/item relative cursor-pointer select-none rounded-lg p-2 text-paragraph-sm text-text-strong-950 outline-none',
'flex items-center gap-2',
'transition duration-200 ease-out',
// hover
'data-[highlighted]:bg-bg-weak-50',
// focus
'focus:outline-none',
// disabled
'data-[disabled]:text-text-disabled-300',
inset && 'pl-9',
className,
)}
{...rest}
/>
));
DropdownMenuItem.displayName = 'DropdownMenuItem';
function DropdownItemIcon<T extends React.ElementType>({
className,
as,
...rest
}: PolymorphicComponentProps<T>) {
const Component = as || 'div';
return (
<Component
className={cn(
// base
'size-5 text-text-sub-600',
// disabled
'group-has-[[data-disabled]]:text-text-disabled-300',
className,
)}
{...rest}
/>
);
}
DropdownItemIcon.displayName = 'DropdownItemIcon';
const DropdownMenuGroup = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group>
>(({ className, ...rest }, forwardedRef) => (
<DropdownMenuPrimitive.Group
ref={forwardedRef}
className={cn('flex flex-col gap-1', className)}
{...rest}
/>
));
DropdownMenuGroup.displayName = 'DropdownMenuGroup';
const DropdownMenuLabel = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label>
>(({ className, ...rest }, forwardedRef) => (
<DropdownMenuPrimitive.Label
ref={forwardedRef}
className={cn(
'px-2 py-1 text-subheading-xs uppercase text-text-soft-400',
className,
)}
{...rest}
/>
));
DropdownMenuLabel.displayName = 'DropdownMenuLabel';
const DropdownMenuSubTrigger = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...rest }, forwardedRef) => (
<DropdownMenuPrimitive.SubTrigger
ref={forwardedRef}
className={cn(
// base
'group/item relative cursor-pointer select-none rounded-lg p-2 text-paragraph-sm text-text-strong-950 outline-0',
'flex items-center gap-2',
'transition duration-200 ease-out',
// hover
'data-[highlighted]:bg-bg-weak-50',
// disabled
'data-[disabled]:text-text-disabled-300',
inset && 'pl-9',
className,
)}
{...rest}
>
{children}
<span className='flex-1' />
<DropdownItemIcon as={RiArrowRightSLine} />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName = 'DropdownMenuSubTrigger';
const DropdownMenuSubContent = React.forwardRef<
React.ComponentRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...rest }, forwardedRef) => (
<DropdownMenuPrimitive.SubContent
ref={forwardedRef}
className={cn(
'z-50 w-max overflow-hidden rounded-2xl bg-bg-white-0 p-2 shadow-regular-md ring-1 ring-inset ring-stroke-soft-200',
'flex flex-col gap-1',
// animation
'data-[state=open]:animate-in data-[state=open]:fade-in-0',
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
'data-[state=closed]:zoom-out-95 data-[state=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',
className,
)}
{...rest}
/>
));
DropdownMenuSubContent.displayName = 'DropdownMenuSubContent';
export {
DropdownMenu as Root,
DropdownMenuPortal as Portal,
DropdownMenuTrigger as Trigger,
DropdownMenuContent as Content,
DropdownMenuItem as Item,
DropdownItemIcon as ItemIcon,
DropdownMenuGroup as Group,
DropdownMenuLabel as Label,
DropdownMenuSub as MenuSub,
DropdownMenuSubTrigger as MenuSubTrigger,
DropdownMenuSubContent as MenuSubContent,
DropdownMenuCheckboxItem as CheckboxItem,
DropdownMenuRadioGroup as RadioGroup,
DropdownMenuRadioItem as RadioItem,
DropdownMenuSeparator as Separator,
DropdownMenuArrow as Arrow,
};
Update the import paths to match your project setup.
Examples
Sub Menu
API Reference
This component is based on the Radix UI Dropdown primitives. Refer to their documentation for the API reference.
Dropdown.Item
This component is based on the Dropdown.Item primitive. And adds:
Prop | Type | Default |
---|---|---|
inset | boolean |
Dropdown.ItemIcon
The Dropdown.ItemIcon
component is polymorphic, allowing you to change the underlying HTML element using the as
prop.
Prop | Type | Default |
---|---|---|
as | React.ElementType | div |
© 2024 AlignUI Design System. All rights reserved.
ON THIS PAGE