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
Mark Stacey 5 years ago committed by GitHub
parent 351424df7d
commit 8a12257f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ui/app/components/app/index.scss
  2. 63
      ui/app/components/ui/dropdown/dropdown.js
  3. 22
      ui/app/components/ui/dropdown/dropdown.scss
  4. 90
      ui/app/components/ui/dropdown/dropdown.stories.js
  5. 1
      ui/app/components/ui/dropdown/index.js

@ -100,6 +100,8 @@
@import '../ui/check-box/index'; @import '../ui/check-box/index';
@import '../ui/dropdown/dropdown';
@import 'permissions-connect-header/index'; @import 'permissions-connect-header/index';
@import 'permissions-connect-footer/index'; @import 'permissions-connect-footer/index';

@ -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…
Cancel
Save