Skip to content

Commit

Permalink
feat: Dropdown component
Browse files Browse the repository at this point in the history
  • Loading branch information
steven11329 committed Sep 27, 2021
1 parent 89dec01 commit a0e5994
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 9 deletions.
22 changes: 13 additions & 9 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
]
}
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
typescript: {
check: false,
checkOptions: {},
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
54 changes: 54 additions & 0 deletions src/components/Dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from 'react';
import { Meta, Story } from '@storybook/react';
import Dropdown from './Dropdown';
import { DropDownItem, DropDownProps } from './Dropdown.type';

const list: DropDownItem[] = [
{
id: 'A001',
name: 'Distributor',
value: 'Distributor',
},
{
id: 'A002',
name: 'Distributor A',
value: 'Distributor-A',
},
{
id: 'A003',
name: 'Distributor B',
value: 'Distributor-B',
},
{
id: 'A004',
name: 'Distributor C',
value: 'Distributor-C',
},
];

export default {
title: 'Components/Dropdown',
component: Dropdown,
argTypes: {
onSelect: { action: 'onSelected' },
disabled: {
control: 'boolean',
},
},
} as Meta;

const Template: Story<DropDownProps> = (args) => <Dropdown {...args} />;

export const Default: Story<DropDownProps> = Template.bind({});

Default.args = {
placeholder: 'Select',
list,
};

export const Selected: Story<DropDownProps> = Template.bind({});

Selected.args = {
...Default.args,
selectedId: 'A004',
};
163 changes: 163 additions & 0 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import React, { useState, useEffect, useRef } from 'react';
import classnames from 'classnames';
import makeStyles from '@material-ui/core/styles/makeStyles';
import KeyboardArrowDown from '@material-ui/icons/KeyboardArrowDown';
import KeyboardArrowUp from '@material-ui/icons/KeyboardArrowUp';
import Popper from '@material-ui/core/Popper';
import { DropDownItem, DropDownProps } from './Dropdown.type';
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import CheckSvg from '../../assets/image/svg/check.svg';

const useStyles = makeStyles(
(theme) => ({
select: {
...theme.text.Subtitle_16_Med,
userSelect: 'none',
cursor: 'pointer',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
color: theme.color.secondary.$80,
backgroundColor: '#FFF',
padding: '8px 0px 8px 16px',
borderRadius: 4,
},
'select-empty': {
color: theme.color.secondary.$60,
},
'select--disabled': {
opacity: 0.3,
pointerEvents: 'none',
},
icon: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: 40,
height: 24,
},
list: {
backgroundColor: '#FFF',
margin: '8px auto',
borderRadius: 4,
boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.1)',
},
item: {
...theme.text.Subtitle_16_Med,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
lineHeight: 2.5,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, .05)',
},
},
itemIcon: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: 40,
height: 40,
},
}),
{ name: 'Dropdown' },
);

function Dropdown({
className,
list,
listClassName,
itemClassName,
placeholder,
selectedId,
disabled,
onSelect,
}: DropDownProps): JSX.Element {
const selectRef = useRef<HTMLDivElement>(null);
const classes = useStyles();
const [selectedItem, setSelectedItem] = useState<DropDownItem | null>(null);
const [isOpen, setIsOpen] = useState(false);

useEffect(() => {
if (selectedId && selectedId !== selectedItem?.id) {
for (let i = 0; i < list.length; i++) {
if (selectedId === list[i].id) {
setSelectedItem(list[i]);
break;
}
}
}
}, [selectedId]);

const handleOnClickSelect = () => {
setIsOpen(true);
};

const handleOnClickAway = () => {
setIsOpen(false);
};

const handleOnClick = (item: DropDownItem) => {
setIsOpen(false);
setSelectedItem(item);
onSelect(item.value, item);
};

const items = list.map((item) => (
<div
key={`dropdown-item-${item.id}`}
className={classnames(classes.item, itemClassName)}
onClick={() => handleOnClick(item)}
>
<div className={classes.itemIcon}>
{selectedItem?.id === item.id && <img src={CheckSvg} />}
</div>
{item.name}
</div>
));

return (
<>
<div
ref={selectRef}
className={classnames(
classes.select,
{
[classes['select-empty']]: !selectedItem,
},
className,
{
[classes['select--disabled']]: disabled,
},
)}
onClick={handleOnClickSelect}
>
{selectedItem?.name ?? placeholder}
<div className={classes.icon}>
{isOpen ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
</div>
</div>
<Popper
anchorEl={selectRef.current}
open={isOpen}
placement="bottom"
popperRef={(ref) => {
if (listClassName) {
ref?.popper.classList.add(classnames(listClassName));
}
}}
>
<ClickAwayListener onClickAway={handleOnClickAway}>
<div
className={classes.list}
style={{ width: selectRef.current?.offsetWidth ?? 'auto' }}
>
{items}
</div>
</ClickAwayListener>
</Popper>
</>
);
}

export default Dropdown;
40 changes: 40 additions & 0 deletions src/components/Dropdown/Dropdown.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export interface DropDownItem {
id: string;
name: string;
value: string | number;
}

export interface DropDownProps {
/**
* For adjustment Dropdown styles
*/
className?: string;
/**
* Item list
*/
list: DropDownItem[];
/**
* Placeholder
*/
placeholder?: string;
/**
* For adjustment list styles
*/
listClassName?: string;
/**
* For adjustment item styles
*/
itemClassName?: string;
/**
* Specify item
*/
selectedId?: string;
/**
* Trigger when select a item
*/
onSelect: (value: DropDownItem['value'], item: DropDownItem) => void;
/**
* Disable dropdown
*/
disabled?: boolean;
}
1 change: 1 addition & 0 deletions src/components/Dropdown/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Dropdown';
2 changes: 2 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export { default as PhoneTextField } from './TextField/PhoneTextField';
export { default as PasswordTextField } from './TextField/PasswordTextField';
export { default as LanguageButton } from './Button/LanguageButton';
export { default as Searchbar } from './Searchbar';
export { default as DialogButton } from './Button/DialogButton';
export { default as Dropdown } from './Dropdown';

0 comments on commit a0e5994

Please sign in to comment.