Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

fix(dropdown): style improvements #786

Merged
merged 6 commits into from
Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Fixes
- Make `headerMedia` visible for screen readers in `ListItem` @layershifter ([#772](https://github.com/stardust-ui/react/pull/772))
- Cleanup for `Dropdown` examples' accessibility and added localisation example. @silviuavram ([#771](https://github.com/stardust-ui/react/pull/771))
- Fix highlighted selected option in single selection `Dropdown` when opened @silviuavram ([#726](https://github.com/stardust-ui/react/pull/726))
- Fix highlighted selected option in single selection `Dropdown` when opened @silviuavram ([#726](https://github.com/stardust-ui/react/pull/726))
- Improve `Dropdown` component styles @Bugaa92 ([#786](https://github.com/stardust-ui/react/pull/786))

<!--------------------------------[ v0.18.0 ]------------------------------- -->
## [v0.18.0](https://github.com/stardust-ui/react/tree/v0.18.0) (2019-01-24)
Expand Down
94 changes: 57 additions & 37 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react'
import * as PropTypes from 'prop-types'
import * as _ from 'lodash'
import cx from 'classnames'

import {
Extendable,
Expand Down Expand Up @@ -140,6 +141,7 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
}

export interface DropdownState {
isOpen?: boolean
value: ShorthandValue | ShorthandValue[]
backspaceDelete: boolean
focused: boolean
Expand All @@ -160,6 +162,7 @@ export default class Dropdown extends AutoControlledComponent<
private buttonRef = React.createRef<HTMLElement>()
private inputRef = React.createRef<HTMLElement>()
private listRef = React.createRef<HTMLElement>()
private selectedItemsRef = React.createRef<HTMLDivElement>()

static displayName = 'Dropdown'

Expand Down Expand Up @@ -199,7 +202,7 @@ export default class Dropdown extends AutoControlledComponent<
]),
}

static defaultProps = {
static defaultProps: DropdownProps = {
as: 'div',
itemToString: item => {
if (!item || React.isValidElement(item)) {
Expand Down Expand Up @@ -256,11 +259,7 @@ export default class Dropdown extends AutoControlledComponent<
selectedItem={search && !multiple ? undefined : null}
getA11yStatusMessage={getA11yStatusMessage}
defaultHighlightedIndex={defaultHighlightedIndex}
onStateChange={changes => {
if (changes.isOpen && !search) {
this.listRef.current.focus()
}
}}
onStateChange={this.handleStateChange}
>
{({
getInputProps,
Expand All @@ -280,19 +279,24 @@ export default class Dropdown extends AutoControlledComponent<
return (
<Ref innerRef={innerRef}>
<div
className={classes.container}
className={cx(`${Dropdown.className}__container`, classes.container)}
onClick={multiple ? this.handleContainerClick.bind(this, isOpen) : undefined}
>
{multiple && this.renderSelectedItems()}
{search
? this.renderSearchInput(
accessibilityRootPropsRest,
getInputProps,
highlightedIndex,
selectItemAtIndex,
variables,
)
: this.renderTriggerButton(styles, getToggleButtonProps)}
<div
ref={this.selectedItemsRef}
className={cx(`${Dropdown.className}__selected-items`, classes.selectedItems)}
>
{multiple && this.renderSelectedItems()}
{search
? this.renderSearchInput(
accessibilityRootPropsRest,
getInputProps,
highlightedIndex,
selectItemAtIndex,
variables,
)
: this.renderTriggerButton(styles, getToggleButtonProps)}
</div>
{Indicator.create(toggleIndicator, {
defaultProps: {
direction: isOpen ? 'top' : 'bottom',
Expand Down Expand Up @@ -359,7 +363,7 @@ export default class Dropdown extends AutoControlledComponent<
) => void,
variables,
): JSX.Element {
const { searchInput, multiple, placeholder, toggleIndicator } = this.props
const { searchInput, multiple, placeholder } = this.props
const { searchQuery, value } = this.state

const noPlaceholder =
Expand All @@ -368,7 +372,6 @@ export default class Dropdown extends AutoControlledComponent<
return DropdownSearchInput.create(searchInput || {}, {
defaultProps: {
placeholder: noPlaceholder ? '' : placeholder,
hasToggleButton: !!toggleIndicator,
variables,
inputRef: this.inputRef,
},
Expand All @@ -394,14 +397,16 @@ export default class Dropdown extends AutoControlledComponent<
getItemProps: (options: GetItemPropsOptions<ShorthandValue>) => any,
getInputProps: (options?: GetInputPropsOptions) => any,
) {
const { search } = this.props
const { innerRef, ...accessibilityMenuProps } = getMenuProps(
{ refKey: 'innerRef' },
{ suppressRefError: true },
)
const { search } = this.props

// If it's just a selection, some attributes and listeners from Downshift input need to go on the menu list.
if (!search) {
const accessibilityInputProps = getInputProps()

accessibilityMenuProps['aria-activedescendant'] =
accessibilityInputProps['aria-activedescendant']
accessibilityMenuProps['onKeyDown'] = e => {
Expand Down Expand Up @@ -440,8 +445,8 @@ export default class Dropdown extends AutoControlledComponent<
highlightedIndex: number,
) {
const { loading, loadingMessage, noResultsMessage, renderItem } = this.props

const filteredItems = this.getItemsFilteredBySearchQuery()

const items = _.map(filteredItems, (item, index) =>
DropdownItem.create(item, {
defaultProps: {
Expand Down Expand Up @@ -528,6 +533,16 @@ export default class Dropdown extends AutoControlledComponent<
}
}

private handleStateChange = (changes: StateChangeOptions<ShorthandValue>) => {
if (changes.isOpen !== undefined && changes.isOpen !== this.state.isOpen) {
this.setState({ isOpen: changes.isOpen })
}

if (changes.isOpen && !this.props.search) {
this.listRef.current.focus()
}
}

private getItemsFilteredBySearchQuery = (): ShorthandValue[] => {
const { items, itemToString, multiple, search } = this.props
const { searchQuery, value } = this.state
Expand All @@ -536,10 +551,12 @@ export default class Dropdown extends AutoControlledComponent<
if (multiple) {
filteredItems = _.difference(filteredItems, value as ShorthandValue[])
}

if (search) {
if (_.isFunction(search)) {
return search(filteredItems, searchQuery)
}

return filteredItems.filter(
item =>
itemToString(item)
Expand Down Expand Up @@ -572,9 +589,7 @@ export default class Dropdown extends AutoControlledComponent<
item: ShorthandValue,
index: number,
getItemProps: (options: GetItemPropsOptions<ShorthandValue>) => any,
) => ({
accessibilityItemProps: getItemProps({ item, index }),
})
) => ({ accessibilityItemProps: getItemProps({ item, index }) })

private handleSelectedItemOverrides = (
predefinedProps: DropdownSelectedItemProps,
Expand Down Expand Up @@ -606,9 +621,9 @@ export default class Dropdown extends AutoControlledComponent<
searchInputProps: DropdownSearchInputProps,
) => {
this.setState({ focused: false })

_.invoke(predefinedProps, 'onInputBlur', e, searchInputProps)
}

const handleInputKeyDown = (
e: React.SyntheticEvent,
searchInputProps: DropdownSearchInputProps,
Expand Down Expand Up @@ -700,21 +715,27 @@ export default class Dropdown extends AutoControlledComponent<
}

private handleSelectedChange = (item: ShorthandValue) => {
const { multiple, getA11ySelectionMessage, search } = this.props
const { items, multiple, getA11ySelectionMessage, search } = this.props
const newValue = multiple ? [...(this.state.value as ShorthandValue[]), item] : item

this.trySetState({
value: newValue,
searchQuery: '',
})
if (!this.props.search && !this.props.multiple) {
this.setState({
defaultHighlightedIndex: this.props.items.indexOf(item),
})
this.trySetState({ value: newValue, searchQuery: '' })

if (!search && !multiple) {
this.setState({ defaultHighlightedIndex: items.indexOf(item) })
}

if (getA11ySelectionMessage && getA11ySelectionMessage.onAdd) {
this.setA11yStatus(getA11ySelectionMessage.onAdd(item))
}

if (multiple) {
setTimeout(
() =>
(this.selectedItemsRef.current.scrollTop = this.selectedItemsRef.current.scrollHeight),
0,
)
}

if (!search) {
this.buttonRef.current.focus()
}
Expand Down Expand Up @@ -745,9 +766,8 @@ export default class Dropdown extends AutoControlledComponent<
poppedItem = value.pop()
}

this.trySetState({
value,
})
this.trySetState({ value })

if (getA11ySelectionMessage && getA11ySelectionMessage.onRemove) {
this.setA11yStatus(getA11ySelectionMessage.onRemove(poppedItem))
}
Expand Down
3 changes: 0 additions & 3 deletions src/components/Dropdown/DropdownSearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ import { UIComponentProps } from '../../lib/commonPropInterfaces'
import Input from '../Input/Input'

export interface DropdownSearchInputProps extends UIComponentProps<DropdownSearchInputProps> {
/** Informs the search input about an existing toggle button. */
hasToggleButton?: boolean

/** Ref for input DOM node. */
inputRef?: React.Ref<HTMLElement>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,18 @@ const dropdownSearchInputStyles: ComponentSlotStylesInput<
DropdownSearchInputProps,
DropdownVariables
> = {
input: ({ variables: { backgroundColor, comboboxPaddingInput } }): ICSSInJSStyle => ({
input: ({ variables: v }): ICSSInJSStyle => ({
width: '100%',
backgroundColor,
padding: comboboxPaddingInput,
backgroundColor: v.backgroundColor,

':focus': {
borderBottomColor: 'transparent',
},
}),

combobox: ({
variables: { comboboxFlexBasis, toggleIndicatorSize },
props: { hasToggleButton },
}): ICSSInJSStyle => ({
flexBasis: comboboxFlexBasis,
combobox: ({ variables: v }): ICSSInJSStyle => ({
flexBasis: v.comboboxFlexBasis,
flexGrow: 1,
...(hasToggleButton && {
marginRight: toggleIndicatorSize,
}),
}),
}

Expand Down
67 changes: 31 additions & 36 deletions src/themes/teams/components/Dropdown/dropdownStyles.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,34 @@
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
import { DropdownProps } from '../../../../components/Dropdown/Dropdown'
import { DropdownProps, DropdownState } from '../../../../components/Dropdown/Dropdown'
import { DropdownVariables } from './dropdownVariables'
import { pxToRem } from '../../../../lib'

const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables> = {
const dropdownStyles: ComponentSlotStylesInput<DropdownProps & DropdownState, DropdownVariables> = {
root: (): ICSSInJSStyle => ({}),

container: ({
props: { focused, fluid },
variables: {
backgroundColor,
borderBottom,
borderRadius,
borderColor,
borderColorFocus,
borderRadiusFocus,
color,
width,
},
}): ICSSInJSStyle => ({
container: ({ props: p, variables: v }): ICSSInJSStyle => ({
display: 'flex',
flexWrap: 'wrap',
outline: 0,
border: 0,
backgroundColor,
borderBottom,
borderColor,
borderRadius,
color,
width: fluid ? '100%' : width,
backgroundColor: v.backgroundColor,
borderBottom: v.borderBottom,
borderRadius: v.borderRadius,
color: v.color,
width: p.fluid ? '100%' : v.width,
position: 'relative',
...(focused && {
borderColor: borderColorFocus,
borderRadius: borderRadiusFocus,
}),
...(p.focused && { borderColor: v.borderColorFocus }),
}),

selectedItems: ({ props: p, variables: v }): ICSSInJSStyle => ({
display: 'flex',
flexWrap: 'wrap',
overflowY: 'auto',
maxHeight: v.selectedItemsMaxHeight,
...(p.toggleIndicator && { paddingRight: v.toggleIndicatorSize }),
}),

button: ({ variables: { comboboxPaddingButton } }): ICSSInJSStyle => {
button: ({ variables: v }): ICSSInJSStyle => {
const transparentColorStyle = {
backgroundColor: 'transparent',
borderColor: 'transparent',
Expand All @@ -45,7 +37,7 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
boxShadow: 'none',
margin: '0',
justifyContent: 'left',
padding: comboboxPaddingButton,
padding: v.comboboxPaddingButton,
...transparentColorStyle,
height: pxToRem(30),
':hover': transparentColorStyle,
Expand All @@ -60,17 +52,20 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
}
},

list: ({
variables: { listMaxHeight, width, listBackgroundColor },
props: { fluid },
}): ICSSInJSStyle => ({
list: ({ props: p, variables: v }): ICSSInJSStyle => ({
outline: 0,
position: 'absolute',
borderRadius: v.listBorderRadius,
zIndex: 1000,
maxHeight: listMaxHeight,
maxHeight: v.listMaxHeight,
overflowY: 'auto',
width: fluid ? '100%' : width,
width: p.fluid ? '100%' : v.width,
top: 'calc(100% + 2px)', // leave room for container + its border
background: listBackgroundColor,
background: v.listBackgroundColor,
...(p.isOpen && {
boxShadow: v.listBoxShadow,
padding: v.listPadding,
}),
}),

loadingMessage: ({ variables: v }): ICSSInJSStyle => ({
Expand All @@ -82,7 +77,7 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
fontWeight: 'bold',
}),

toggleIndicator: ({ props: p, variables: v }): ICSSInJSStyle => ({
toggleIndicator: ({ variables: v }): ICSSInJSStyle => ({
position: 'absolute',
height: v.toggleIndicatorSize,
width: v.toggleIndicatorSize,
Expand All @@ -93,7 +88,7 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
justifyContent: 'center',
alignItems: 'center',
userSelect: 'none',
...(p.fluid ? { right: 0 } : { left: `calc(${v.width} - ${v.toggleIndicatorSize})` }),
right: pxToRem(5),
}),
}

Expand Down
Loading