Spaces:
Paused
Paused
| import { cva } from 'class-variance-authority'; | |
| import { Command as CommandPrimitive } from 'cmdk'; | |
| import * as React from 'react'; | |
| import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling'; | |
| import { InputAnatomy } from '../input'; | |
| import { Modal, ModalProps } from '../modal'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * Anatomy | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export const CommandAnatomy = defineStyleAnatomy({ | |
| root: cva([ | |
| 'UI-Command__root', | |
| 'flex h-full w-full flex-col overflow-hidden rounded-[--radius-md] bg-[--paper] text-[--foreground]', | |
| ]), | |
| inputContainer: cva([ | |
| 'UI-Command__input', | |
| 'flex items-center px-3 py-2', | |
| 'cmdk-input-wrapper', | |
| ]), | |
| inputIcon: cva(['UI-Command__inputIcon', 'mr-2 h-5 w-5 shrink-0 opacity-50']), | |
| list: cva([ | |
| 'UI-Command__list', | |
| 'max-h-[300px] overflow-y-auto overflow-x-hidden', | |
| ]), | |
| empty: cva([ | |
| 'UI-Command__empty', | |
| 'py-6 text-center text-base text-[--muted]', | |
| ]), | |
| group: cva([ | |
| 'UI-Command__group', | |
| 'overflow-hidden p-1 text-[--foreground]', | |
| '[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-[--muted]', | |
| ]), | |
| separator: cva(['UI-Command__separator', '-mx-1 h-px bg-[--border]']), | |
| item: cva([ | |
| 'UI-Command__item', | |
| 'relative flex cursor-default select-none items-center rounded-[--radius] px-2 py-1.5 text-base outline-none', | |
| 'aria-selected:bg-[--subtle] data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50', | |
| '[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', | |
| ]), | |
| itemIconContainer: cva([ | |
| 'UI-Command__itemIconContainer', | |
| 'mr-2 text-base shrink-0 w-4', | |
| ]), | |
| shortcut: cva([ | |
| 'UI-Command__shortcut', | |
| 'ml-auto text-xs tracking-widest text-[--muted]', | |
| ]), | |
| }); | |
| export const CommandDialogAnatomy = defineStyleAnatomy({ | |
| content: cva(['UI-CommandDialog__content', 'overflow-hidden p-0']), | |
| command: cva([ | |
| 'UI-CommandDialog__command', | |
| '[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-[--muted]', | |
| '[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pb-2 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5', | |
| '[&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-2 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-5', | |
| ]), | |
| }); | |
| /* ------------------------------------------------------------------------------------------------- | |
| * Command | |
| * -----------------------------------------------------------------------------------------------*/ | |
| const __CommandAnatomyContext = React.createContext< | |
| ComponentAnatomy<typeof CommandAnatomy> | |
| >({}); | |
| export type CommandProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive | |
| > & | |
| ComponentAnatomy<typeof CommandAnatomy>; | |
| export const Command = React.forwardRef<HTMLDivElement, CommandProps>( | |
| (props, ref) => { | |
| const { | |
| className, | |
| inputContainerClass, | |
| inputIconClass, | |
| listClass, | |
| emptyClass, | |
| groupClass, | |
| separatorClass, | |
| itemClass, | |
| itemIconContainerClass, | |
| shortcutClass, | |
| loop = true, | |
| ...rest | |
| } = props; | |
| return ( | |
| <__CommandAnatomyContext.Provider | |
| value={{ | |
| inputContainerClass, | |
| inputIconClass, | |
| listClass, | |
| emptyClass, | |
| groupClass, | |
| separatorClass, | |
| itemClass, | |
| itemIconContainerClass, | |
| shortcutClass, | |
| }} | |
| > | |
| <CommandPrimitive | |
| ref={ref} | |
| className={cn(CommandAnatomy.root(), className)} | |
| loop={loop} | |
| {...rest} | |
| /> | |
| </__CommandAnatomyContext.Provider> | |
| ); | |
| } | |
| ); | |
| Command.displayName = CommandPrimitive.displayName; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandInput | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandInputProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive.Input | |
| > & | |
| Pick< | |
| ComponentAnatomy<typeof CommandAnatomy>, | |
| 'inputContainerClass' | 'inputIconClass' | |
| >; | |
| export const CommandInput = React.forwardRef< | |
| HTMLInputElement, | |
| CommandInputProps | |
| >((props, ref) => { | |
| const { className, inputContainerClass, inputIconClass, ...rest } = props; | |
| const { | |
| inputContainerClass: _inputContainerClass, | |
| inputIconClass: _inputIconClass, | |
| } = React.useContext(__CommandAnatomyContext); | |
| return ( | |
| <div | |
| className={cn( | |
| CommandAnatomy.inputContainer(), | |
| _inputContainerClass, | |
| inputContainerClass | |
| )} | |
| cmdk-input-wrapper="" | |
| > | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="24" | |
| height="24" | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| stroke="currentColor" | |
| strokeWidth="2" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| className={cn( | |
| CommandAnatomy.inputIcon(), | |
| _inputIconClass, | |
| inputIconClass | |
| )} | |
| > | |
| <circle cx="11" cy="11" r="8" /> | |
| <path d="m21 21-4.3-4.3" /> | |
| </svg> | |
| <CommandPrimitive.Input | |
| ref={ref} | |
| className={cn( | |
| InputAnatomy.root({ | |
| intent: 'unstyled', | |
| size: 'sm', | |
| isDisabled: rest.disabled, | |
| }), | |
| className | |
| )} | |
| {...rest} | |
| /> | |
| </div> | |
| ); | |
| }); | |
| CommandInput.displayName = 'CommandInput'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandList | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandListProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive.List | |
| >; | |
| export const CommandList = React.forwardRef<HTMLDivElement, CommandListProps>( | |
| (props, ref) => { | |
| const { className, ...rest } = props; | |
| const { listClass } = React.useContext(__CommandAnatomyContext); | |
| return ( | |
| <CommandPrimitive.List | |
| ref={ref} | |
| className={cn(CommandAnatomy.list(), listClass, className)} | |
| {...rest} | |
| /> | |
| ); | |
| } | |
| ); | |
| CommandList.displayName = 'CommandList'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandEmpty | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandEmptyProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive.Empty | |
| >; | |
| export const CommandEmpty = React.forwardRef<HTMLDivElement, CommandEmptyProps>( | |
| (props, ref) => { | |
| const { className, ...rest } = props; | |
| const { emptyClass } = React.useContext(__CommandAnatomyContext); | |
| return ( | |
| <CommandPrimitive.Empty | |
| ref={ref} | |
| className={cn(CommandAnatomy.empty(), emptyClass, className)} | |
| {...rest} | |
| /> | |
| ); | |
| } | |
| ); | |
| CommandEmpty.displayName = 'CommandEmpty'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandGroup | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandGroupProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive.Group | |
| >; | |
| export const CommandGroup = React.forwardRef<HTMLDivElement, CommandGroupProps>( | |
| (props, ref) => { | |
| const { className, ...rest } = props; | |
| const { groupClass } = React.useContext(__CommandAnatomyContext); | |
| return ( | |
| <CommandPrimitive.Group | |
| ref={ref} | |
| className={cn(CommandAnatomy.group(), groupClass, className)} | |
| {...rest} | |
| /> | |
| ); | |
| } | |
| ); | |
| CommandGroup.displayName = 'CommandGroup'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandSeparator | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandSeparatorProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive.Separator | |
| >; | |
| export const CommandSeparator = React.forwardRef< | |
| HTMLDivElement, | |
| CommandSeparatorProps | |
| >((props, ref) => { | |
| const { className, ...rest } = props; | |
| const { separatorClass } = React.useContext(__CommandAnatomyContext); | |
| return ( | |
| <CommandPrimitive.Separator | |
| ref={ref} | |
| className={cn(CommandAnatomy.separator(), separatorClass, className)} | |
| {...rest} | |
| /> | |
| ); | |
| }); | |
| CommandSeparator.displayName = 'CommandSeparator'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandItem | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandItemProps = React.ComponentPropsWithoutRef< | |
| typeof CommandPrimitive.Item | |
| > & | |
| Pick<ComponentAnatomy<typeof CommandAnatomy>, 'itemIconContainerClass'> & { | |
| leftIcon?: React.ReactNode; | |
| }; | |
| export const CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>( | |
| (props, ref) => { | |
| const { className, itemIconContainerClass, leftIcon, children, ...rest } = | |
| props; | |
| const { itemClass, itemIconContainerClass: _itemIconContainerClass } = | |
| React.useContext(__CommandAnatomyContext); | |
| const itemRef = React.useRef<HTMLDivElement | null>(null); | |
| React.useEffect(() => { | |
| const element = itemRef.current; | |
| if (!element) return; | |
| const observer = new MutationObserver((mutations) => { | |
| mutations.forEach((mutation) => { | |
| if ( | |
| mutation.attributeName === 'aria-selected' && | |
| element.getAttribute('aria-selected') === 'true' | |
| ) { | |
| element.scrollIntoView({ block: 'nearest' }); | |
| } | |
| }); | |
| }); | |
| observer.observe(element, { attributes: true }); | |
| return () => observer.disconnect(); | |
| }, []); | |
| const setRefs = React.useCallback( | |
| (node: HTMLDivElement | null) => { | |
| itemRef.current = node; | |
| if (ref) { | |
| if (typeof ref === 'function') { | |
| ref(node); | |
| } | |
| } | |
| }, | |
| [ref] | |
| ); | |
| return ( | |
| <CommandPrimitive.Item | |
| ref={setRefs} | |
| className={cn(CommandAnatomy.item(), itemClass, className)} | |
| {...rest} | |
| data-cmdkvalue={rest.id} | |
| > | |
| {leftIcon && ( | |
| <span | |
| className={cn( | |
| CommandAnatomy.itemIconContainer(), | |
| _itemIconContainerClass, | |
| itemIconContainerClass | |
| )} | |
| > | |
| {leftIcon} | |
| </span> | |
| )} | |
| {children} | |
| </CommandPrimitive.Item> | |
| ); | |
| } | |
| ); | |
| CommandItem.displayName = 'CommandItem'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandShortcut | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandShortcutProps = React.ComponentPropsWithoutRef<'span'>; | |
| export const CommandShortcut = React.forwardRef< | |
| HTMLSpanElement, | |
| CommandShortcutProps | |
| >((props, ref) => { | |
| const { className, ...rest } = props; | |
| const { shortcutClass } = React.useContext(__CommandAnatomyContext); | |
| return ( | |
| <span | |
| ref={ref} | |
| className={cn(CommandAnatomy.shortcut(), shortcutClass, className)} | |
| {...rest} | |
| /> | |
| ); | |
| }); | |
| CommandShortcut.displayName = 'CommandShortcut'; | |
| /* ------------------------------------------------------------------------------------------------- | |
| * CommandDialog | |
| * -----------------------------------------------------------------------------------------------*/ | |
| export type CommandDialogProps = ModalProps & | |
| ComponentAnatomy<typeof CommandDialogAnatomy> & { | |
| commandProps: React.ComponentPropsWithoutRef<typeof CommandPrimitive>; | |
| }; | |
| export const CommandDialog = (props: CommandDialogProps) => { | |
| const { children, commandClass, contentClass, commandProps, ...rest } = props; | |
| return ( | |
| <Modal | |
| {...rest} | |
| contentClass={cn(CommandDialogAnatomy.content(), contentClass)} | |
| > | |
| <Command | |
| shouldFilter={false} | |
| className={cn(CommandDialogAnatomy.command(), commandClass)} | |
| {...commandProps} | |
| > | |
| {children} | |
| </Command> | |
| </Modal> | |
| ); | |
| }; | |
| CommandDialog.displayName = 'CommandDialog'; | |