Skip to content

Commit

Permalink
feat: handle disabled options in select v.4 (#1317)
Browse files Browse the repository at this point in the history
* feat: handle disabled options in select

* fix: typo in test

* fix: select use isDisabled prop instead of $disabled

* fix: type mismatch between onChange argument and SelectValue
  • Loading branch information
RobelTekle committed May 25, 2022
1 parent 60d3d52 commit 56ebc97
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 10 deletions.
5 changes: 5 additions & 0 deletions packages/Core/theme/defaultFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type ThemeDefaultFields = {
highlighted: CSSObject
selectedAndHighlighted: CSSObject
selected: CSSObject
disabled: CSSObject
}
fieldset: CSSObject
}
Expand Down Expand Up @@ -114,6 +115,10 @@ export const getDefaultFields = (theme: WuiTheme): ThemeDefaultFields => {
color: colors.dark[200],
fontWeight: fontWeights.bold,
},
disabled: {
color: colors.nude[500],
cursor: 'not-allowed',
},
},
fieldset: {
'border-width': '0',
Expand Down
123 changes: 116 additions & 7 deletions packages/Select/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useState } from 'react'
import { fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import capitalize from 'lodash.capitalize'
Expand All @@ -7,7 +7,17 @@ import { DateIcon } from '@welcome-ui/icons.date'

import { render } from '../../utils/tests'

import { Option, Select } from './index'
import { Option, Select, SelectProps } from './index'

const SelectWrapper: React.FC<SelectProps> = props => {
const [value, setValue] = useState<SelectProps['value']>()

const handleChange: SelectProps['onChange'] = newValue => {
setValue(newValue)
}

return <Select onChange={handleChange} value={value} {...props} />
}

const MONTHS = [
'january',
Expand All @@ -22,7 +32,7 @@ const MONTHS = [
'october',
'november',
'december',
].map(month => ({ label: capitalize(month), value: month }))
].map(month => ({ label: capitalize(month), value: month, disabled: month === 'january' }))

const MONTHS_WITH_INTEGER_VALUES = MONTHS.map((item, index) => ({
label: item.label,
Expand All @@ -33,7 +43,7 @@ const SOCIAL_OPT_GROUP = [
{
label: 'Professional networks',
options: [
{ value: 'behance', label: 'Behance' },
{ value: 'behance', label: 'Behance', disabled: true },
{ value: 'dribbble', label: 'Dribbble' },
{ value: 'github', label: 'Github' },
],
Expand Down Expand Up @@ -127,11 +137,25 @@ test('<Select> calls onChange with correct (object) values', () => {
expect(handleChange).toHaveBeenCalledWith(
'february',
expect.objectContaining({
target: { name: 'select', value: { label: 'February', value: 'february' } },
target: { name: 'select', value: { label: 'February', value: 'february', disabled: false } },
}) // Ignore preventDefault
)
})

test('<Select> calls onChange on disabled option', () => {
const { getByRole, getByTestId } = render(
<SelectWrapper dataTestId="select" name="select" options={MONTHS} />
)

const select = getByTestId('select')
fireEvent.click(select)

const options = getByRole('listbox').querySelectorAll('li')
fireEvent.click(options[0])

expect(select).toHaveTextContent('')
})

test('<Select isClearable> can remove option', () => {
const { getByTestId, getByTitle } = render(
<Select dataTestId="select" isClearable name="select" options={MONTHS} value="february" />
Expand Down Expand Up @@ -173,6 +197,32 @@ test('<Select isMultiple> can select multiple items', () => {
expect(tags.map(tag => tag.textContent)).toStrictEqual(['February', 'March', 'April'])
})

test('<Select isMultiple> cannot select disabled items', () => {
const { getAllByRole, getByRole, getByTestId } = render(
<Select
dataTestId="select"
isMultiple
name="select"
options={MONTHS}
value={['february', 'march']}
/>
)

const select = getByTestId('select')
let tags = getAllByRole('listitem')
expect(tags.length).toBe(2)

fireEvent.click(select)

// Click 1st result (January)
const options = getByRole('listbox').querySelectorAll('li')
fireEvent.click(options[0])

tags = getAllByRole('listitem')
expect(tags.length).toBe(2)
expect(tags.map(tag => tag.textContent)).toStrictEqual(['February', 'March'])
})

test('<Select> can accept value, label or object as value', () => {
const { getAllByRole, getByRole, getByTestId } = render(
<Select
Expand Down Expand Up @@ -398,8 +448,8 @@ test('<Select isCreatable isMultiple> can create new items', () => {
target: {
name: 'select',
value: [
{ label: 'February', value: 'february' },
{ label: 'March', value: 'march' },
{ disabled: false, label: 'February', value: 'february' },
{ disabled: false, label: 'March', value: 'march' },
{ label: 'Kayab', value: 'kayab' },
],
},
Expand Down Expand Up @@ -469,3 +519,62 @@ test('<Select groupsEnabled> shows groups header', () => {
)
})
})

test('<Select groupsEnabled> onChange with correct (object) values', () => {
const handleChange = jest.fn()

const { getByRole, getByTestId } = render(
<Select
dataTestId="select"
groupsEnabled
name="select"
onChange={handleChange}
options={SOCIAL_OPT_GROUP}
renderGroupHeader={({ label, options }) => (
<div data-testid="group-header">
<h4>{label}</h4>
<span>{options.length}</span>
</div>
)}
/>
)

const select = getByTestId('select')
fireEvent.click(select)

const options = getByRole('listbox').querySelectorAll('li')
fireEvent.click(options[1])

expect(handleChange).toHaveBeenCalledTimes(1)
expect(handleChange).toHaveBeenCalledWith(
'Dribbble',
expect.objectContaining({
target: { name: 'select', value: { value: 'dribbble', label: 'Dribbble' } },
}) // Ignore preventDefault
)
})

test('<Select groupsEnabled> onChange on disabled option', () => {
const { getByRole, getByTestId } = render(
<SelectWrapper
dataTestId="select"
groupsEnabled
name="select"
options={SOCIAL_OPT_GROUP}
renderGroupHeader={({ label, options }) => (
<div data-testid="group-header">
<h4>{label}</h4>
<span>{options.length}</span>
</div>
)}
/>
)

const select = getByTestId('select')
fireEvent.click(select)

const options = getByRole('listbox').querySelectorAll('li')

fireEvent.click(options[0])
expect(select).toHaveTextContent('')
})
13 changes: 10 additions & 3 deletions packages/Select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ import { multipleSelections } from './multipleSelections'
import * as S from './styles'

export type OptionValue = string | number
export type Option = { label: string; value: OptionValue; icon?: React.ReactElement }
export type Option = {
label: string
value: OptionValue
icon?: React.ReactElement
disabled?: boolean
}
export type OptionGroup = { label: string; options: Option[] }
export type OptionItem = Option | OptionGroup
export type Options = Array<Option | OptionGroup>
export type SelectValue = string | number | string[] | Option | (string | Option)[]
export type SelectValue = string | number | string[] | Option | (string | number | Option)[]

export interface SelectOptions extends DefaultFieldStylesProps {
/** We need to add `autoComplete` off to avoid select UI issues when is an input */
Expand Down Expand Up @@ -173,7 +178,7 @@ export const Select = forwardRef<'input', SelectProps>(
let newItems
let isClearInput

if (!option) {
if (!option || option?.disabled) {
// If removing option
newItems = isMultiple ? selected : []
isClearInput = true
Expand Down Expand Up @@ -330,6 +335,7 @@ export const Select = forwardRef<'input', SelectProps>(
return (
<S.Item
allowUnselectFromList={allowUnselectFromList}
isDisabled={option.disabled}
isHighlighted={highlightedIndex === index}
isMultiple={isMultiple}
key={option.value}
Expand All @@ -350,6 +356,7 @@ export const Select = forwardRef<'input', SelectProps>(
acc.itemsToRender.push(
<S.Item
allowUnselectFromList={allowUnselectFromList}
isDisabled={result.disabled}
isHighlighted={highlightedIndex === resultIndex}
isMultiple={isMultiple}
key={result.value}
Expand Down
3 changes: 3 additions & 0 deletions packages/Select/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const Menu = styled.ul`
export const Item = styled.li(
({
allowUnselectFromList,
isDisabled,
isHighlighted,
isMultiple,
isSelected,
Expand All @@ -91,11 +92,13 @@ export const Item = styled.li(
isHighlighted: boolean
isMultiple: boolean
isSelected: boolean
isDisabled?: boolean
}) => css`
color: nude.800;
${isHighlighted && th('defaultFields.select.highlighted')};
${isSelected && !isMultiple && th('defaultFields.select.selected')};
${isSelected && isMultiple && !allowUnselectFromList && th('defaultFields.select.existing')};
${isDisabled && th('defaultFields.select.disabled')};
${overflowEllipsis};
padding: sm;
list-style: none;
Expand Down

0 comments on commit 56ebc97

Please sign in to comment.