dropdown update

This commit is contained in:
Nate Kelley 2025-03-14 13:31:52 -06:00
parent 9fea44cc60
commit dfede461bc
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
4 changed files with 56 additions and 34 deletions

View File

@ -21,22 +21,28 @@ export const SaveToCollectionsDropdown: React.FC<{
const [openCollectionModal, setOpenCollectionModal] = React.useState(false); const [openCollectionModal, setOpenCollectionModal] = React.useState(false);
const [showDropdown, setShowDropdown] = React.useState(false); const [showDropdown, setShowDropdown] = React.useState(false);
const items: DropdownProps['items'] = useMemo( const items: DropdownProps['items'] = useMemo(() => {
() => const collectionsItems = (collectionsList || []).map<DropdownItem>((collection) => {
(collectionsList || []).map<DropdownItem>((collection) => { return {
return { value: collection.id,
value: collection.id, label: collection.name,
label: collection.name, selected: selectedCollections.some((id) => id === collection.id),
selected: selectedCollections.some((id) => id === collection.id), onClick: () => onClickItem(collection),
onClick: () => onClickItem(collection), link: createBusterRoute({
link: createBusterRoute({ route: BusterRoutes.APP_COLLECTIONS_ID,
route: BusterRoutes.APP_COLLECTIONS_ID, collectionId: collection.id
collectionId: collection.id })
}) };
}; });
}),
[collectionsList, selectedCollections] return [
); ...collectionsItems,
{
value: 'new',
label: 'New collection'
}
];
}, [collectionsList, selectedCollections]);
const onClickItem = useMemoizedFn((collection: BusterCollectionListItem) => { const onClickItem = useMemoizedFn((collection: BusterCollectionListItem) => {
const isSelected = selectedCollections.some((id) => id === collection.id); const isSelected = selectedCollections.some((id) => id === collection.id);
@ -63,6 +69,7 @@ export const SaveToCollectionsDropdown: React.FC<{
const onOpenChange = useMemoizedFn((open: boolean) => { const onOpenChange = useMemoizedFn((open: boolean) => {
setShowDropdown(open); setShowDropdown(open);
console.log('open', open);
}); });
const onClick = useMemoizedFn(() => { const onClick = useMemoizedFn(() => {
@ -84,11 +91,12 @@ export const SaveToCollectionsDropdown: React.FC<{
side="bottom" side="bottom"
align="start" align="start"
menuHeader={'Save to a collection'} menuHeader={'Save to a collection'}
open={showDropdown}
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
footerContent={memoizedButton} footerContent={memoizedButton}
items={items}> items={items}>
<AppTooltip title={showDropdown ? '' : 'Save to collection'}>{children} </AppTooltip> <div>
<AppTooltip title={showDropdown ? '' : 'Save to collection'}>{children} </AppTooltip>
</div>
</Dropdown> </Dropdown>
<NewCollectionModal <NewCollectionModal

View File

@ -540,6 +540,13 @@ export const WithFooterContent: Story = {
} }
}; };
export const WithFooterAndHeader: Story = {
args: {
...WithFooterContent.args,
menuHeader: 'Menu...'
}
};
// Example with numbered items // Example with numbered items
export const WithNumberedItemsNoFilter: Story = { export const WithNumberedItemsNoFilter: Story = {
args: { args: {
@ -565,6 +572,7 @@ export const WithNumberedItemsNoFilter: Story = {
label: 'Third Item', label: 'Third Item',
onClick: fn(), onClick: fn(),
icon: <Storage />, icon: <Storage />,
searchLabel: 'Third Item with secondary label',
secondaryLabel: 'With secondary label' secondaryLabel: 'With secondary label'
}, },
{ {

View File

@ -164,9 +164,9 @@ export const DropdownBase = <T,>({
className={cn('max-w-72 min-w-44', className)} className={cn('max-w-72 min-w-44', className)}
align={align} align={align}
side={side} side={side}
footerContent={footerContent}> footerContent={footerContent}
{menuHeader && ( headerContent={
<> menuHeader && (
<DropdownMenuHeaderSelector <DropdownMenuHeaderSelector
menuHeader={menuHeader} menuHeader={menuHeader}
onChange={handleSearchChange} onChange={handleSearchChange}
@ -174,10 +174,8 @@ export const DropdownBase = <T,>({
onSelectItem={onSelectItem} onSelectItem={onSelectItem}
showIndex={showIndex} showIndex={showIndex}
/> />
<DropdownMenuSeparator /> )
</> }>
)}
<div className="max-h-[375px] overflow-y-auto"> <div className="max-h-[375px] overflow-y-auto">
{hasShownItem ? ( {hasShownItem ? (
<> <>
@ -324,11 +322,12 @@ const DropdownItem = <T,>({
const content = ( const content = (
<> <>
{icon && !loading && <span className="text-icon-color">{icon}</span>} {icon && !loading && <span className="text-icon-color">{icon}</span>}
{loading && <CircleSpinnerLoader size={9} />}
<div className={cn('flex flex-col gap-y-1', truncate && 'overflow-hidden')}> <div className={cn('flex flex-col gap-y-1', truncate && 'overflow-hidden')}>
<span className={cn(truncate && 'truncate')}>{label}</span> <span className={cn(truncate && 'truncate')}>{label}</span>
{secondaryLabel && <span className="text-gray-light text2xs">{secondaryLabel}</span>} {secondaryLabel && <span className="text-gray-light text2xs">{secondaryLabel}</span>}
</div> </div>
{loading && <CircleSpinnerLoader size={9} />}
{shortcut && <DropdownMenuShortcut>{shortcut}</DropdownMenuShortcut>} {shortcut && <DropdownMenuShortcut>{shortcut}</DropdownMenuShortcut>}
{link && ( {link && (
<DropdownMenuLink <DropdownMenuLink
@ -511,6 +510,7 @@ const DropdownMenuHeaderSearch = <T,>({
<Input <Input
autoFocus autoFocus
variant={'ghost'} variant={'ghost'}
size={'small'}
placeholder={placeholder} placeholder={placeholder}
value={text} value={text}
onChange={onChangePreflight} onChange={onChangePreflight}

View File

@ -68,20 +68,24 @@ const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & { React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> & {
footerContent?: React.ReactNode; footerContent?: React.ReactNode;
headerContent?: React.ReactNode;
} }
>(({ className, children, sideOffset = 4, footerContent, ...props }, ref) => { >(({ className, children, sideOffset = 4, footerContent, headerContent, ...props }, ref) => {
const NodeWrapper = footerContent ? 'div' : 'span';
const nodeWrapperProps = footerContent ? { className: 'p-2' } : { className: '' };
return ( return (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn(baseContentClass, 'shadow', footerContent && 'p-0', className)} className={cn(baseContentClass, 'shadow', 'p-0', className)}
{...props}> {...props}>
<NodeWrapper {...nodeWrapperProps}>{children}</NodeWrapper> {headerContent && (
{footerContent && <div className="border-t p-2">{footerContent}</div>} <div className="flex flex-col">
<div className="p-1">{headerContent}</div>
<div className="bg-border h-[0.5px] w-full" />
</div>
)}
<div className="used-for-ref-purpose">{children}</div>
{footerContent && <div className="border-t p-1">{footerContent}</div>}
</DropdownMenuPrimitive.Content> </DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
); );
@ -102,6 +106,7 @@ const DropdownMenuItem = React.forwardRef<
className={cn( className={cn(
'relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-base outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0', 'relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-base outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
'focus:bg-item-hover focus:text-foreground', 'focus:bg-item-hover focus:text-foreground',
'dropdown-item mx-1 [&.dropdown-item:first-child]:mt-1! [&.dropdown-item:has(+.dropdown-separator)]:mb-1 [&.dropdown-item:has(~.dropdown-separator)]:mt-1 [&.dropdown-item:last-child]:mb-1!',
inset && 'pl-8', inset && 'pl-8',
truncate && 'overflow-hidden', truncate && 'overflow-hidden',
'group', 'group',
@ -123,7 +128,8 @@ const itemClass = cn(
'focus:bg-item-hover focus:text-foreground', 'focus:bg-item-hover focus:text-foreground',
'relative flex cursor-pointer items-center rounded-sm py-1.5 text-sm outline-none select-none', 'relative flex cursor-pointer items-center rounded-sm py-1.5 text-sm outline-none select-none',
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50', 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'gap-1.5' 'gap-1.5',
'mx-1 dropdown-item [&.dropdown-item:has(+.dropdown-separator)]:mb-1 [&.dropdown-item:has(~.dropdown-separator)]:mt-1 [&.dropdown-item:first-child]:mt-1! [&.dropdown-item:last-child]:mb-1!'
); );
const DropdownMenuCheckboxItemSingle = React.forwardRef< const DropdownMenuCheckboxItemSingle = React.forwardRef<