Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dropdown] - update component #16

Merged
15 changes: 1 addition & 14 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,5 @@
}

.dropdown {
@apply absolute overflow-x-auto mt-1 rounded bg-gray-80 transition-colors whitespace-nowrap p-1 font-roboto text-gray-30 text-xs font-semibold;

& > div {
@apply flex items-center mb-1 cursor-pointer rounded px-1.5;
height: 33px;

&:hover {
@apply bg-gray-70 text-gray-10;
}

&:last-child {
@apply mb-0;
}
}
@apply absolute overflow-x-auto mt-1 transition-colors;
}
53 changes: 32 additions & 21 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,51 @@ import { Story } from '@storybook/react';

import React, { ComponentProps } from 'react';

import { Menu } from '../Menu/Menu';
import { MenuItem } from '../Menu/components/MenuItem/MenuItem';
import { Dropdown } from './Dropdown';
import { DropdownMode, DropdownSize } from './Dropdown.types';

export default {
title: 'Molecule/Dropdown',
component: Dropdown,
};

const Template: Story<ComponentProps<typeof Dropdown>> = args => (
<Dropdown {...args} />
<div className="flex justify-center">
<Dropdown {...args} />
</div>
);

export const Basic = Template.bind({});
Basic.args = {
text: 'Dropdown Button',
children: (
<div>
<div
className="my-2"
onClick={() => alert('Click on the Dropdown Item 1')}
>
Dropdown Item 1
</div>
<div
className="my-2"
onClick={() => alert('Click on the Dropdown Item 2')}
>
Dropdown Item 2
</div>
<div
className="my-2"
onClick={() => alert('Click on the Dropdown Item 3')}
>
Dropdown Item 3
</div>
</div>
<Menu>
<MenuItem text="Dropdown Item 1" />
<MenuItem text="Dropdown Item 2" />
<MenuItem text="Dropdown Item 3" />
</Menu>
),
size: DropdownSize.large,
mode: DropdownMode.sameWidth,
};

const AdvancedTemplate: Story<ComponentProps<typeof Dropdown>> = args => {
return <Dropdown {...args} />;
};

export const Interactive = AdvancedTemplate.bind({});
Interactive.args = {
text: 'Mode control',
size: DropdownSize.large,
className: 'm-auto',
mode: DropdownMode.center,
children: (
<Menu>
<MenuItem text="Dropdown Menu Item 1" />
<MenuItem text="Dropdown Menu Item 2" />
<MenuItem text="Dropdown Menu Item 3" />
</Menu>
),
};
33 changes: 33 additions & 0 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import React from 'react';

import { Dropdown } from './Dropdown';

describe('Dropdown', () => {
test('renders a dropdown', () => {
const data = <div>Change text</div>;
render(<Dropdown text="Dropdown" children={data} />);

const dropdown = screen.getByRole('button');
expect(dropdown).toHaveTextContent('Dropdown');
});

test('renders an open dropdown', () => {
const data = <div>Option</div>;
const { getByText } = render(<Dropdown text="Dropdown" children={data} />);
userEvent.click(getByText('Dropdown'));
const dropdownOption = screen.queryByText('Option');
expect(dropdownOption).toBeInTheDocument();
});

test('should close dropdown after click outside', () => {
const data = <div>Option</div>;
const { getByText } = render(<Dropdown text="Dropdown" children={data} />);
userEvent.click(getByText('Dropdown'));
userEvent.click(document.body);
const dropdownOption = screen.queryByText('Option');
expect(dropdownOption).not.toBeInTheDocument();
});
});
30 changes: 18 additions & 12 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import React, {

import classNames from 'classnames';

import { Icon } from '../../1_atoms/Icon/Icon';
import { ARROW_DOWN } from '../../1_atoms/Icon/iconNames';
import { Portal } from '../../1_atoms/Portal/Portal';
import { useOnClickOutside } from '../../hooks/useOnClickOutside';
import { Nullable } from '../../types';
import styles from './Dropdown.module.css';
import { DropdownColor, DropdownCoords, DropdownMode } from './Dropdown.types';
import { DropdownCoords, DropdownMode, DropdownSize } from './Dropdown.types';
import { getDropdownPositionStyles } from './Dropdown.utils';

type DropdownProps = {
text: ReactNode;
children: ReactNode;
mode?: DropdownMode;
color?: DropdownColor;
size?: DropdownSize;
onOpen?: () => void;
onClose?: () => void;
className?: string;
Expand All @@ -32,7 +34,7 @@ export const Dropdown: React.FC<DropdownProps> = ({
text,
children,
mode = DropdownMode.sameWidth,
color = DropdownColor.gray3,
size = DropdownSize.large,
onOpen,
onClose,
className,
Expand Down Expand Up @@ -74,10 +76,12 @@ export const Dropdown: React.FC<DropdownProps> = ({
return getDropdownPositionStyles(coords, mode);
}, [coords, mode]);

const classNameComplete = useMemo(
const classNamesComplete = useMemo(
() =>
classNames(styles.button, color, className, { [styles.isOpen]: isOpen }),
[color, className, isOpen],
classNames(styles.button, styles[size], className, {
[styles.isOpen]: isOpen,
}),
[size, className, isOpen],
);

const useClickedOutside = useCallback(() => {
Expand All @@ -99,24 +103,26 @@ export const Dropdown: React.FC<DropdownProps> = ({
return (
<>
<button
className={classNames(classNameComplete)}
className={classNames(classNamesComplete)}
data-action-id={dataActionId}
onClick={onButtonClick}
type="button"
ref={buttonRef}
>
{text}
<span
className={classNames(styles.iconArrow, {
'transform rotate-180 rounded-b-none': isOpen,
<Icon
icon={ARROW_DOWN}
size={10}
className={classNames('transition-transform ml-2', {
'transform rotate-180': isOpen,
})}
></span>
/>
</button>

{isOpen && (
<Portal target="body">
<div
className={classNames(styles.dropdown, dropdownClassName, color)}
className={classNames(styles.dropdown, dropdownClassName)}
style={dropdownStyles}
ref={dropdownRef}
>
Expand Down
7 changes: 3 additions & 4 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ export enum DropdownMode {
sameWidth = 'sameWidth',
}

export enum DropdownColor {
gray2 = 'bg-gray-2',
gray3 = 'bg-gray-3',
gray4 = 'bg-gray-4',
export enum DropdownSize {
large = 'large',
small = 'small',
}

export type DropdownCoords = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
.host {
@apply list-none;
}
.host:last-child .button{
@apply mb-0;
}
.host:first-child .button{
@apply mt-0;
}
.host .button {
@apply flex text-left items-center w-full px-2.5 py-1 cursor-pointer rounded transition-colors my-1;

@media (min-width: 1536px) {
@apply px-4;
}
}
& .button {
@apply flex text-left items-center w-full px-2.5 py-1 cursor-pointer rounded transition-colors my-1;

.host .button.disabled {
@apply opacity-50 cursor-not-allowed;
}
&.disabled {
@apply opacity-50 cursor-not-allowed;
}

.host .button.active {
@apply opacity-100;
}
&.active {
@apply opacity-100;
}

.host .button.active,
.host .button:not(.disabled):hover {
@apply bg-gray-70 text-gray-10;
}
&.active,
&:not(.disabled):hover {
@apply bg-gray-70 text-gray-10;
}

.host .button .text {
@apply text-sm font-semibold leading-none text-gray-10 flex items-center;
line-height: 25px;
}
& .text {
@apply font-semibold flex items-center;
line-height: 25px;
}

& .label {
@apply text-gray-30 font-normal text-tiny inline-block leading-none pointer-events-none;
}
}

.host .button .label {
@apply text-gray-30 font-normal text-tiny inline-block leading-none pointer-events-none;
&:last-child {
& .button{
@apply mb-0;
}
}

&:first-child {
& .button{
@apply mt-0;
}
}
}