diff --git a/src/DropdownMenu/DropdownItems.tsx b/src/DropdownMenu/DropdownItems.tsx new file mode 100644 index 0000000..ea09d9c --- /dev/null +++ b/src/DropdownMenu/DropdownItems.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { css, styled } from '@storybook/theming'; +import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; +import { typography } from '../_helpers'; +import { color, spacing } from '../_tokens'; +import { Icon } from '../Icon'; + +const itemStyles = css` + ${typography.body14}; + line-height: 1; + height: 28px; + display: flex; + align-items: center; + color: ${color.slate800}; + cursor: pointer; + padding: 0 ${spacing[2]} 0 ${spacing[6]}; + border-radius: 3px; + + &[data-highlighted] { + background-color: ${color.blue100}; + outline: none; + } + + &[data-disabled] { + color: ${color.slate400}; + pointer-events: none; + } +`; + +const ItemIndicator = styled(RadixDropdownMenu.ItemIndicator)` + position: absolute; + left: 0; + width: ${spacing[5]}; + display: inline-flex; + align-items: center; + justify-content: center; +`; + +export const CheckboxItem = styled(RadixDropdownMenu.CheckboxItem)` + ${itemStyles} + position: relative; +`; + +export const DropdownMenuCheckboxItem = ({ + children, + ...props +}: RadixDropdownMenu.MenuCheckboxItemProps) => ( + + + + + {children} + +); + +export const DropdownMenuItem = styled(RadixDropdownMenu.Item)` + ${itemStyles} +`; diff --git a/src/DropdownMenu/DropdownMenu.stories.tsx b/src/DropdownMenu/DropdownMenu.stories.tsx new file mode 100644 index 0000000..427aeb8 --- /dev/null +++ b/src/DropdownMenu/DropdownMenu.stories.tsx @@ -0,0 +1,175 @@ +import React, { Meta, StoryObj } from '@storybook/react'; +import { within, userEvent } from '@storybook/testing-library'; +import { DropdownMenu } from './DropdownMenu'; +import { DropdownMenuItem, DropdownMenuCheckboxItem } from './DropdownItems'; +import { Icon } from '../Icon'; + +const meta: Meta = { + title: 'Components/Dropdown', + component: DropdownMenu, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const MenuButton = await canvas.findByRole('button'); + MenuButton.focus(); + await userEvent.keyboard('{enter}'); + }, +}; + +export default meta; +type Story = StoryObj; + +export const ClosedLight: Story = { + render: (args) => ( + + CLI + Github Action + Config File + + ), + play: async () => {}, + args: { + label: 'Filter config options', + }, +}; + +export const ClosedDark: Story = { + ...ClosedLight, + args: { + ...ClosedLight.args, + variant: 'dark', + }, + parameters: { + backgrounds: { default: 'dark' }, + }, +}; + +export const LabelWithIconLight: Story = { + decorators: [(storyFn) =>
{storyFn()}
], + render: (args) => ( + + CLI + Github Action + Config File + + ), + args: { + label: ( + <> + Filter config options + + + ), + }, +}; + +export const LabelWithIconDark: Story = { + ...LabelWithIconLight, + args: { + ...LabelWithIconLight.args, + variant: 'dark', + }, + parameters: { + backgrounds: { default: 'dark' }, + }, +}; + +export const DisabledItemsLight: Story = { + decorators: [(storyFn) =>
{storyFn()}
], + render: (args) => ( + + CLI + Github Action + Config File + + ), + args: LabelWithIconLight.args, +}; + +export const DisabledItemsDark: Story = { + ...DisabledItemsLight, + args: { + ...DisabledItemsLight.args, + variant: 'dark', + }, + parameters: { + backgrounds: { default: 'dark' }, + }, +}; + +export const CheckboxItemsLight: Story = { + decorators: [(storyFn) =>
{storyFn()}
], + render: (args) => ( + + CLI + Github Action + Config File + + ), + args: LabelWithIconLight.args, +}; + +export const CheckboxItemsDark: Story = { + ...CheckboxItemsLight, + args: { + ...CheckboxItemsLight.args, + variant: 'dark', + }, + parameters: { + backgrounds: { default: 'dark' }, + }, +}; + +// Simple closed +// Disabled +// Label with icon +// Checkbox item +// Checkbox Selected + +// export const WithIcon: Story = { +// args: { +// items: features, +// label: ( +// <> +// Filter +// +// +// ), +// }, +// }; + +// export const DarkClosed: Story = { +// args: { +// ...LightClosed.args, +// variant: 'dark', +// }, +// parameters: { +// backgrounds: { default: 'dark' }, +// }, +// }; + +// export const LightOpen: Story = { +// args: { +// label: 'Features', +// items: features, +// }, +// decorators: [(storyFn) =>
{storyFn()}
], +// play: async ({ canvasElement }) => { +// const canvas = within(canvasElement); +// const MenuButton = await canvas.findByRole('button', { +// name: 'Features', +// }); +// MenuButton.focus(); +// await userEvent.keyboard('{enter}'); +// }, +// }; + +// export const DarkOpen: Story = { +// args: { +// ...LightOpen.args, +// variant: 'dark', +// }, +// parameters: { +// backgrounds: { default: 'dark' }, +// }, +// play: LightOpen.play, +// }; diff --git a/src/DropdownMenu/DropdownMenu.tsx b/src/DropdownMenu/DropdownMenu.tsx new file mode 100644 index 0000000..64ad6a0 --- /dev/null +++ b/src/DropdownMenu/DropdownMenu.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { styled } from '@storybook/theming'; +import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; +import { color, spacing } from '../_tokens'; +import { DropdownTrigger } from './DropdownTrigger'; + +const DropdownMenuContent = styled(RadixDropdownMenu.Content)` + margin: 0; + background: ${color.white}; + border-radius: 4px; + padding: ${spacing[2]}; + min-width: 200px; + box-shadow: 0px 0px 15px ${color.blackTr05}, 0px 1px 2px ${color.blackTr10}; +`; + +interface DropdownProps { + variant?: 'light' | 'dark'; + label: React.ReactNode; + children: React.ReactNode; +} + +export const DropdownMenu = ({ + label, + variant, + children, + ...props +}: DropdownProps) => { + return ( + + {label} + + + + {children} + {/* {items.map((item) => ( + + ))} */} + + + + ); +}; diff --git a/src/DropdownMenu/DropdownTrigger.tsx b/src/DropdownMenu/DropdownTrigger.tsx new file mode 100644 index 0000000..9672469 --- /dev/null +++ b/src/DropdownMenu/DropdownTrigger.tsx @@ -0,0 +1,87 @@ +import React, { FC } from 'react'; +import { css, styled } from '@storybook/theming'; +import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; +import { color, fontWeight, spacing } from '../_tokens'; +import { typography } from '../_helpers'; + +export const buttonStyles = css` + all: unset; + display: flex; + align-items: center; + justify-content: center; + padding: 0 ${spacing[2]}; + outline: none; + user-select: none; + border-radius: 4px; + border: none; + height: ${spacing[8]}; + background: transparent; + gap: 6px; + text-decoration: none; + cursor: pointer; + transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out; + ${typography.body14} + font-weight: ${fontWeight.bold}; + + &:focus { + box-shadow: 0 0 0 2px rgba(30, 167, 253, 0.3); + background-color: rgba(30, 167, 253, 0.14); + } + + &:hover { + background-color: rgba(30, 167, 253, 0.14); + color: ${color.blue500}; + } +`; + +const TriggerButton = styled(RadixDropdownMenu.Trigger, { + shouldForwardProp: (propName) => + propName !== 'variant' && propName !== 'isActive', +})<{ + variant?: 'light' | 'dark'; + isActive?: boolean; +}>` + ${buttonStyles} + background-color: ${({ isActive, variant }) => { + if (isActive) { + return 'rgba(30, 167, 253, 0.07)'; + } + return variant === 'light' ? color.slate100 : color.slate800; + }}; + color: ${({ isActive, variant }) => { + if (isActive) return color.blue500; + if (variant === 'light') return color.slate500; + return color.white; + }}; + + &[data-state='open'] { + background-color: rgba(30, 167, 253, 0.14); + color: ${color.blue500}; + } + + &[data-state='open'] > .CaretDown { + transform: rotate(-180deg) translateY(0px); + } +`; + +const CaretDown = styled.div` + position: relative; + transform: translateY(2px); + transition: transform 250ms ease; +`; + +interface DesktopItemProps { + variant?: 'light' | 'dark'; + children?: React.ReactNode; +} + +export const DropdownTrigger: FC = ({ + children, + variant = 'light', +}) => { + return ( + <> + {children} + + ); +}; diff --git a/src/DropdownMenu/index.ts b/src/DropdownMenu/index.ts new file mode 100644 index 0000000..6e3ecb2 --- /dev/null +++ b/src/DropdownMenu/index.ts @@ -0,0 +1,2 @@ +export * from './DropdownMenu'; +export * from './DropdownItems'; diff --git a/src/index.ts b/src/index.ts index 36436a1..d6ae6d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,11 @@ export { Container, FullWidthContainer } from './Container'; export { Button } from './Button'; export { CustomerStoryHero } from './CustomerStoryHero'; export { Divider } from './Divider'; +export { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuItem, +} from './DropdownMenu'; export { Footer, FooterProps } from './Footer'; export { Grid } from './Grid'; export {