Skip to content

Commit

Permalink
Add Dropdown shared component
Browse files Browse the repository at this point in the history
  • Loading branch information
nothing9537 committed Oct 16, 2023
1 parent 79e72bd commit c360d63
Show file tree
Hide file tree
Showing 15 changed files with 469 additions and 105 deletions.
2 changes: 1 addition & 1 deletion src/app/styles/themes/blue.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--inverted-bg-color: #2c3941;
--primary-color: #F2F3F8;
--secondary-color: #3d9ccf;
--inverted-primary-color: #f7f7f7;
--inverted-primary-color: #b9b9b9;
--inverted-secondary-color: #54a5d1;

// * Skeleton
Expand Down
24 changes: 2 additions & 22 deletions src/shared/assets/icons/ArticlesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,8 @@ import { FC, HTMLAttributes } from 'react';

export const ArticlesPageIcon: FC<HTMLAttributes<SVGElement>> = ({ ...props }) => {
return (
<svg {...props} width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_16_4)">
<path d="M8.33333 6.66667H13.3333V7.77778H8.33333V6.66667Z" />
<path d="M8.33333 8.88889H13.3333V10H8.33333V8.88889Z" />
<path d="M8.33333 11.1111H13.3333V12.2222H8.33333V11.1111Z" />
<path d="M8.33333 13.3333H13.3333V14.4444H8.33333V13.3333Z" />
<path d="M6.11111 4.44444H7.22222V5.55555H6.11111V4.44444Z" />
<path d="M6.11111 6.66667H7.22222V7.77778H6.11111V6.66667Z" />
<path d="M6.11111 8.88889H7.22222V10H6.11111V8.88889Z" />
<path d="M6.11111 11.1111H7.22222V12.2222H6.11111V11.1111Z" />
<path d="M6.11111 13.3333H7.22222V14.4444H6.11111V13.3333Z" />
<path
d="M8.33333 4.44444V5.55555H13.1444C12.9258 5.21155 12.7592 4.83714 12.65 4.44444H8.33333Z"
/>
<path
d="M15.5555 7.34445V17.7778H4.44444V2.22223H12.6556C12.763 1.82984 12.9278 1.45544 13.1444 1.11111H4.44444C4.14975 1.11111 3.86714 1.22818 3.65877 1.43655C3.45039 1.64493 3.33333 1.92754 3.33333 2.22223V17.7778C3.33333 18.0725 3.45039 18.3551 3.65877 18.5635C3.86714 18.7718 4.14975 18.8889 4.44444 18.8889H15.5555C15.8502 18.8889 16.1328 18.7718 16.3412 18.5635C16.5496 18.3551 16.6667 18.0725 16.6667 17.7778V7.5C16.291 7.49855 15.9172 7.44622 15.5555 7.34445Z"
/>
<path
d="M16.6667 6.11111C18.2008 6.11111 19.4444 4.86746 19.4444 3.33333C19.4444 1.79921 18.2008 0.555557 16.6667 0.555557C15.1325 0.555557 13.8889 1.79921 13.8889 3.33333C13.8889 4.86746 15.1325 6.11111 16.6667 6.11111Z"
/>
</g>
<svg {...props} xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path d="M18.8889 20H1.11111C0.816426 20 0.533811 19.8946 0.325437 19.7071C0.117063 19.5196 0 19.2652 0 19V1C0 0.734784 0.117063 0.48043 0.325437 0.292893C0.533811 0.105357 0.816426 0 1.11111 0H18.8889C19.1836 0 19.4662 0.105357 19.6746 0.292893C19.8829 0.48043 20 0.734784 20 1V19C20 19.2652 19.8829 19.5196 19.6746 19.7071C19.4662 19.8946 19.1836 20 18.8889 20ZM17.7778 18V2H2.22222V18H17.7778ZM4.44444 4H8.88889V8H4.44444V4ZM4.44444 10H15.5556V12H4.44444V10ZM4.44444 14H15.5556V16H4.44444V14ZM11.1111 5H15.5556V7H11.1111V5Z" />
</svg>

);
};
1 change: 1 addition & 0 deletions src/shared/types/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Direction = 'top left' | 'top right' | 'bottom left' | 'bottom right';
2 changes: 1 addition & 1 deletion src/shared/ui/Button/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
padding: 0;
border: none;
background: none;
outline: none;
outline-color: transparent;
}

.clearInverted {
Expand Down
75 changes: 75 additions & 0 deletions src/shared/ui/Dropdown/Dropdown.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.Dropdown {
position: relative;
}

.trigger {
cursor: pointer;
outline-color: transparent;
margin: 0;
padding: 0;
border: none;
background: none;
}

.options {
background: var(--card-bg);
position: absolute;
z-index: 10;
border-radius: 0 0 4px 4px;
border: 2px solid var(--primary-color);
display: flex;
flex-direction: column;
overflow: hidden;
}

.option {
padding: 8px 24px;
padding-left: 12px;
width: 100%;
border: none;
background: none;
outline-color: transparent;
color: var(--primary-color);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
gap: 8px;
}

.top-rounding {
border-radius: 12px 12px 0 0;
}

.bottom-rounding {
border-radius: 0 0 12px 12px;
}

.active {
background: var(--inverted-primary-color) ;
}

.disabled {
opacity: .5;
}

.bottom-right {
top: 100%;
right: 0;
}

.bottom-left {
top: 100%;
left: 0;
}

.top-right {
bottom: 100%;
right: 0;
}

.top-left {
bottom: 100%;
left: 0;
}
112 changes: 112 additions & 0 deletions src/shared/ui/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AboutPageIcon, ArticlesPageIcon } from 'shared/assets/icons';
import { action } from '@storybook/addon-actions';
import { Button } from '../Button';
import { Dropdown } from './Dropdown';

const meta: Meta<typeof Dropdown> = {
title: 'shared/Dropdown',
component: Dropdown,
tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof Dropdown>;

export const Root: Story = {
args: {
component: (
<Button>
Trigger Button
</Button>
),
items: [
{
// ItemIcon: AboutPageIcon,
label: 'Some label 1',
value: '1',
action: () => action('Action 1'),
},
{
// ItemIcon: ArticlesPageIcon,
label: 'Some label 2',
value: '2',
action: () => action('Action 2'),
},
],
},
};

export const ItemsWithIcons: Story = {
args: {
component: (
<Button>
Trigger Button
</Button>
),
items: [
{
ItemIcon: AboutPageIcon,
label: 'Some label 1',
value: '1',
action: (index) => action(`Action ${index}`),
},
{
ItemIcon: ArticlesPageIcon,
label: 'Some label 2',
value: '2',
action: (index) => action(`Action ${index}`),
},
],
},
};

export const ItemWithHref: Story = {
args: {
component: (
<Button>
Trigger Button
</Button>
),
items: [
{
ItemIcon: AboutPageIcon,
label: 'Some label 1',
value: '1',
action: (index) => action(`Action ${index}`),
href: '/some-link',
},
{
ItemIcon: ArticlesPageIcon,
label: 'Some label 2',
value: '2',
action: (index) => action(`Action ${index}`),
},
],
},
};

export const DisabledItem: Story = {
args: {
component: (
<Button>
Trigger Button
</Button>
),
items: [
{
ItemIcon: AboutPageIcon,
label: 'Some label 1',
value: '1',
action: (index) => action(`Action ${index}`),
disabled: true,
},
{
ItemIcon: ArticlesPageIcon,
label: 'Some label 2',
value: '2',
action: (index) => action(`Action ${index}`),
},
],
},
};
86 changes: 86 additions & 0 deletions src/shared/ui/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { FC, Fragment, ReactNode, useCallback } from 'react';
import { Menu } from '@headlessui/react';
import { classNames, Mods } from 'shared/lib/classNames/classNames';
import { Direction } from 'shared/types/ui';
import { Icon } from '../Icon';
import { AppLink } from '../AppLink';
import cls from './Dropdown.module.scss';

type DropdownAction = (index?: number) => void;

export interface DropdownItem {
ItemIcon?: FC;
action?: DropdownAction;
label: string;
value?: any;
disabled?: boolean;
href?: string;
}

interface DropdownProps {
className?: string;
items: DropdownItem[];
component: ReactNode;
onChange?: (v: any) => void;
position?: Direction;
}

const positionMapper: Record<Direction, string> = {
'bottom left': cls['bottom-left'],
'bottom right': cls['bottom-right'],
'top left': cls['top-left'],
'top right': cls['top-right'],
};

export const Dropdown: FC<DropdownProps> = ({ className, items, component, onChange, position = 'bottom left' }) => {
const onChangeHandler = useCallback((value: any, index: number, action?: DropdownAction) => () => {
onChange?.(value);
action?.(index);
}, [onChange]);

const optionsMods: Mods = {
[cls['top-rounding']]: position === 'top left' || position === 'top right',
[cls['bottom-rounding']]: position === 'bottom left' || position === 'bottom right',
};

return (
<Menu as="div" className={classNames(cls.Dropdown, {}, [className])}>
<Menu.Button as="button" className={cls.trigger}>
{component}
</Menu.Button>
<Menu.Items className={classNames(cls.options, optionsMods, [positionMapper[position]])}>
{items.map((option, index) => (
<Menu.Item as={Fragment} key={option.value + index} disabled={option.disabled}>
{({ active, disabled }) => {
const mods: Mods = { [cls.active]: active, [cls.disabled]: disabled };

return (option.href
? (
<AppLink
to={option.href}
className={classNames(cls.option, mods)}
onClick={onChangeHandler(option.value, index, option.action)}
>
{option.ItemIcon && <Icon SVG={<option.ItemIcon />} />}
{option.label}
</AppLink>
)
: (
<button
type="button"
className={classNames(cls.option, mods)}
onClick={onChangeHandler(option.value, index, option.action)}
disabled={option.disabled}
>
{option.ItemIcon && <Icon SVG={<option.ItemIcon />} />}
{option.label}
</button>
)
);
}}
</Menu.Item>
))}
</Menu.Items>
</Menu>
);
};
1 change: 1 addition & 0 deletions src/shared/ui/Dropdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Dropdown } from './Dropdown';
6 changes: 6 additions & 0 deletions src/shared/ui/Icon/Icon.module.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
.Icon {
width: fit-content;
height: fit-content;
display: flex;
align-items: center;
justify-content: center;

svg {
fill: var(--primary-color);

Expand Down
2 changes: 1 addition & 1 deletion src/shared/ui/Input/Input.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
width: 100%;

&:focus {
outline: none;
outline-color: transparent;
}
}

Expand Down
Loading

0 comments on commit c360d63

Please sign in to comment.