update dropdown

This commit is contained in:
Nate Kelley 2025-02-25 13:58:01 -07:00
parent 780f9a489b
commit bdfc75c859
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 505 additions and 338 deletions

655
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,12 +27,13 @@
"@million/lint": "^1.0.14",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@supabase/auth-helpers-nextjs": "^0.10.0",
"@supabase/auth-helpers-react": "^0.5.0",
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.48.1",
"@supabase/supabase-js": "^2.49.1",
"@tailwindcss/forms": "^0.5.10",
"@tanstack/react-query": "^5.66.9",
"@tanstack/react-query-devtools": "^5.66.9",
@ -70,8 +71,7 @@
"framer-motion": "^12.4.7",
"html2canvas": "^1.4.1",
"js-cookie": "^3.0.5",
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.4",
"jspdf": "^3.0.0",
"jwt-decode": "^4.0.0",
"material-symbols": "^0.28.2",
"monaco-sql-languages": "^0.13.1",
@ -85,7 +85,7 @@
"patternomaly": "^1.3.2",
"pluralize": "^8.0.0",
"posthog-js": "*",
"prettier": "^3.5.1",
"prettier": "^3.5.2",
"prettier-plugin-tailwindcss": "^0.6.11",
"react": "^18",
"react-chartjs-2": "^5.3.0",
@ -96,27 +96,27 @@
"react-markdown": "^9.0.3",
"react-material-symbols": "^4.4.0",
"react-monaco-editor": "^0.58.0",
"react-scan": "^0.1.3",
"react-scan": "^0.2.3",
"react-syntax-highlighter": "^15.6.1",
"react-virtualized-auto-sizer": "^1.0.25",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"sonner": "^2.0.1",
"split-pane-react": "^0.1.3",
"tailwind-merge": "^3.0.1",
"tailwind-merge": "^3.0.2",
"utility-types": "^3.11.0",
"uuid": "^11.1.0",
"virtua": "^0.40.0"
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.4",
"@storybook/addon-essentials": "^8.5.8",
"@storybook/addon-interactions": "^8.5.8",
"@storybook/addon-onboarding": "^8.5.8",
"@storybook/blocks": "^8.5.8",
"@storybook/nextjs": "^8.5.8",
"@storybook/react": "^8.5.8",
"@storybook/test": "^8.5.8",
"@storybook/addon-essentials": "^8.6.0",
"@storybook/addon-interactions": "^8.6.0",
"@storybook/addon-onboarding": "^8.6.0",
"@storybook/blocks": "^8.6.0",
"@storybook/nextjs": "^8.6.0",
"@storybook/react": "^8.6.0",
"@storybook/test": "^8.6.0",
"@tailwindcss/postcss": "^4.0.9",
"@tanstack/eslint-plugin-query": "^5.66.1",
"@testing-library/jest-dom": "^6.6.3",
@ -147,11 +147,11 @@
"jest-environment-jsdom": "^29.7.0",
"monaco-editor-webpack-plugin": "^7.1.0",
"postcss": "8.5.3",
"sass": "^1.83.4",
"storybook": "^8.5.8",
"tailwindcss": "^4.0.8",
"sass": "^1.85.1",
"storybook": "^8.6.0",
"tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7",
"ts-jest": "^29.2.5",
"ts-jest": "^29.2.6",
"typescript": "^5"
},
"overrides": {}

View File

@ -1,7 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Dropdown } from './Dropdown';
import { Button } from '../buttons/Button';
import { PaintRoller } from '../icons';
import { PaintRoller, Star, Storage } from '../icons';
const meta: Meta<typeof Dropdown> = {
title: 'Base/Dropdown',
component: Dropdown,
@ -210,7 +211,7 @@ export const WithLoadingItems: Story = {
// Example with selection
export const WithSelection: Story = {
args: {
selectType: 'single',
selectType: true,
items: [
{
id: '1',
@ -233,3 +234,35 @@ export const WithSelection: Story = {
children: <Button>Selection Menu</Button>
}
};
// Example with secondary labels
export const WithSecondaryLabel: Story = {
args: {
menuLabel: 'Items with Secondary Labels',
items: [
{
id: '1',
label: 'Profile Settings',
secondaryLabel: 'User preferences',
onClick: () => console.log('Profile clicked'),
icon: <PaintRoller />
},
{
id: '2',
label: 'Storage',
secondaryLabel: '45GB used',
onClick: () => console.log('Storage clicked'),
icon: <Storage />
},
{ type: 'divider' },
{
id: '3',
label: 'Subscription',
secondaryLabel: 'Pro Plan',
onClick: () => console.log('Subscription clicked'),
icon: <Star />
}
],
children: <Button>Menu with Secondary Labels</Button>
}
};

View File

@ -13,8 +13,7 @@ import {
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem
DropdownMenuCheckboxItem
} from './DropdownBase';
import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader';
import { useMemoizedFn } from 'ahooks';
@ -22,6 +21,7 @@ import { cn } from '@/lib/classMerge';
export interface DropdownItem {
label: React.ReactNode;
secondaryLabel?: string;
id: string;
showIndex?: boolean;
shortcut?: string;
@ -41,7 +41,7 @@ export type DropdownItems = (DropdownItem | DropdownDivider | React.ReactNode)[]
export interface DropdownProps extends DropdownMenuProps {
items?: DropdownItems;
selectType?: 'default' | 'single' | 'multiple';
selectType?: boolean;
menuLabel?: string | React.ReactNode;
minWidth?: number;
maxWidth?: number;
@ -61,7 +61,7 @@ const dropdownItemKey = (item: DropdownItems[number], index: number) => {
export const Dropdown: React.FC<DropdownProps> = React.memo(
({
items = [],
selectType = 'default',
selectType = false,
menuLabel,
minWidth = 240,
maxWidth,
@ -110,7 +110,7 @@ const DropdownItemSelector: React.FC<{
index: number;
onSelect: DropdownProps['onSelect'];
closeOnSelect: boolean;
selectType: NonNullable<DropdownProps['selectType']>;
selectType: DropdownProps['selectType'];
}> = React.memo(({ item, index, onSelect, closeOnSelect, selectType }) => {
if ((item as DropdownDivider).type === 'divider') {
return <DropdownMenuSeparator />;
@ -133,7 +133,14 @@ const DropdownItemSelector: React.FC<{
DropdownItemSelector.displayName = 'DropdownItemSelector';
const DropdownItem = ({
const DropdownItem: React.FC<
DropdownItem & {
onSelect: DropdownProps['onSelect'];
closeOnSelect: boolean;
index: number;
selectType: DropdownProps['selectType'];
}
> = ({
label,
id,
showIndex,
@ -147,12 +154,8 @@ const DropdownItem = ({
items,
closeOnSelect,
onSelect,
selectType
}: DropdownItem & {
onSelect: DropdownProps['onSelect'];
closeOnSelect: boolean;
index: number;
selectType: NonNullable<DropdownProps['selectType']>;
selectType = false,
secondaryLabel
}) => {
const onClickItem = useMemoizedFn((e: React.MouseEvent<HTMLDivElement>) => {
if (onClick) onClick();
@ -163,24 +166,51 @@ const DropdownItem = ({
const Wrapper = useMemo(() => {
if (isSubItem) return DropdownSubMenuWrapper;
if (selectType === 'multiple' || selectType === 'single') return DropdownMenuCheckboxItem;
if (selectType) return DropdownMenuCheckboxItem;
return DropdownMenuItem;
}, [isSubItem, selectType]);
return (
<Wrapper
items={items}
disabled={disabled}
checked={selected}
onClick={onClickItem}
closeOnSelect={closeOnSelect}
selectType={selectType}>
const content = (
<>
{showIndex && <span className="text-gray-light">{index}</span>}
{icon && !loading && <span className="text-icon-color">{icon}</span>}
{loading && <CircleSpinnerLoader size={9} />}
{label}
<div className="flex flex-col gap-y-1">
{label}
{secondaryLabel && <span className="text-gray-light text-xxs">{secondaryLabel}</span>}
</div>
{shortcut && <DropdownMenuShortcut>{shortcut}</DropdownMenuShortcut>}
</Wrapper>
</>
);
if (isSubItem) {
return (
<DropdownSubMenuWrapper
items={items}
closeOnSelect={closeOnSelect}
onSelect={onSelect}
selectType={selectType}>
{content}
</DropdownSubMenuWrapper>
);
}
if (selectType) {
return (
<DropdownMenuCheckboxItem
checked={selected}
disabled={disabled}
onClick={onClickItem}
closeOnSelect={closeOnSelect}>
{content}
</DropdownMenuCheckboxItem>
);
}
return (
<DropdownMenuItem disabled={disabled} onClick={onClickItem} closeOnSelect={closeOnSelect}>
{content}
</DropdownMenuItem>
);
};
@ -196,7 +226,7 @@ const DropdownSubMenuWrapper = React.memo(
children: React.ReactNode;
closeOnSelect: boolean;
onSelect?: DropdownProps['onSelect'];
selectType: NonNullable<DropdownProps['selectType']>;
selectType: DropdownProps['selectType'];
}) => {
return (
<DropdownMenuSub>

View File

@ -2,9 +2,7 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
//import { Check, ChevronRight, Circle } from 'lucide-react';
import { Check, ChevronRight } from '../icons/NucleoIconOutlined';
import { ShapeCircle } from '../icons/NucleoIconOutlined';
import { cn } from '@/lib/classMerge';
@ -18,8 +16,6 @@ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
@ -106,7 +102,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> & {
closeOnSelect?: boolean;
selectType?: string;
selectType?: boolean;
}
>(({ className, children, onClick, checked, closeOnSelect = true, selectType, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
@ -136,29 +132,6 @@ const DropdownMenuCheckboxItem = React.forwardRef<
));
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-item-hover focus:text-foreground relative flex cursor-pointer items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<div className="flex h-4 w-4 items-center justify-center">
<ShapeCircle />
</div>
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
@ -167,7 +140,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@ -179,7 +152,7 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('bg-muted -mx-1 my-1 h-px', className)}
className={cn('bg-border -mx-1 my-1 h-[0.5px]', className)}
{...props}
/>
));
@ -198,7 +171,6 @@ export {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
@ -206,6 +178,5 @@ export {
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup
DropdownMenuSubTrigger
};

View File

View File

@ -27,7 +27,7 @@ const AppToaster = ({ ...props }: ToasterProps) => {
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground'
cancelButton: 'group-[.toast]:bg-border group-[.toast]:text-foreground'
}
}}
{...props}