diff --git a/package.json b/package.json index 05eefa71..9263f78a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build-storybook": "storybook build" }, "dependencies": { + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68461d30..84a70096 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@radix-ui/react-dropdown-menu': + specifier: ^2.0.6 + version: 2.0.6(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) '@radix-ui/react-dialog': specifier: ^1.0.5 version: 1.0.5(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) @@ -1822,14 +1825,12 @@ packages: resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==} dependencies: '@floating-ui/utils': 0.1.6 - dev: true /@floating-ui/dom@1.5.3: resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} dependencies: '@floating-ui/core': 1.5.0 '@floating-ui/utils': 0.1.6 - dev: true /@floating-ui/react-dom@2.0.2(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==} @@ -1840,11 +1841,9 @@ packages: '@floating-ui/dom': 1.5.3 react: 18.0.0 react-dom: 18.0.0(react@18.0.0) - dev: true /@floating-ui/utils@0.1.6: resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} - dev: true /@humanwhocodes/config-array@0.6.0: resolution: {integrity: sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==} @@ -2199,7 +2198,6 @@ packages: '@types/react-dom': 18.0.0 react: 18.0.0 react-dom: 18.0.0(react@18.0.0) - dev: true /@radix-ui/react-collection@1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} @@ -2223,7 +2221,6 @@ packages: '@types/react-dom': 18.0.0 react: 18.0.0 react-dom: 18.0.0(react@18.0.0) - dev: true /@radix-ui/react-compose-refs@1.0.1(@types/react@18.0.0)(react@18.0.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} @@ -2297,7 +2294,6 @@ packages: '@babel/runtime': 7.23.2 '@types/react': 18.0.0 react: 18.0.0 - dev: true /@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==} @@ -2349,6 +2345,33 @@ packages: react-dom: 18.0.0(react@18.0.0) dev: false + /@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-menu': 2.0.6(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@types/react': 18.0.0 + '@types/react-dom': 18.0.0 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.0.0)(react@18.0.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -2451,6 +2474,44 @@ packages: react-dom: 18.0.0(react@18.0.0) dev: false + /@radix-ui/react-menu@2.0.6(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@types/react': 18.0.0 + '@types/react-dom': 18.0.0 + aria-hidden: 1.2.3 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + react-remove-scroll: 2.5.5(@types/react@18.0.0)(react@18.0.0) + dev: false + /@radix-ui/react-popper@1.1.2(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} peerDependencies: @@ -2481,6 +2542,36 @@ packages: react-dom: 18.0.0(react@18.0.0) dev: true + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@floating-ui/react-dom': 2.0.2(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.0.0)(react@18.0.0) + '@radix-ui/rect': 1.0.1 + '@types/react': 18.0.0 + '@types/react-dom': 18.0.0 + react: 18.0.0 + react-dom: 18.0.0(react@18.0.0) + dev: false + /@radix-ui/react-portal@1.0.3(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: @@ -2592,7 +2683,6 @@ packages: '@types/react-dom': 18.0.0 react: 18.0.0 react-dom: 18.0.0(react@18.0.0) - dev: true /@radix-ui/react-select@1.2.2(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0): resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==} @@ -2854,7 +2944,6 @@ packages: '@radix-ui/rect': 1.0.1 '@types/react': 18.0.0 react: 18.0.0 - dev: true /@radix-ui/react-use-size@1.0.1(@types/react@18.0.0)(react@18.0.0): resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} @@ -2895,7 +2984,6 @@ packages: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: '@babel/runtime': 7.23.2 - dev: true /@rushstack/eslint-patch@1.5.1: resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==} diff --git a/src/components/ui/DropdownMenu/DropdownMenu.stories.tsx b/src/components/ui/DropdownMenu/DropdownMenu.stories.tsx new file mode 100644 index 00000000..5c9ca557 --- /dev/null +++ b/src/components/ui/DropdownMenu/DropdownMenu.stories.tsx @@ -0,0 +1,89 @@ +import type { Meta, StoryObj } from '@storybook/react' +import Button from '../Button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from './DropdownMenu' + +const meta = { + title: 'UI/DropdownMenu', + component: DropdownMenu, + tags: ['autodocs'], + argTypes: {}, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Normal: Story = { + args: {}, + render: () => { + return ( + + + + + + My Account + + + + Profile + ⇧⌘P + + + Billing + ⌘B + + + Settings + ⌘S + + + Keyboard shortcuts + ⌘K + + + + + Team + + Invite users + + + Email + Message + + More... + + + + + New Team + ⌘+T + + + + GitHub + Support + API + + + Log out + ⇧⌘Q + + + + ) + }, +} diff --git a/src/components/ui/DropdownMenu/DropdownMenu.tsx b/src/components/ui/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 00000000..909e4c98 --- /dev/null +++ b/src/components/ui/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,204 @@ +'use client' + +import * as React from 'react' +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' +import { + CheckIcon, + ChevronRightIcon, + DotFilledIcon, +} from '@radix-ui/react-icons' +import { cn } from '@/utils' + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut' + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/components/ui/DropdownMenu/index.tsx b/src/components/ui/DropdownMenu/index.tsx new file mode 100644 index 00000000..0896f2b9 --- /dev/null +++ b/src/components/ui/DropdownMenu/index.tsx @@ -0,0 +1,35 @@ +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} from './DropdownMenu' + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/styles/colors.ts b/src/styles/colors.ts index 53b4e0cb..73613926 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -28,6 +28,7 @@ const LIGHT_THEMES = { 'secondary-color': COLORS.secondary_200, 'secondary-hover-color': COLORS.secondary_300, 'text-color': COLORS.black, + 'background-secondary-color': COLORS.gray, 'dialog-background-color': COLORS.black, } as const @@ -38,6 +39,7 @@ const DARK_THEMES = { 'secondary-color': COLORS.secondary_200, 'secondary-hover-color': COLORS.secondary_300, 'text-color': COLORS.white, + 'background-secondary-color': COLORS.gray, 'dialog-background-color': COLORS.black, } as const