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
4 changes: 3 additions & 1 deletion apps/frontend/src/app/3_organisms/EthersProviderTest.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Chain, getProvider } from '@sovryn/ethers-provider';
import React, { useEffect, useState } from 'react';

import { Chain, getProvider } from '@sovryn/ethers-provider';

import { network } from '../../utils/network';

const ProviderItem = ({ chain }: { chain: Chain }) => {
Expand Down
28 changes: 19 additions & 9 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.module.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
.button {
@apply flex items-center justify-between relative h-full min-h-8 min-w-32 rounded-lg text-base leading-none py-1 px-4 cursor-pointer select-none transition-opacity hover:bg-opacity-50;
}
.button.isOpen {
@apply rounded-b-none;
}
.button .iconArrow {
@apply w-4 h-4 ml-2 transition-transform bg-contain bg-center bg-no-repeat;
background-image: url("data:image/svg+xml,%3Csvg width='23' height='15' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' viewBox='0 0.399 23 14.203' fill='%23e8e8e8'%3E%3Csvg xmlns='http://www.w3.org/2000/svg' width='23' height='15' viewBox='0.75 0.837 21.5 13.276'%3E%3Cpath paint-order='stroke fill markers' fill-rule='evenodd' d='M19.724.837L11.5 9.043 3.276.837.75 3.363l10.75 10.75 10.75-10.75L19.724.837z'/%3E%3C/svg%3E%3C/svg%3E");
@apply flex items-center bg-gray-50 justify-between relative h-full min-w-20 whitespace-nowrap font-roboto text-gray-10 text-xs font-semibold px-2.5 leading-none cursor-pointer select-none transition-colors border rounded border-gray-50;

&.isOpen {
@apply bg-gray-80 border-gray-80;
}

&:hover {
@apply bg-gray-60 border-gray-50;
}

&.small {
@apply h-8;
}

&.large {
@apply h-10;
}
}

.dropdown {
@apply absolute overflow-x-auto rounded-b-lg min-h-8 py-2 px-4;
@apply absolute overflow-x-auto mt-1 transition-colors;
}
60 changes: 38 additions & 22 deletions packages/ui/src/2_molecules/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,57 @@
import { Story } from '@storybook/react';

import React, { ComponentProps } from 'react';
import React, { ComponentProps, useState } 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">
<div className="mr-10">
<p>Small Size</p>
<Dropdown {...args} size={DropdownSize.small} />
</div>
<div>
<p>Large size</p>
<Dropdown {...args} />
</div>
</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>
),
};

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 text-gray-10 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;
}
}
}