feat(joyent-ui-toolkit): Add dropdown component

This commit is contained in:
robertzlatarski 2017-06-15 19:44:04 +03:00 committed by Sérgio Ramos
parent edc5fcba87
commit 3fc72f667e
8 changed files with 186 additions and 24 deletions

View File

@ -1,40 +1,42 @@
import { css } from 'styled-components'; import { css } from 'styled-components';
import remcalc from 'remcalc'; import remcalc from 'remcalc';
const bounds = { export const breakpoints = {
small: { small: {
upper: remcalc(768) upper: 768
}, },
medium: { medium: {
upper: remcalc(1024), upper: 1024,
lower: remcalc(769) lower: 769
}, },
large: { large: {
upper: remcalc(1200), upper: 1200,
lower: remcalc(1025) lower: 1025
}, },
xlarge: { xlarge: {
lower: remcalc(1201) lower: 1201
} }
}; };
const screens = { const screens = {
// >= 768px // >= 768px
smallOnly: `only screen and (max-width: ${bounds.small.upper})`, smallOnly: `only screen and (max-width: ${remcalc(breakpoints.small.upper)})`,
small: `only screen and (min-width: ${bounds.small.upper}})`, small: `only screen and (min-width: ${remcalc(breakpoints.small.upper)})`,
// >= 1024px // >= 1024px
mediumOnly: `only screen and (min-width: ${bounds.medium.lower}) mediumOnly: `only screen and (min-width: ${remcalc(breakpoints.medium.lower)})
and (max-width: ${bounds.medium.upper})`, and (max-width: ${remcalc(breakpoints.medium.upper)})`,
mediumDown: `only screen and (max-width: ${bounds.medium.upper})`, mediumDown: `only screen and (max-width: ${remcalc(
medium: `only screen and (min-width: ${bounds.medium.lower})`, breakpoints.medium.upper
)})`,
medium: `only screen and (min-width: ${remcalc(breakpoints.medium.lower)})`,
// >= 1200px // >= 1200px
largeOnly: `only screen and (min-width: ${bounds.large.lower}) largeOnly: `only screen and (min-width: ${remcalc(breakpoints.large.lower)})
and (max-width: ${bounds.large.upper})`, and (max-width: ${remcalc(breakpoints.large.upper)})`,
largeDown: `only screen and (max-width: ${bounds.large.upper})`, largeDown: `only screen and (max-width: ${remcalc(breakpoints.large.upper)})`,
large: `only screen and (min-width: ${bounds.large.upper})`, large: `only screen and (min-width: ${remcalc(breakpoints.large.upper)})`,
xlarge: `only screen and (min-width: ${bounds.xlarge.lower}) xlarge: `only screen and (min-width: ${remcalc(breakpoints.xlarge.lower)})
and (max-width: ${bounds.xlarge.upper})`, and (max-width: ${remcalc(breakpoints.xlarge.upper)})`,
xlargeUp: `only screen and (min-width: ${bounds.xlarge.lower})` xlargeUp: `only screen and (min-width: ${remcalc(breakpoints.xlarge.lower)})`
}; };
const breakpoint = label => (...args) => css` const breakpoint = label => (...args) => css`
@ -64,5 +66,6 @@ export default {
largeDown, largeDown,
large, large,
xlarge, xlarge,
xlargeUp xlargeUp,
breakpoints
}; };

View File

@ -0,0 +1,131 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Select } from '../form';
import { Tooltip, TooltipButton as DropdownItem } from '../tooltip';
import Baseline from '../baseline';
import { small, smallOnly } from '../breakpoints';
import { ArrowIcon } from '../icons';
import remcalc from 'remcalc';
const StyledSelectList = styled(Tooltip)`
${smallOnly`
display: none;
`};
width: 100%;
ul {
position: relative;
display: block;
left: auto;
}
ul:after, ul:before {
left: 97%;
}
`;
const StyledSelect = styled(Select)`
${small`
option {
display: none;
}
`}
`;
const StyledArrowIcon = styled(ArrowIcon)`
${smallOnly`
display: none;
`};
position: absolute;
left: 97%;
top: 50%;
margin-left: -${remcalc(4.5)};
`;
const Container = styled.div`
position: relative;
`;
/**
* @example ./usage.md
*/
class Dropdown extends Component {
constructor(props) {
super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.dropdownOnChange = this.dropdownOnChange.bind(this);
this.dropdownOnBlur = this.dropdownOnBlur.bind(this);
this.state = {
isDroppedDown: false
};
}
componentDidMount() {
window.addEventListener('click', this.dropdownOnBlur);
}
componentWillUnmount() {
window.addEventListener('click', this.dropdownOnBlur);
}
toggleDropdown() {
this.setState(prevState => ({ isDroppedDown: !prevState.isDroppedDown }));
}
dropdownOnBlur(ev) {
if (
!ReactDOM.findDOMNode(this).contains(ev.target) &&
this.state.isDroppedDown
) {
this.toggleDropdown();
}
}
dropdownOnChange(ev) {
this.setState({ isDroppedDown: false });
if (!this.props.onChange) {
return;
}
this.props.onChange(ev.target.value);
}
render() {
const { data, placeholder, className, id, ...rest } = this.props;
return (
<Container className={className} id={id}>
<StyledSelect
defaultValue={placeholder}
onChange={this.dropdownOnChange}
onClick={this.toggleDropdown}
{...rest}
>
<option disabled value={placeholder}>{placeholder}</option>
{data.map((val, index) =>
<option value={val} key={index}>{val}</option>
)}
</StyledSelect>
<StyledArrowIcon onClick={this.toggleDropdown} />
{this.state.isDroppedDown &&
<StyledSelectList>
{data.map((val, index) =>
<DropdownItem
key={index}
value={val}
onClick={this.dropdownOnChange}
>
{val}
</DropdownItem>
)}
</StyledSelectList>}
</Container>
);
}
}
Dropdown.propTypes = {
data: PropTypes.arrayOf(PropTypes.string),
placeholder: PropTypes.string,
onChange: PropTypes.func
};
Dropdown.defaultProps = {
placeholder: 'Choose'
};
export default Baseline(Dropdown);

View File

@ -0,0 +1,10 @@
```
const Dropdown = require('./index').default;
const Label = require('../form/label').default;
<div style={{ position: 'relative', height: '175px' }}>
<Label>Service</Label>
<Dropdown placeholder="Choose" data={["Wordpress", "Nginx", "Percona"]}>
</Dropdown>
</div>
```

View File

@ -0,0 +1,7 @@
import Baseline from '../baseline';
// eslint-disable-next-line no-unused-vars
import React from 'react';
import ArrowIcon from './svg/icon_arrow.svg';
export default Baseline(ArrowIcon);

View File

@ -1,3 +1,4 @@
export { default as CloseIcon } from './close'; export { default as CloseIcon } from './close';
export { default as PlusIcon } from './plus'; export { default as PlusIcon } from './plus';
export { default as MinusIcon } from './minus'; export { default as MinusIcon } from './minus';
export { default as ArrowIcon } from './arrow';

View File

@ -18,6 +18,7 @@ export { default as CloseButton } from './close-button';
export { default as IconButton } from './icon-button'; export { default as IconButton } from './icon-button';
export { Tooltip, TooltipButton, TooltipDivider } from './tooltip'; export { Tooltip, TooltipButton, TooltipDivider } from './tooltip';
export { Dropdown } from './dropdown';
export { export {
borderRadius, borderRadius,

View File

@ -88,11 +88,19 @@ class Tooltip extends Component {
top = 'auto', top = 'auto',
left = 'auto', left = 'auto',
bottom = 'auto', bottom = 'auto',
right = 'auto' right = 'auto',
className,
...rest
} = this.props; } = this.props;
return ( return (
<StyledContainer top={top} left={left} bottom={bottom} right={right}> <StyledContainer
className={className}
top={top}
left={left}
bottom={bottom}
right={right}
{...rest}
>
<StyledList> <StyledList>
{children} {children}
</StyledList> </StyledList>

View File

@ -59,6 +59,7 @@ module.exports = {
'src/form/input.js', 'src/form/input.js',
'src/form/number-input.js', 'src/form/number-input.js',
'src/form/checkbox.js', 'src/form/checkbox.js',
'src/dropdown/index.js',
'src/form/radio.js', 'src/form/radio.js',
'src/form/select.js', 'src/form/select.js',
'src/form/toggle.js', 'src/form/toggle.js',