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

Commit 733169b

Browse files
author
Alexandru Buliga
authored
fix(dropdown): style improvements (#786)
* fix(dropdown): style improvements * fixed toggle indicator padding * removed code making isOpen autocontrolled prop * implemented scrolling in selected items list * fixed toggle indicator position * fixed styles and scrolling in Edge
1 parent 62d7cf4 commit 733169b

File tree

8 files changed

+115
-106
lines changed

8 files changed

+115
-106
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
5353
### Fixes
5454
- Make `headerMedia` visible for screen readers in `ListItem` @layershifter ([#772](https://github.com/stardust-ui/react/pull/772))
5555
- Cleanup for `Dropdown` examples' accessibility and added localisation example. @silviuavram ([#771](https://github.com/stardust-ui/react/pull/771))
56-
- Fix highlighted selected option in single selection `Dropdown` when opened @silviuavram ([#726](https://github.com/stardust-ui/react/pull/726))
56+
- Fix highlighted selected option in single selection `Dropdown` when opened @silviuavram ([#726](https://github.com/stardust-ui/react/pull/726))
57+
- Improve `Dropdown` component styles @Bugaa92 ([#786](https://github.com/stardust-ui/react/pull/786))
5758

5859
<!--------------------------------[ v0.18.0 ]------------------------------- -->
5960
## [v0.18.0](https://github.com/stardust-ui/react/tree/v0.18.0) (2019-01-24)

src/components/Dropdown/Dropdown.tsx

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react'
22
import * as PropTypes from 'prop-types'
33
import * as _ from 'lodash'
4+
import cx from 'classnames'
45

56
import {
67
Extendable,
@@ -140,6 +141,7 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
140141
}
141142

142143
export interface DropdownState {
144+
isOpen?: boolean
143145
value: ShorthandValue | ShorthandValue[]
144146
backspaceDelete: boolean
145147
focused: boolean
@@ -160,6 +162,7 @@ export default class Dropdown extends AutoControlledComponent<
160162
private buttonRef = React.createRef<HTMLElement>()
161163
private inputRef = React.createRef<HTMLElement>()
162164
private listRef = React.createRef<HTMLElement>()
165+
private selectedItemsRef = React.createRef<HTMLDivElement>()
163166

164167
static displayName = 'Dropdown'
165168

@@ -199,7 +202,7 @@ export default class Dropdown extends AutoControlledComponent<
199202
]),
200203
}
201204

202-
static defaultProps = {
205+
static defaultProps: DropdownProps = {
203206
as: 'div',
204207
itemToString: item => {
205208
if (!item || React.isValidElement(item)) {
@@ -256,11 +259,7 @@ export default class Dropdown extends AutoControlledComponent<
256259
selectedItem={search && !multiple ? undefined : null}
257260
getA11yStatusMessage={getA11yStatusMessage}
258261
defaultHighlightedIndex={defaultHighlightedIndex}
259-
onStateChange={changes => {
260-
if (changes.isOpen && !search) {
261-
this.listRef.current.focus()
262-
}
263-
}}
262+
onStateChange={this.handleStateChange}
264263
>
265264
{({
266265
getInputProps,
@@ -280,19 +279,24 @@ export default class Dropdown extends AutoControlledComponent<
280279
return (
281280
<Ref innerRef={innerRef}>
282281
<div
283-
className={classes.container}
282+
className={cx(`${Dropdown.className}__container`, classes.container)}
284283
onClick={multiple ? this.handleContainerClick.bind(this, isOpen) : undefined}
285284
>
286-
{multiple && this.renderSelectedItems()}
287-
{search
288-
? this.renderSearchInput(
289-
accessibilityRootPropsRest,
290-
getInputProps,
291-
highlightedIndex,
292-
selectItemAtIndex,
293-
variables,
294-
)
295-
: this.renderTriggerButton(styles, getToggleButtonProps)}
285+
<div
286+
ref={this.selectedItemsRef}
287+
className={cx(`${Dropdown.className}__selected-items`, classes.selectedItems)}
288+
>
289+
{multiple && this.renderSelectedItems()}
290+
{search
291+
? this.renderSearchInput(
292+
accessibilityRootPropsRest,
293+
getInputProps,
294+
highlightedIndex,
295+
selectItemAtIndex,
296+
variables,
297+
)
298+
: this.renderTriggerButton(styles, getToggleButtonProps)}
299+
</div>
296300
{Indicator.create(toggleIndicator, {
297301
defaultProps: {
298302
direction: isOpen ? 'top' : 'bottom',
@@ -359,7 +363,7 @@ export default class Dropdown extends AutoControlledComponent<
359363
) => void,
360364
variables,
361365
): JSX.Element {
362-
const { searchInput, multiple, placeholder, toggleIndicator } = this.props
366+
const { searchInput, multiple, placeholder } = this.props
363367
const { searchQuery, value } = this.state
364368

365369
const noPlaceholder =
@@ -368,7 +372,6 @@ export default class Dropdown extends AutoControlledComponent<
368372
return DropdownSearchInput.create(searchInput || {}, {
369373
defaultProps: {
370374
placeholder: noPlaceholder ? '' : placeholder,
371-
hasToggleButton: !!toggleIndicator,
372375
variables,
373376
inputRef: this.inputRef,
374377
},
@@ -394,14 +397,16 @@ export default class Dropdown extends AutoControlledComponent<
394397
getItemProps: (options: GetItemPropsOptions<ShorthandValue>) => any,
395398
getInputProps: (options?: GetInputPropsOptions) => any,
396399
) {
400+
const { search } = this.props
397401
const { innerRef, ...accessibilityMenuProps } = getMenuProps(
398402
{ refKey: 'innerRef' },
399403
{ suppressRefError: true },
400404
)
401-
const { search } = this.props
405+
402406
// If it's just a selection, some attributes and listeners from Downshift input need to go on the menu list.
403407
if (!search) {
404408
const accessibilityInputProps = getInputProps()
409+
405410
accessibilityMenuProps['aria-activedescendant'] =
406411
accessibilityInputProps['aria-activedescendant']
407412
accessibilityMenuProps['onKeyDown'] = e => {
@@ -440,8 +445,8 @@ export default class Dropdown extends AutoControlledComponent<
440445
highlightedIndex: number,
441446
) {
442447
const { loading, loadingMessage, noResultsMessage, renderItem } = this.props
443-
444448
const filteredItems = this.getItemsFilteredBySearchQuery()
449+
445450
const items = _.map(filteredItems, (item, index) =>
446451
DropdownItem.create(item, {
447452
defaultProps: {
@@ -528,6 +533,16 @@ export default class Dropdown extends AutoControlledComponent<
528533
}
529534
}
530535

536+
private handleStateChange = (changes: StateChangeOptions<ShorthandValue>) => {
537+
if (changes.isOpen !== undefined && changes.isOpen !== this.state.isOpen) {
538+
this.setState({ isOpen: changes.isOpen })
539+
}
540+
541+
if (changes.isOpen && !this.props.search) {
542+
this.listRef.current.focus()
543+
}
544+
}
545+
531546
private getItemsFilteredBySearchQuery = (): ShorthandValue[] => {
532547
const { items, itemToString, multiple, search } = this.props
533548
const { searchQuery, value } = this.state
@@ -536,10 +551,12 @@ export default class Dropdown extends AutoControlledComponent<
536551
if (multiple) {
537552
filteredItems = _.difference(filteredItems, value as ShorthandValue[])
538553
}
554+
539555
if (search) {
540556
if (_.isFunction(search)) {
541557
return search(filteredItems, searchQuery)
542558
}
559+
543560
return filteredItems.filter(
544561
item =>
545562
itemToString(item)
@@ -572,9 +589,7 @@ export default class Dropdown extends AutoControlledComponent<
572589
item: ShorthandValue,
573590
index: number,
574591
getItemProps: (options: GetItemPropsOptions<ShorthandValue>) => any,
575-
) => ({
576-
accessibilityItemProps: getItemProps({ item, index }),
577-
})
592+
) => ({ accessibilityItemProps: getItemProps({ item, index }) })
578593

579594
private handleSelectedItemOverrides = (
580595
predefinedProps: DropdownSelectedItemProps,
@@ -606,9 +621,9 @@ export default class Dropdown extends AutoControlledComponent<
606621
searchInputProps: DropdownSearchInputProps,
607622
) => {
608623
this.setState({ focused: false })
609-
610624
_.invoke(predefinedProps, 'onInputBlur', e, searchInputProps)
611625
}
626+
612627
const handleInputKeyDown = (
613628
e: React.SyntheticEvent,
614629
searchInputProps: DropdownSearchInputProps,
@@ -700,21 +715,27 @@ export default class Dropdown extends AutoControlledComponent<
700715
}
701716

702717
private handleSelectedChange = (item: ShorthandValue) => {
703-
const { multiple, getA11ySelectionMessage, search } = this.props
718+
const { items, multiple, getA11ySelectionMessage, search } = this.props
704719
const newValue = multiple ? [...(this.state.value as ShorthandValue[]), item] : item
705720

706-
this.trySetState({
707-
value: newValue,
708-
searchQuery: '',
709-
})
710-
if (!this.props.search && !this.props.multiple) {
711-
this.setState({
712-
defaultHighlightedIndex: this.props.items.indexOf(item),
713-
})
721+
this.trySetState({ value: newValue, searchQuery: '' })
722+
723+
if (!search && !multiple) {
724+
this.setState({ defaultHighlightedIndex: items.indexOf(item) })
714725
}
726+
715727
if (getA11ySelectionMessage && getA11ySelectionMessage.onAdd) {
716728
this.setA11yStatus(getA11ySelectionMessage.onAdd(item))
717729
}
730+
731+
if (multiple) {
732+
setTimeout(
733+
() =>
734+
(this.selectedItemsRef.current.scrollTop = this.selectedItemsRef.current.scrollHeight),
735+
0,
736+
)
737+
}
738+
718739
if (!search) {
719740
this.buttonRef.current.focus()
720741
}
@@ -745,9 +766,8 @@ export default class Dropdown extends AutoControlledComponent<
745766
poppedItem = value.pop()
746767
}
747768

748-
this.trySetState({
749-
value,
750-
})
769+
this.trySetState({ value })
770+
751771
if (getA11ySelectionMessage && getA11ySelectionMessage.onRemove) {
752772
this.setA11yStatus(getA11ySelectionMessage.onRemove(poppedItem))
753773
}

src/components/Dropdown/DropdownSearchInput.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ import { UIComponentProps } from '../../lib/commonPropInterfaces'
1414
import Input from '../Input/Input'
1515

1616
export interface DropdownSearchInputProps extends UIComponentProps<DropdownSearchInputProps> {
17-
/** Informs the search input about an existing toggle button. */
18-
hasToggleButton?: boolean
19-
2017
/** Ref for input DOM node. */
2118
inputRef?: React.Ref<HTMLElement>
2219

src/themes/teams/components/Dropdown/dropdownSearchInputStyles.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,18 @@ const dropdownSearchInputStyles: ComponentSlotStylesInput<
66
DropdownSearchInputProps,
77
DropdownVariables
88
> = {
9-
input: ({ variables: { backgroundColor, comboboxPaddingInput } }): ICSSInJSStyle => ({
9+
input: ({ variables: v }): ICSSInJSStyle => ({
1010
width: '100%',
11-
backgroundColor,
12-
padding: comboboxPaddingInput,
11+
backgroundColor: v.backgroundColor,
1312

1413
':focus': {
1514
borderBottomColor: 'transparent',
1615
},
1716
}),
1817

19-
combobox: ({
20-
variables: { comboboxFlexBasis, toggleIndicatorSize },
21-
props: { hasToggleButton },
22-
}): ICSSInJSStyle => ({
23-
flexBasis: comboboxFlexBasis,
18+
combobox: ({ variables: v }): ICSSInJSStyle => ({
19+
flexBasis: v.comboboxFlexBasis,
2420
flexGrow: 1,
25-
...(hasToggleButton && {
26-
marginRight: toggleIndicatorSize,
27-
}),
2821
}),
2922
}
3023

src/themes/teams/components/Dropdown/dropdownStyles.ts

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,34 @@
11
import { ComponentSlotStylesInput, ICSSInJSStyle } from '../../../types'
2-
import { DropdownProps } from '../../../../components/Dropdown/Dropdown'
2+
import { DropdownProps, DropdownState } from '../../../../components/Dropdown/Dropdown'
33
import { DropdownVariables } from './dropdownVariables'
44
import { pxToRem } from '../../../../lib'
55

6-
const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables> = {
6+
const dropdownStyles: ComponentSlotStylesInput<DropdownProps & DropdownState, DropdownVariables> = {
77
root: (): ICSSInJSStyle => ({}),
88

9-
container: ({
10-
props: { focused, fluid },
11-
variables: {
12-
backgroundColor,
13-
borderBottom,
14-
borderRadius,
15-
borderColor,
16-
borderColorFocus,
17-
borderRadiusFocus,
18-
color,
19-
width,
20-
},
21-
}): ICSSInJSStyle => ({
9+
container: ({ props: p, variables: v }): ICSSInJSStyle => ({
2210
display: 'flex',
2311
flexWrap: 'wrap',
2412
outline: 0,
2513
border: 0,
26-
backgroundColor,
27-
borderBottom,
28-
borderColor,
29-
borderRadius,
30-
color,
31-
width: fluid ? '100%' : width,
14+
backgroundColor: v.backgroundColor,
15+
borderBottom: v.borderBottom,
16+
borderRadius: v.borderRadius,
17+
color: v.color,
18+
width: p.fluid ? '100%' : v.width,
3219
position: 'relative',
33-
...(focused && {
34-
borderColor: borderColorFocus,
35-
borderRadius: borderRadiusFocus,
36-
}),
20+
...(p.focused && { borderColor: v.borderColorFocus }),
21+
}),
22+
23+
selectedItems: ({ props: p, variables: v }): ICSSInJSStyle => ({
24+
display: 'flex',
25+
flexWrap: 'wrap',
26+
overflowY: 'auto',
27+
maxHeight: v.selectedItemsMaxHeight,
28+
...(p.toggleIndicator && { paddingRight: v.toggleIndicatorSize }),
3729
}),
3830

39-
button: ({ variables: { comboboxPaddingButton } }): ICSSInJSStyle => {
31+
button: ({ variables: v }): ICSSInJSStyle => {
4032
const transparentColorStyle = {
4133
backgroundColor: 'transparent',
4234
borderColor: 'transparent',
@@ -45,7 +37,7 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
4537
boxShadow: 'none',
4638
margin: '0',
4739
justifyContent: 'left',
48-
padding: comboboxPaddingButton,
40+
padding: v.comboboxPaddingButton,
4941
...transparentColorStyle,
5042
height: pxToRem(30),
5143
':hover': transparentColorStyle,
@@ -60,17 +52,20 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
6052
}
6153
},
6254

63-
list: ({
64-
variables: { listMaxHeight, width, listBackgroundColor },
65-
props: { fluid },
66-
}): ICSSInJSStyle => ({
55+
list: ({ props: p, variables: v }): ICSSInJSStyle => ({
56+
outline: 0,
6757
position: 'absolute',
58+
borderRadius: v.listBorderRadius,
6859
zIndex: 1000,
69-
maxHeight: listMaxHeight,
60+
maxHeight: v.listMaxHeight,
7061
overflowY: 'auto',
71-
width: fluid ? '100%' : width,
62+
width: p.fluid ? '100%' : v.width,
7263
top: 'calc(100% + 2px)', // leave room for container + its border
73-
background: listBackgroundColor,
64+
background: v.listBackgroundColor,
65+
...(p.isOpen && {
66+
boxShadow: v.listBoxShadow,
67+
padding: v.listPadding,
68+
}),
7469
}),
7570

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

85-
toggleIndicator: ({ props: p, variables: v }): ICSSInJSStyle => ({
80+
toggleIndicator: ({ variables: v }): ICSSInJSStyle => ({
8681
position: 'absolute',
8782
height: v.toggleIndicatorSize,
8883
width: v.toggleIndicatorSize,
@@ -93,7 +88,7 @@ const dropdownStyles: ComponentSlotStylesInput<DropdownProps, DropdownVariables>
9388
justifyContent: 'center',
9489
alignItems: 'center',
9590
userSelect: 'none',
96-
...(p.fluid ? { right: 0 } : { left: `calc(${v.width} - ${v.toggleIndicatorSize})` }),
91+
right: pxToRem(5),
9792
}),
9893
}
9994

0 commit comments

Comments
 (0)