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

Commit 6d08667

Browse files
committed
feat(Dropdown): add loading prop
1 parent fe645d6 commit 6d08667

17 files changed

+307
-82
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react'
2+
import Knobs from 'docs/src/components/Knobs/Knobs'
3+
4+
type DropdownExampleLoadingKnobsProps = {
5+
loading?: boolean
6+
onKnobChange: () => void
7+
}
8+
9+
const DropdownExampleLoadingKnobs: React.FC<DropdownExampleLoadingKnobsProps> = props => {
10+
const { loading, onKnobChange } = props
11+
12+
return (
13+
<Knobs>
14+
<Knobs.Boolean name="loading" onChange={onKnobChange} value={loading} />
15+
</Knobs>
16+
)
17+
}
18+
19+
DropdownExampleLoadingKnobs.defaultProps = {
20+
loading: true,
21+
}
22+
23+
export default DropdownExampleLoadingKnobs
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Dropdown } from '@stardust-ui/react'
2+
import * as React from 'react'
3+
4+
const inputItems = [
5+
'Bruce Wayne',
6+
'Natasha Romanoff',
7+
'Steven Strange',
8+
'Alfred Pennyworth',
9+
`Scarlett O'Hara`,
10+
'Imperator Furiosa',
11+
'Bruce Banner',
12+
'Peter Parker',
13+
'Selina Kyle',
14+
]
15+
16+
const DropdownExampleLoading: React.FC<{ knobs: { loading: boolean } }> = ({ knobs }) => (
17+
<Dropdown
18+
loading={knobs.loading}
19+
multiple
20+
items={inputItems}
21+
placeholder="Start typing a name"
22+
search
23+
/>
24+
)
25+
26+
export default DropdownExampleLoading
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as React from 'react'
2+
import ComponentExample from 'docs/src/components/ComponentDoc/ComponentExample'
3+
import ExampleSection from 'docs/src/components/ComponentDoc/ExampleSection'
4+
5+
const State = () => (
6+
<ExampleSection title="State">
7+
<ComponentExample
8+
title="Loading"
9+
description="A dropdown with single selection."
10+
examplePath="components/Dropdown/State/DropdownExampleLoading"
11+
/>
12+
</ExampleSection>
13+
)
14+
15+
export default State

docs/src/examples/components/Dropdown/Types/DropdownExampleMultipleSearch.shorthand.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const DropdownExample = () => (
1919
search
2020
getA11ySelectionMessage={getA11ySelectionMessage}
2121
getA11yStatusMessage={getA11yStatusMessage}
22-
noResultsMessage="We couldn't find any matches."
22+
messageNoResults="We couldn't find any matches."
2323
placeholder="Start typing a name"
2424
items={inputItems}
2525
/>

docs/src/examples/components/Dropdown/Variations/DropdownExampleMultipleSearchFluid.shorthand.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const DropdownExample = () => (
1717
multiple
1818
getA11ySelectionMessage={getA11ySelectionMessage}
1919
getA11yStatusMessage={getA11yStatusMessage}
20-
noResultsMessage="We couldn't find any matches."
2120
search
2221
fluid
2322
placeholder="Start typing a name"

docs/src/examples/components/Dropdown/Variations/DropdownExampleMultipleSearchImageAndContent.shorthand.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ const DropdownExample = () => (
5555
getA11yStatusMessage={getA11yStatusMessage}
5656
search
5757
getA11ySelectionMessage={getA11ySelectionMessage}
58-
noResultsMessage="We couldn't find any matches."
5958
placeholder="Start typing a name"
6059
items={inputItems}
6160
/>

docs/src/examples/components/Dropdown/Variations/DropdownExampleMultipleSearchToggleButton.shorthand.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const DropdownExample = () => (
1818
multiple
1919
getA11yStatusMessage={getA11yStatusMessage}
2020
getA11ySelectionMessage={getA11ySelectionMessage}
21-
noResultsMessage="We couldn't find any matches."
2221
search
2322
placeholder="Start typing a name"
2423
toggleButton

docs/src/examples/components/Dropdown/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import * as React from 'react'
2+
23
import Types from './Types'
34
import Variations from './Variations'
5+
import State from './State'
46

57
const DropdownExamples = () => (
68
<div>
79
<Types />
810
<Variations />
11+
<State />
912
</div>
1013
)
1114

src/components/Dropdown/Dropdown.tsx

Lines changed: 66 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ import * as PropTypes from 'prop-types'
33
import * as _ from 'lodash'
44

55
import { Extendable, ShorthandValue, ComponentEventHandler } from '../../../types/utils'
6-
import {
7-
ComponentSlotStylesInput,
8-
ComponentVariablesInput,
9-
ComponentSlotClasses,
10-
} from '../../themes/types'
6+
import { ComponentSlotStylesInput, ComponentVariablesInput } from '../../themes/types'
117
import Downshift, {
128
DownshiftState,
139
StateChangeOptions,
@@ -24,13 +20,15 @@ import {
2420
customPropTypes,
2521
commonPropTypes,
2622
handleRef,
23+
UIComponentProps,
2724
} from '../../lib'
2825
import keyboardKey from 'keyboard-key'
2926
import List from '../List/List'
30-
import Text from '../Text/Text'
3127
import Ref from '../Ref/Ref'
32-
import { UIComponentProps } from '../../lib/commonPropInterfaces'
28+
import DropdownIndicator from './DropdownIndicator'
3329
import DropdownItem from './DropdownItem'
30+
import DropdownMessageLoading from './DropdownMessageLoading'
31+
import DropdownMessageNoResults from './DropdownMessageNoResults'
3432
import DropdownSelectedItem, { DropdownSelectedItemProps } from './DropdownSelectedItem'
3533
import DropdownSearchInput, { DropdownSearchInputProps } from './DropdownSearchInput'
3634
import Button from '../Button/Button'
@@ -70,6 +68,8 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
7068
*/
7169
getA11yStatusMessage?: (options: A11yStatusMessageOptions<ShorthandValue>) => string
7270

71+
indicator?: ShorthandValue
72+
7373
/** Array of props for generating list options (Dropdown.Item[]) and selected item labels(Dropdown.SelectedItem[]), if it's a multiple selection. */
7474
items?: ShorthandValue[]
7575

@@ -78,11 +78,16 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
7878
*/
7979
itemToString?: (item: ShorthandValue) => string
8080

81+
loading?: boolean
82+
8183
/** A dropdown can perform a multiple selection. */
8284
multiple?: boolean
8385

84-
/** A string to be displayed in the list when dropdown has no available items to show. */
85-
noResultsMessage?: string
86+
/** A message to be displayed in the list when dropdown is loading. */
87+
messageLoading?: ShorthandValue
88+
89+
/** A message to be displayed in the list when dropdown has no available items to show. */
90+
messageNoResults?: ShorthandValue
8691

8792
/**
8893
* Callback for change in dropdown search query value.
@@ -110,9 +115,6 @@ export interface DropdownProps extends UIComponentProps<DropdownProps, DropdownS
110115
/** Sets search query value (controlled mode). */
111116
searchQuery?: string
112117

113-
/** Whether toggle button (that shows/hides items list) should be rendered. */
114-
toggleButton?: boolean
115-
116118
/** Sets currently selected value(s) (controlled mode). */
117119
value?: ShorthandValue | ShorthandValue[]
118120
}
@@ -155,17 +157,19 @@ export default class Dropdown extends AutoControlledComponent<
155157
fluid: PropTypes.bool,
156158
getA11ySelectionMessage: PropTypes.object,
157159
getA11yStatusMessage: PropTypes.func,
160+
indicator: customPropTypes.itemShorthand,
158161
items: customPropTypes.collectionShorthand,
159162
itemToString: PropTypes.func,
163+
loading: PropTypes.bool,
160164
multiple: PropTypes.bool,
161-
noResultsMessage: PropTypes.string,
165+
messageLoading: PropTypes.string,
166+
messageNoResults: PropTypes.string,
162167
onSearchQueryChange: PropTypes.func,
163168
onSelectedChange: PropTypes.func,
164169
placeholder: PropTypes.string,
165170
search: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
166171
searchQuery: PropTypes.string,
167172
searchInput: customPropTypes.itemShorthand,
168-
toggleButton: PropTypes.bool,
169173
value: PropTypes.oneOfType([
170174
customPropTypes.itemShorthand,
171175
customPropTypes.collectionShorthand,
@@ -174,6 +178,7 @@ export default class Dropdown extends AutoControlledComponent<
174178

175179
static defaultProps = {
176180
as: 'div',
181+
indicator: '',
177182
itemToString: item => {
178183
if (!item || React.isValidElement(item)) {
179184
return ''
@@ -186,6 +191,8 @@ export default class Dropdown extends AutoControlledComponent<
186191

187192
return `${item}`
188193
},
194+
messageLoading: 'Loading...',
195+
messageNoResults: "We couldn't find any matches.",
189196
}
190197

191198
static autoControlledProps = ['searchQuery', 'value']
@@ -211,7 +218,15 @@ export default class Dropdown extends AutoControlledComponent<
211218
variables,
212219
unhandledProps,
213220
}: RenderResultConfig<DropdownProps>) {
214-
const { search, multiple, toggleButton, getA11yStatusMessage, itemToString } = this.props
221+
const {
222+
fluid,
223+
indicator,
224+
loading,
225+
search,
226+
multiple,
227+
getA11yStatusMessage,
228+
itemToString,
229+
} = this.props
215230
const { searchQuery } = this.state
216231

217232
return (
@@ -262,7 +277,15 @@ export default class Dropdown extends AutoControlledComponent<
262277
variables,
263278
)
264279
: this.renderTriggerButton(styles, getToggleButtonProps)}
265-
{toggleButton && this.renderToggleButton(getToggleButtonProps, classes, isOpen)}
280+
{DropdownIndicator.create(indicator, {
281+
defaultProps: {
282+
...getToggleButtonProps(),
283+
fluid,
284+
loading,
285+
open: isOpen,
286+
variables,
287+
},
288+
})}
266289
{this.renderItemsList(
267290
styles,
268291
variables,
@@ -322,7 +345,7 @@ export default class Dropdown extends AutoControlledComponent<
322345
) => void,
323346
variables,
324347
): JSX.Element {
325-
const { searchInput, multiple, placeholder, toggleButton } = this.props
348+
const { indicator, searchInput, multiple, placeholder } = this.props
326349
const { searchQuery, value } = this.state
327350

328351
const noPlaceholder =
@@ -331,7 +354,7 @@ export default class Dropdown extends AutoControlledComponent<
331354
return DropdownSearchInput.create(searchInput || {}, {
332355
defaultProps: {
333356
placeholder: noPlaceholder ? '' : placeholder,
334-
hasToggleButton: !!toggleButton,
357+
hasToggleButton: !!indicator,
335358
variables,
336359
inputRef: this.inputRef,
337360
},
@@ -346,19 +369,6 @@ export default class Dropdown extends AutoControlledComponent<
346369
})
347370
}
348371

349-
private renderToggleButton(
350-
getToggleButtonProps: (options?: GetToggleButtonPropsOptions) => any,
351-
classes: ComponentSlotClasses,
352-
isOpen: boolean,
353-
) {
354-
const { onClick } = getToggleButtonProps()
355-
return (
356-
<span className={classes.toggleButton} onClick={onClick}>
357-
{isOpen ? String.fromCharCode(9650) : String.fromCharCode(9660)}
358-
</span>
359-
)
360-
}
361-
362372
private renderItemsList(
363373
styles: ComponentSlotStylesInput,
364374
variables: ComponentVariablesInput,
@@ -370,7 +380,10 @@ export default class Dropdown extends AutoControlledComponent<
370380
getItemProps: (options: GetItemPropsOptions<ShorthandValue>) => any,
371381
getInputProps: (options?: GetInputPropsOptions) => any,
372382
) {
373-
const accessibilityMenuProps = getMenuProps({ refKey: 'innerRef' }, { suppressRefError: true })
383+
const { innerRef, ...accessibilityMenuProps } = getMenuProps(
384+
{ refKey: 'innerRef' },
385+
{ suppressRefError: true },
386+
)
374387
const { search } = this.props
375388
// If it's just a selection, some attributes and listeners from Downshift input need to go on the menu list.
376389
if (!search) {
@@ -387,7 +400,7 @@ export default class Dropdown extends AutoControlledComponent<
387400
)
388401
}
389402
}
390-
const { innerRef, ...accessibilityMenuPropsRest } = accessibilityMenuProps
403+
391404
return (
392405
<Ref
393406
innerRef={(listElement: HTMLElement) => {
@@ -396,49 +409,42 @@ export default class Dropdown extends AutoControlledComponent<
396409
}}
397410
>
398411
<List
399-
{...accessibilityMenuPropsRest}
412+
{...accessibilityMenuProps}
400413
styles={styles.list}
401414
tabIndex={search ? undefined : -1} // needs to be focused when trigger button is activated.
402415
aria-hidden={!isOpen}
403-
items={isOpen ? this.renderItems(styles, variables, getItemProps, highlightedIndex) : []}
416+
items={isOpen ? this.renderItems(variables, getItemProps, highlightedIndex) : []}
404417
/>
405418
</Ref>
406419
)
407420
}
408421

409422
private renderItems(
410-
styles: ComponentSlotStylesInput,
411423
variables: ComponentVariablesInput,
412424
getItemProps: (options: GetItemPropsOptions<ShorthandValue>) => any,
413425
highlightedIndex: number,
414426
) {
415-
const { noResultsMessage } = this.props
427+
const { loading, messageLoading, messageNoResults } = this.props
428+
416429
const filteredItems = this.getItemsFilteredBySearchQuery()
430+
const items = filteredItems.map((item, index) =>
431+
DropdownItem.create(item, {
432+
defaultProps: {
433+
active: highlightedIndex === index,
434+
variables,
435+
...(typeof item === 'object' &&
436+
!item.hasOwnProperty('key') && {
437+
key: (item as any).header,
438+
}),
439+
},
440+
overrideProps: () => this.handleItemOverrides(item, index, getItemProps),
441+
}),
442+
)
417443

418-
if (filteredItems.length > 0) {
419-
return filteredItems.map((item, index) => {
420-
return DropdownItem.create(item, {
421-
defaultProps: {
422-
active: highlightedIndex === index,
423-
variables,
424-
...(typeof item === 'object' &&
425-
!item.hasOwnProperty('key') && {
426-
key: (item as any).header,
427-
}),
428-
},
429-
overrideProps: () => this.handleItemOverrides(item, index, getItemProps),
430-
})
431-
})
432-
}
433-
// render no match message.
434444
return [
435-
noResultsMessage
436-
? {
437-
key: 'dropdown-no-results',
438-
content: <Text weight="bold" content={noResultsMessage} />,
439-
styles: styles.emptyListItem,
440-
}
441-
: null,
445+
...items,
446+
loading && DropdownMessageLoading.create(messageLoading),
447+
!loading && items.length === 0 && DropdownMessageNoResults.create(messageNoResults),
442448
]
443449
}
444450

0 commit comments

Comments
 (0)