Add dropdown component (#8524)
This new dropdown component uses a native `select` element, thus avoiding various issues encountered in attempting to reuse our existing dropdown components for the new permission system alert modal. The prefixed forms of `appearance` have been added temporarily so that the component can be used in Storybook, as our Storybook config isn't setup to do autoprefixing yet. Our real build system does handle autoprefixing for this rule correctly already.feature/default_network_editable
parent
351424df7d
commit
8a12257f8e
@ -0,0 +1,63 @@ |
|||||||
|
import React, { useCallback } from 'react' |
||||||
|
import PropTypes from 'prop-types' |
||||||
|
import classnames from 'classnames' |
||||||
|
|
||||||
|
const Dropdown = ({ className, disabled, onChange, options, selectedOption, style, title }) => { |
||||||
|
const _onChange = useCallback( |
||||||
|
(event) => { |
||||||
|
event.preventDefault() |
||||||
|
event.stopPropagation() |
||||||
|
onChange(event.target.value) |
||||||
|
}, |
||||||
|
[onChange], |
||||||
|
) |
||||||
|
|
||||||
|
return ( |
||||||
|
<select |
||||||
|
className={classnames('dropdown', className)} |
||||||
|
disabled={disabled} |
||||||
|
title={title} |
||||||
|
onChange={_onChange} |
||||||
|
style={style} |
||||||
|
value={selectedOption} |
||||||
|
> |
||||||
|
{ |
||||||
|
options.map((option) => { |
||||||
|
return ( |
||||||
|
<option |
||||||
|
key={option.value} |
||||||
|
value={option.value} |
||||||
|
> |
||||||
|
{ option.name || option.value } |
||||||
|
</option> |
||||||
|
) |
||||||
|
}) |
||||||
|
} |
||||||
|
</select> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
Dropdown.propTypes = { |
||||||
|
className: PropTypes.string, |
||||||
|
disabled: PropTypes.bool, |
||||||
|
title: PropTypes.string, |
||||||
|
onChange: PropTypes.func.isRequired, |
||||||
|
options: PropTypes.arrayOf( |
||||||
|
PropTypes.exact({ |
||||||
|
name: PropTypes.string, |
||||||
|
value: PropTypes.string.isRequired, |
||||||
|
}) |
||||||
|
).isRequired, |
||||||
|
selectedOption: PropTypes.string, |
||||||
|
style: PropTypes.object, |
||||||
|
} |
||||||
|
|
||||||
|
Dropdown.defaultProps = { |
||||||
|
className: undefined, |
||||||
|
disabled: false, |
||||||
|
title: undefined, |
||||||
|
selectedOption: null, |
||||||
|
style: undefined, |
||||||
|
} |
||||||
|
|
||||||
|
export default Dropdown |
@ -0,0 +1,22 @@ |
|||||||
|
.dropdown { |
||||||
|
appearance: none; |
||||||
|
|
||||||
|
// TODO: remove these after getting autoprefixer working in Storybook |
||||||
|
-moz-appearance: none; |
||||||
|
-webkit-appearance: none; |
||||||
|
|
||||||
|
border: 1px solid $Grey-500; |
||||||
|
border-radius: 6px; |
||||||
|
background-image: url('/images/icons/caret-down.svg'); |
||||||
|
background-repeat: no-repeat, repeat; |
||||||
|
background-position: right 18px top 50%; |
||||||
|
background-color: white; |
||||||
|
padding: 8px 32px 8px 16px; |
||||||
|
font-family: Roboto, 'sans-serif'; |
||||||
|
font-size: 14px; |
||||||
|
|
||||||
|
[dir='rtl'] & { |
||||||
|
background-position: left 18px top 50%; |
||||||
|
padding: 8px 16px 8px 32px; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
import React from 'react' |
||||||
|
import { action } from '@storybook/addon-actions' |
||||||
|
import Dropdown from '.' |
||||||
|
import { boolean, select, text } from '@storybook/addon-knobs/react' |
||||||
|
|
||||||
|
export default { |
||||||
|
title: 'Dropdown', |
||||||
|
} |
||||||
|
|
||||||
|
const unnamedOptions = [...Array(10).keys()].map((index) => { |
||||||
|
return { value: `option${index}` } |
||||||
|
}) |
||||||
|
|
||||||
|
const namedOptions = unnamedOptions.map((option, index) => { |
||||||
|
return Object.assign({}, option, { name: `Option ${index}` }) |
||||||
|
}) |
||||||
|
|
||||||
|
const namedOptionsWithVeryLongNames = unnamedOptions.map((option, index) => { |
||||||
|
return Object.assign({}, option, { name: `Option ${index} with a very${', very'.repeat(index)} long name` }) |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
export const simple = () => ( |
||||||
|
<Dropdown |
||||||
|
disabled={boolean('Disabled', false)} |
||||||
|
title={text('Title', 'Test dropdown name')} |
||||||
|
onChange={action('Selection changed')} |
||||||
|
options={namedOptions} |
||||||
|
required={boolean('Required', false)} |
||||||
|
selectedOption={ |
||||||
|
select( |
||||||
|
'Selected Option', |
||||||
|
namedOptions.map((option) => option.value), |
||||||
|
namedOptions[0].value |
||||||
|
) |
||||||
|
} |
||||||
|
/> |
||||||
|
) |
||||||
|
|
||||||
|
export const optionsWithoutNames = () => ( |
||||||
|
<Dropdown |
||||||
|
disabled={boolean('Disabled', false)} |
||||||
|
title={text('Title', 'Test dropdown name')} |
||||||
|
onChange={action('Selection changed')} |
||||||
|
options={unnamedOptions} |
||||||
|
required={boolean('Required', false)} |
||||||
|
selectedOption={ |
||||||
|
select( |
||||||
|
'Selected Option', |
||||||
|
unnamedOptions.map((option) => option.value), |
||||||
|
unnamedOptions[0].value |
||||||
|
) |
||||||
|
} |
||||||
|
/> |
||||||
|
) |
||||||
|
|
||||||
|
export const optionsWithLongNames = () => ( |
||||||
|
<Dropdown |
||||||
|
disabled={boolean('Disabled', false)} |
||||||
|
title={text('Title', 'Test dropdown name')} |
||||||
|
onChange={action('Selection changed')} |
||||||
|
options={namedOptionsWithVeryLongNames} |
||||||
|
required={boolean('Required', false)} |
||||||
|
selectedOption={ |
||||||
|
select( |
||||||
|
'Selected Option', |
||||||
|
namedOptionsWithVeryLongNames.map((option) => option.value), |
||||||
|
namedOptionsWithVeryLongNames[0].value |
||||||
|
) |
||||||
|
} |
||||||
|
/> |
||||||
|
) |
||||||
|
|
||||||
|
export const optionsWithLongNamesAndShortWidth = () => ( |
||||||
|
<Dropdown |
||||||
|
disabled={boolean('Disabled', false)} |
||||||
|
title={text('Title', 'Test dropdown name')} |
||||||
|
onChange={action('Selection changed')} |
||||||
|
options={namedOptionsWithVeryLongNames} |
||||||
|
required={boolean('Required', false)} |
||||||
|
selectedOption={ |
||||||
|
select( |
||||||
|
'Selected Option', |
||||||
|
namedOptionsWithVeryLongNames.map((option) => option.value), |
||||||
|
namedOptionsWithVeryLongNames[0].value |
||||||
|
) |
||||||
|
} |
||||||
|
style={{ width: '200px' }} |
||||||
|
/> |
||||||
|
) |
@ -0,0 +1 @@ |
|||||||
|
export { default } from './dropdown' |
Loading…
Reference in new issue