| import type { FC } from 'react' | |
| import { useMemo, useState } from 'react' | |
| import { RiArrowDownSLine, RiCheckLine, RiCloseCircleFill, RiFilter3Line } from '@remixicon/react' | |
| import cn from '@/utils/classnames' | |
| import { | |
| PortalToFollowElem, | |
| PortalToFollowElemContent, | |
| PortalToFollowElemTrigger, | |
| } from '@/app/components/base/portal-to-follow-elem' | |
| export type Item = { | |
| value: number | string | |
| name: string | |
| } & Record<string, any> | |
| type Props = { | |
| className?: string | |
| panelClassName?: string | |
| showLeftIcon?: boolean | |
| leftIcon?: any | |
| value: number | string | |
| items: Item[] | |
| onSelect: (item: any) => void | |
| onClear: () => void | |
| } | |
| const Chip: FC<Props> = ({ | |
| className, | |
| panelClassName, | |
| showLeftIcon = true, | |
| leftIcon, | |
| value, | |
| items, | |
| onSelect, | |
| onClear, | |
| }) => { | |
| const [open, setOpen] = useState(false) | |
| const triggerContent = useMemo(() => { | |
| return items.find(item => item.value === value)?.name || '' | |
| }, [items, value]) | |
| return ( | |
| <PortalToFollowElem | |
| open={open} | |
| onOpenChange={setOpen} | |
| placement='bottom-start' | |
| offset={4} | |
| > | |
| <div className='relative'> | |
| <PortalToFollowElemTrigger | |
| onClick={() => setOpen(v => !v)} | |
| className='block' | |
| > | |
| <div className={cn( | |
| 'flex items-center min-h-8 px-2 py-1 rounded-lg border-[0.5px] border-transparent bg-components-input-bg-normal cursor-pointer hover:bg-state-base-hover-alt', | |
| open && !value && '!bg-state-base-hover-alt hover:bg-state-base-hover-alt', | |
| !open && !!value && '!bg-components-button-secondary-bg shadow-xs !border-components-button-secondary-border hover:!bg-components-button-secondary-bg-hover hover:border-components-button-secondary-border-hover', | |
| open && !!value && '!bg-components-button-secondary-bg-hover !border-components-button-secondary-border-hover shadow-xs hover:!bg-components-button-secondary-bg-hover hover:border-components-button-secondary-border-hover', | |
| className, | |
| )}> | |
| {showLeftIcon && ( | |
| <div className='p-0.5'> | |
| {leftIcon || ( | |
| <RiFilter3Line className={cn('h-4 w-4 text-text-tertiary', !!value && 'text-text-secondary')} /> | |
| )} | |
| </div> | |
| )} | |
| <div className='grow first-line:p-1 flex items-center gap-0.5'> | |
| <div className={cn('system-sm-regular text-text-tertiary', !!value && 'text-text-secondary')}> | |
| {triggerContent} | |
| </div> | |
| </div> | |
| {!value && <RiArrowDownSLine className='h-4 w-4 text-text-tertiary' />} | |
| {!!value && ( | |
| <div className='p-[1px] cursor-pointer group/clear' onClick={(e) => { | |
| e.stopPropagation() | |
| onClear() | |
| }}> | |
| <RiCloseCircleFill className='h-3.5 w-3.5 text-text-quaternary group-hover/clear:text-text-tertiary' /> | |
| </div> | |
| )} | |
| </div> | |
| </PortalToFollowElemTrigger> | |
| <PortalToFollowElemContent className='z-[1002]'> | |
| <div className={cn('relative w-[240px] bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg', panelClassName)}> | |
| <div className='p-1 max-h-72 overflow-auto'> | |
| {items.map(item => ( | |
| <div | |
| key={item.value} | |
| className='flex items-center gap-2 pl-3 py-[6px] px-2 rounded-lg cursor-pointer hover:bg-state-base-hover' | |
| onClick={() => { | |
| onSelect(item) | |
| setOpen(false) | |
| }} | |
| > | |
| <div title={item.name} className='grow text-text-secondary system-sm-medium truncate'>{item.name}</div> | |
| {value === item.value && <RiCheckLine className='shrink-0 w-4 h-4 text-util-colors-blue-light-blue-light-600' />} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </PortalToFollowElemContent> | |
| </div> | |
| </PortalToFollowElem> | |
| ) | |
| } | |
| export default Chip | |