Skip to content

Commit

Permalink
dropdown component
Browse files Browse the repository at this point in the history
  • Loading branch information
winkerVSbecks committed Aug 1, 2024
1 parent b1dfe1f commit d68a2b8
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/DropdownMenu/DropdownItems.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<CheckboxItem {...props}>
<ItemIndicator>
<Icon name="check" size={10} />
</ItemIndicator>
{children}
</CheckboxItem>
);

export const DropdownMenuItem = styled(RadixDropdownMenu.Item)`
${itemStyles}
`;
175 changes: 175 additions & 0 deletions src/DropdownMenu/DropdownMenu.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof DropdownMenu> = {
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<typeof DropdownMenu>;

export const ClosedLight: Story = {
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItem>CLI</DropdownMenuItem>
<DropdownMenuItem>Github Action</DropdownMenuItem>
<DropdownMenuItem>Config File</DropdownMenuItem>
</DropdownMenu>
),
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) => <div style={{ height: '400px' }}>{storyFn()}</div>],
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItem>CLI</DropdownMenuItem>
<DropdownMenuItem>Github Action</DropdownMenuItem>
<DropdownMenuItem>Config File</DropdownMenuItem>
</DropdownMenu>
),
args: {
label: (
<>
Filter config options
<Icon name="filter" aria-hidden size={12} />
</>
),
},
};

export const LabelWithIconDark: Story = {
...LabelWithIconLight,
args: {
...LabelWithIconLight.args,
variant: 'dark',
},
parameters: {
backgrounds: { default: 'dark' },
},
};

export const DisabledItemsLight: Story = {
decorators: [(storyFn) => <div style={{ height: '400px' }}>{storyFn()}</div>],
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuItem>CLI</DropdownMenuItem>
<DropdownMenuItem disabled>Github Action</DropdownMenuItem>
<DropdownMenuItem>Config File</DropdownMenuItem>
</DropdownMenu>
),
args: LabelWithIconLight.args,
};

export const DisabledItemsDark: Story = {
...DisabledItemsLight,
args: {
...DisabledItemsLight.args,
variant: 'dark',
},
parameters: {
backgrounds: { default: 'dark' },
},
};

export const CheckboxItemsLight: Story = {
decorators: [(storyFn) => <div style={{ height: '400px' }}>{storyFn()}</div>],
render: (args) => (
<DropdownMenu {...args}>
<DropdownMenuCheckboxItem checked>CLI</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Github Action</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem checked>Config File</DropdownMenuCheckboxItem>
</DropdownMenu>
),
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
// <Icon name="filter" aria-hidden size={12} />
// </>
// ),
// },
// };

// export const DarkClosed: Story = {
// args: {
// ...LightClosed.args,
// variant: 'dark',
// },
// parameters: {
// backgrounds: { default: 'dark' },
// },
// };

// export const LightOpen: Story = {
// args: {
// label: 'Features',
// items: features,
// },
// decorators: [(storyFn) => <div style={{ height: '400px' }}>{storyFn()}</div>],
// 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,
// };
45 changes: 45 additions & 0 deletions src/DropdownMenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<RadixDropdownMenu.Root>
<DropdownTrigger variant={variant}>{label}</DropdownTrigger>

<RadixDropdownMenu.Portal>
<DropdownMenuContent loop align="start" sideOffset={8} {...props}>
{children}
{/* {items.map((item) => (
<RadixDropdownMenu.Item
key={item.id}
asChild
></RadixDropdownMenu.Item>
))} */}
</DropdownMenuContent>
</RadixDropdownMenu.Portal>
</RadixDropdownMenu.Root>
);
};
87 changes: 87 additions & 0 deletions src/DropdownMenu/DropdownTrigger.tsx
Original file line number Diff line number Diff line change
@@ -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<DesktopItemProps> = ({
children,
variant = 'light',
}) => {
return (
<>
<TriggerButton variant={variant}>{children}</TriggerButton>
</>
);
};
2 changes: 2 additions & 0 deletions src/DropdownMenu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './DropdownMenu';
export * from './DropdownItems';
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit d68a2b8

Please sign in to comment.