Skip to content
This repository has been archived by the owner on Nov 4, 2019. It is now read-only.

Commit

Permalink
Merge pull request #112 from kids-first/add-dropdown
Browse files Browse the repository at this point in the history
✨ Add dropdown menu component
  • Loading branch information
bdolly authored Apr 26, 2019
2 parents 6df027b + cff366c commit a358cf4
Show file tree
Hide file tree
Showing 7 changed files with 2,122 additions and 0 deletions.
1 change: 1 addition & 0 deletions .storybook/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ let req = {
stats: './src/components/Stats/Stats.story.jsx',
navigation: './src/components/SecondaryNav/SecondaryNav.story.jsx',
avatar: './src/components/Avatar/Avatar.story.jsx',
dropdown: './src/components/Dropdown/Dropdown.story.jsx',
};
const storyReqs = require.context('../', true, /^.*\.story\.jsx$/);

Expand Down
61 changes: 61 additions & 0 deletions src/components/Dropdown/Dropdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.Dropdown {
min-width: 200px;
@apply inline-block relative;
&:active,
&:focus,
&--open {
.Dropdown--container {
@apply block;
&:hover {
@apply block;
}
}
}
}

.Dropdown--container {
@apply hidden w-full absolute;
padding-top: 20px;
z-index: 100;
top: 0;

&:hover {
@apply block;
}
}

.Dropdown--button {
@apply w-full text-pink no-underline flex justify-center;
.Icon {
@apply text-pink;
}

&:focus {
& + .Dropdown--container {
@apply block;
}
}
}

.Dropdown--list {
margin: auto;
width: 200px;
@apply list-reset my-8 py-8 shadow-md rounded-sm flex flex-col bg-white;
li {
@apply p-0;
}
}

.Dropdown--item {
@apply px-20 py-12 text-sm text-left border-l-2 border-white text-black no-underline;
.Icon {
@apply text-grey mr-12;
}
&:hover,
&:focus {
@apply text-pink border-pink;
.Icon {
@apply text-pink;
}
}
}
79 changes: 79 additions & 0 deletions src/components/Dropdown/Dropdown.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import propTypes from 'prop-types';
import classes from 'classnames';
import Icon from '../Icon/Icon';
/**
* The dropdown menu allow user to customize the button text, icon and action.
*/
const Dropdown = ({ className, items, children, open }) => {
const dropdownClass = classes('Dropdown', { 'Dropdown--open': open }, className);
return (
<div className={dropdownClass}>
<button type="button" className="Dropdown--button" tabIndex="0">
{children}
<Icon className="mt-8 ml-12" width={16} height={16} kind="chevron-down" />
</button>
<div className="Dropdown--container">
<ul className="Dropdown--list">
{items.map(item => (
<li key={item.value}>
{item.type === 'button' ? (
<button
onClick={item.onClick}
type="button"
className="Dropdown--item"
tabIndex="0"
>
{item.icon && <Icon width={12} height={12} kind={item.icon} />}
{item.value}
</button>
) : (
<a
href={item.href}
onClick={item.onClick}
className="Dropdown--item inline-block"
tabIndex="0"
>
{item.icon && <Icon width={12} height={12} kind={item.icon} />}
{item.value}
</a>
)}
</li>
))}
</ul>
</div>
</div>
);
};

Dropdown.propTypes = {
/** Any additional classes to be applied to avater */
className: propTypes.string,
/** The label on dropdown button */
children: propTypes.node,
/** Array of dropdown objects to display */
items: propTypes.arrayOf(
propTypes.shape({
/** The icon used for the dropdown */
icon: propTypes.string,
/** The label to describe the dropdown */
value: propTypes.string,
/** The action to perform when the dropdown button is clicked */
onClick: propTypes.func,
/** type of DOM element to render (defaults to link) */
type: propTypes.oneOf(['button', 'link']),
/** if link, location of link */
href: propTypes.string,
}),
),
/** Indicating if the dropdown menu is open */
open: propTypes.bool,
};
Dropdown.defaultProps = {
className: null,
children: null,
items: [],
open: false,
};

export default Dropdown;
93 changes: 93 additions & 0 deletions src/components/Dropdown/Dropdown.story.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import { text, boolean } from '@storybook/addon-knobs';
import { generateVariantsFor } from '../../utils/propsVariants';
import Dropdown from './Dropdown';
import Icon from '../Icon/Icon';

const stories = storiesOf('Dropdown', module);

stories.add(
'Dropdown',
() => {
const items = [
{
value: 'Data Resource Protal',
icon: 'file-size',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Website',
icon: 'website',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Studies and Access',
icon: 'study',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Support',
icon: 'info',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Contact',
icon: 'email',
onClick: e => {
e.preventDefault();
},
type: 'link',
href: '/',
},
];
const variants = generateVariantsFor({
items: [[], items],
open: [true, false, null],
});
return (
<section>
<Dropdown className="hide-in-tests" items={items} open={boolean('Open', false)}>
<span>
<Icon className="mr-12" width={16} height={16} kind="resources" />
{text('Dropdown Label', 'Resources')}
</span>
</Dropdown>
{process.env.STORYBOOK_PERCY_ENV ? (
<div className="show-in-tests" style={{ width: 1280 }}>
{variants.map(props => (
<div className="float-left m-20" style={{ height: 300, width: '25%' }}>
<Dropdown {...props}>
<span>
<Icon className="mr-12" width={16} height={16} kind="resources" />
resources
</span>
</Dropdown>
</div>
))}
</div>
) : null}
</section>
);
},
{
info: {
text: `
Displays dropdown menu
`,
},
},
);
68 changes: 68 additions & 0 deletions src/components/Dropdown/__tests__/Dropdown-test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { toPairs } from 'lodash';
import { generateVariantsFor } from '../../../utils/propsVariants';
import Dropdown from '../Dropdown';
import Icon from '../../Icon/Icon';

const items = [
{
value: 'Data Resource Protal',
icon: 'file-size',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Website',
icon: 'website',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Studies and Access',
icon: 'study',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Support',
icon: 'info',
onClick: e => {
e.preventDefault();
},
type: 'button',
},
{
value: 'Contact',
icon: 'email',
onClick: e => {
e.preventDefault();
},
type: 'link',
href: '/',
},
];
const variants = generateVariantsFor({
items: [[], items],
open: [true, false, null],
children: [
<span>
<Icon className="mr-12" width={16} height={16} kind="resources" />
resources
</span>,
'Foobar no comp',
],
});

variants.map(props => {
it(`Dropdown ${toPairs(props).map(prop => `${prop[0]}:${prop[1]}, `)} renders correctly`, () => {
const tree = renderer.create(<Dropdown {...props} />).toJSON();
expect(tree).toMatchSnapshot();
});
});
Loading

0 comments on commit a358cf4

Please sign in to comment.