brunner56's picture
implement app
0bfe2e3
'use client';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling';
/* -------------------------------------------------------------------------------------------------
* Anatomy
* -----------------------------------------------------------------------------------------------*/
export const DropdownMenuAnatomy = defineStyleAnatomy({
subTrigger: cva([
'UI-DropdownMenu__subTrigger',
'focus:bg-[--subtle] data-[state=open]:bg-[--subtle]',
]),
subContent: cva([
'UI-DropdownMenu__subContent',
'z-50 min-w-[12rem] overflow-hidden rounded-[--radius] border bg-[--background] p-2 text-[--foreground] shadow-sm',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-100 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',
]),
root: cva([
'UI-DropdownMenu__root',
'z-50 min-w-[15rem] overflow-hidden rounded-[--radius] border bg-[--background] p-2 text-[--foreground] shadow-sm',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-100 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',
]),
item: cva([
'UI-DropdownMenu__item',
'relative flex cursor-default select-none items-center rounded-[--radius] cursor-pointer px-2 py-2 text-sm outline-none transition-colors',
'focus:bg-[--subtle] data-[disabled]:pointer-events-none',
'data-[disabled]:opacity-50',
'[&>svg]:mr-2 [&>svg]:text-lg',
]),
group: cva(['UI-DropdownMenu__group']),
label: cva([
'UI-DropdownMenu__label',
'px-2 py-1.5 text-sm font-semibold text-[--muted]',
]),
separator: cva([
'UI-DropdownMenu__separator',
'-mx-1 my-2 h-px bg-[--border]',
]),
shortcut: cva([
'UI-DropdownMenu__shortcut',
'ml-auto text-xs tracking-widest opacity-60',
]),
});
/* -------------------------------------------------------------------------------------------------
* DropdownMenu
* -----------------------------------------------------------------------------------------------*/
const __DropdownMenuAnatomyContext = React.createContext<
ComponentAnatomy<typeof DropdownMenuAnatomy>
>({});
export type DropdownMenuProps = ComponentAnatomy<typeof DropdownMenuAnatomy> &
Pick<
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>,
'defaultOpen' | 'open' | 'onOpenChange' | 'dir'
> &
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & {
/**
* Interaction with outside elements will be enabled and other elements will be visible to screen readers.
*/
allowOutsideInteraction?: boolean;
/**
* The trigger element that is always visible and is used to open the menu.
*/
trigger?: React.ReactNode;
};
export const DropdownMenu = React.forwardRef<HTMLDivElement, DropdownMenuProps>(
(props, ref) => {
const {
children,
trigger,
// Root
defaultOpen,
open,
onOpenChange,
dir,
allowOutsideInteraction,
// Content
sideOffset = 4,
className,
subContentClass,
subTriggerClass,
shortcutClass,
itemClass,
labelClass,
separatorClass,
groupClass,
...rest
} = props;
return (
<__DropdownMenuAnatomyContext.Provider
value={{
subContentClass,
subTriggerClass,
shortcutClass,
itemClass,
labelClass,
separatorClass,
groupClass,
}}
>
<DropdownMenuPrimitive.Root
defaultOpen={defaultOpen}
open={open}
onOpenChange={onOpenChange}
dir={dir}
modal={!allowOutsideInteraction}
{...rest}
>
<DropdownMenuPrimitive.Trigger asChild>
{trigger}
</DropdownMenuPrimitive.Trigger>
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(DropdownMenuAnatomy.root(), className)}
{...rest}
>
{children}
</DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Portal>
</DropdownMenuPrimitive.Root>
</__DropdownMenuAnatomyContext.Provider>
);
}
);
DropdownMenu.displayName = 'DropdownMenu';
/* -------------------------------------------------------------------------------------------------
* DropdownMenuGroup
* -----------------------------------------------------------------------------------------------*/
export type DropdownMenuGroupProps = React.ComponentPropsWithoutRef<
typeof DropdownMenuPrimitive.Group
>;
export const DropdownMenuGroup = React.forwardRef<
HTMLDivElement,
DropdownMenuGroupProps
>((props, ref) => {
const { className, ...rest } = props;
const { groupClass } = React.useContext(__DropdownMenuAnatomyContext);
return (
<DropdownMenuPrimitive.Group
ref={ref}
className={cn(DropdownMenuAnatomy.group(), groupClass, className)}
{...rest}
/>
);
});
DropdownMenuGroup.displayName = 'DropdownMenuGroup';
/* -------------------------------------------------------------------------------------------------
* DropdownMenuSub
* -----------------------------------------------------------------------------------------------*/
export type DropdownMenuSubProps = Pick<
ComponentAnatomy<typeof DropdownMenuAnatomy>,
'subTriggerClass'
> &
Pick<
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub>,
'defaultOpen' | 'open' | 'onOpenChange'
> &
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> & {
/**
* The content of the default trigger element that will open the sub menu.
*
* By default, the trigger will be an item with a right chevron icon.
*/
triggerContent?: React.ReactNode;
/**
* Props to pass to the default trigger element that will open the sub menu.
*/
triggerProps?: React.ComponentPropsWithoutRef<
typeof DropdownMenuPrimitive.SubTrigger
>;
triggerInset?: boolean;
};
export const DropdownMenuSub = React.forwardRef<
HTMLDivElement,
DropdownMenuSubProps
>((props, ref) => {
const {
children,
triggerContent,
triggerProps,
triggerInset,
// Sub
defaultOpen,
open,
onOpenChange,
// SubContent
sideOffset = 8,
className,
subTriggerClass,
...rest
} = props;
const { subTriggerClass: _subTriggerClass, subContentClass } =
React.useContext(__DropdownMenuAnatomyContext);
return (
<DropdownMenuPrimitive.Sub {...rest}>
<DropdownMenuPrimitive.SubTrigger
className={cn(
DropdownMenuAnatomy.item(),
DropdownMenuAnatomy.subTrigger(),
triggerInset && 'pl-8',
_subTriggerClass,
subTriggerClass,
className
)}
{...triggerProps}
>
{triggerContent}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={cn(DropdownMenuAnatomy.shortcut(), 'w-4 h-4 ml-auto')}
>
<path d="m9 18 6-6-6-6" />
</svg>
</DropdownMenuPrimitive.SubTrigger>
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.SubContent
ref={ref}
sideOffset={sideOffset}
className={cn(
DropdownMenuAnatomy.subContent(),
subContentClass,
className
)}
{...rest}
>
{children}
</DropdownMenuPrimitive.SubContent>
</DropdownMenuPrimitive.Portal>
</DropdownMenuPrimitive.Sub>
);
});
DropdownMenuSub.displayName = 'DropdownMenuSub';
/* -------------------------------------------------------------------------------------------------
* DropdownMenuItem
* -----------------------------------------------------------------------------------------------*/
export type DropdownMenuItemProps = React.ComponentPropsWithoutRef<
typeof DropdownMenuPrimitive.Item
> & {
inset?: boolean;
};
export const DropdownMenuItem = React.forwardRef<
HTMLDivElement,
DropdownMenuItemProps
>((props, ref) => {
const { className, inset, ...rest } = props;
const { itemClass } = React.useContext(__DropdownMenuAnatomyContext);
return (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
DropdownMenuAnatomy.item(),
inset && 'pl-8',
itemClass,
className
)}
{...rest}
/>
);
});
DropdownMenuItem.displayName = 'DropdownMenuItem';
/* -------------------------------------------------------------------------------------------------
* DropdownMenuLabel
* -----------------------------------------------------------------------------------------------*/
export type DropdownMenuLabelProps = React.ComponentPropsWithoutRef<
typeof DropdownMenuPrimitive.Label
> & {
inset?: boolean;
};
export const DropdownMenuLabel = React.forwardRef<
HTMLDivElement,
DropdownMenuLabelProps
>((props, ref) => {
const { className, inset, ...rest } = props;
const { labelClass } = React.useContext(__DropdownMenuAnatomyContext);
return (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
DropdownMenuAnatomy.label(),
inset && 'pl-8',
labelClass,
className
)}
{...rest}
/>
);
});
DropdownMenuLabel.displayName = 'DropdownMenuLabel';
/* -------------------------------------------------------------------------------------------------
* DropdownMenuSeparator
* -----------------------------------------------------------------------------------------------*/
export type DropdownMenuSeparatorProps = React.ComponentPropsWithoutRef<
typeof DropdownMenuPrimitive.Separator
>;
export const DropdownMenuSeparator = React.forwardRef<
HTMLDivElement,
DropdownMenuSeparatorProps
>((props, ref) => {
const { className, ...rest } = props;
const { separatorClass } = React.useContext(__DropdownMenuAnatomyContext);
return (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn(DropdownMenuAnatomy.separator(), separatorClass, className)}
{...rest}
/>
);
});
DropdownMenuSeparator.displayName = 'DropdownMenuSeparator';
/* -------------------------------------------------------------------------------------------------
* DropdownMenuShortcut
* -----------------------------------------------------------------------------------------------*/
export type DropdownMenuShortcutProps = React.HTMLAttributes<HTMLSpanElement>;
export const DropdownMenuShortcut = React.forwardRef<
HTMLSpanElement,
DropdownMenuShortcutProps
>((props, ref) => {
const { className, ...rest } = props;
const { shortcutClass } = React.useContext(__DropdownMenuAnatomyContext);
return (
<span
ref={ref}
className={cn(DropdownMenuAnatomy.shortcut(), shortcutClass, className)}
{...rest}
/>
);
});
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';