-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* selectable findbyvalue * expose Orientation * singe select * adding multiSelect using selectable[] * tests for multi and optional props * semicolumn * removing undesired change on keycloak login --------- Co-authored-by: Adam Loup <aloup@enquizit.com>
- Loading branch information
1 parent
bbba5f5
commit 3aeb39f
Showing
10 changed files
with
313 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './single/SingleSelect'; | ||
export * from './multi/MultiSelect'; |
46 changes: 46 additions & 0 deletions
46
apps/modernization-ui/src/design-system/select/multi/MultiSelect.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { render } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { MultiSelect } from './MultiSelect'; | ||
|
||
describe('MultiSelect', () => { | ||
const options = [ | ||
{ name: 'Option One', value: '1', label: 'Option One' }, | ||
{ name: 'Option Two', value: '2', label: 'Option Two' }, | ||
{ name: 'Option Three', value: '3', label: 'Option Three' } | ||
]; | ||
|
||
it('should display options when clicked', async () => { | ||
const { getByText, getByRole } = render( | ||
<MultiSelect id="test-multi-select" name="test-multi-select" label="Test Multi Select" options={options} /> | ||
); | ||
|
||
const component = getByRole('combobox'); | ||
|
||
userEvent.click(component); | ||
|
||
options.forEach((option) => { | ||
expect(getByText(option.label)).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it('should allow selecting multiple options', async () => { | ||
const onChange = jest.fn(); | ||
const { getByText, getByRole } = render( | ||
<MultiSelect | ||
id="test-multi-select" | ||
name="test-multi-select" | ||
label="Test Multi Select" | ||
options={options} | ||
onChange={onChange} | ||
/> | ||
); | ||
|
||
const component = getByRole('combobox'); | ||
|
||
userEvent.click(component); | ||
|
||
userEvent.click(getByText('Option One')); | ||
|
||
expect(onChange).toHaveBeenCalledWith([expect.objectContaining({ value: '1', label: 'Option One' })]); | ||
}); | ||
}); |
81 changes: 81 additions & 0 deletions
81
apps/modernization-ui/src/design-system/select/multi/MultiSelect.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import React, { useState } from 'react'; | ||
import Select, { MultiValue, components } from 'react-select'; | ||
import { EntryWrapper, Orientation } from 'components/Entry'; | ||
import { Selectable } from 'options'; | ||
|
||
type MultiSelectProps = { | ||
id: string; | ||
name: string; | ||
label: string; | ||
options: Selectable[]; | ||
value?: Selectable[]; | ||
onChange?: (value: Selectable[]) => void; | ||
orientation?: Orientation; | ||
error?: string; | ||
required?: boolean; | ||
placeholder?: string; | ||
disabled?: boolean; | ||
}; | ||
|
||
const CheckedOption = (props: any) => { | ||
return ( | ||
<> | ||
<components.Option {...props}> | ||
<input type="checkbox" checked={props.isSelected} readOnly /> <label>{props.label}</label> | ||
</components.Option> | ||
</> | ||
); | ||
}; | ||
|
||
export const MultiSelect: React.FC<MultiSelectProps> = ({ | ||
id, | ||
name, | ||
label, | ||
options, | ||
value = [], | ||
onChange, | ||
orientation = 'vertical', | ||
error, | ||
required, | ||
placeholder = '- Select -', | ||
disabled = false | ||
}) => { | ||
const [searchText, setSearchText] = useState(''); | ||
|
||
const handleOnChange = (newValue: MultiValue<Selectable>) => { | ||
if (onChange) { | ||
onChange(newValue as Selectable[]); | ||
} | ||
}; | ||
|
||
const handleInputChange = (searchText: string, action: { action: string }) => { | ||
if (action.action !== 'input-blur' && action.action !== 'set-value') { | ||
setSearchText(searchText); | ||
} | ||
}; | ||
return ( | ||
<div> | ||
<EntryWrapper orientation={orientation} label={label} htmlFor={id} required={required} error={error}> | ||
<Select<Selectable, true> | ||
isMulti | ||
id={id} | ||
name={name} | ||
options={options} | ||
value={value} | ||
onChange={handleOnChange} | ||
placeholder={placeholder} | ||
isDisabled={disabled} | ||
classNamePrefix="multi-select" | ||
hideSelectedOptions={false} | ||
closeMenuOnSelect={false} | ||
closeMenuOnScroll={false} | ||
inputValue={searchText} | ||
onInputChange={handleInputChange} | ||
getOptionLabel={(option) => option.label} | ||
getOptionValue={(option) => option.value} | ||
components={{ Option: CheckedOption }} | ||
/> | ||
</EntryWrapper> | ||
</div> | ||
); | ||
}; |
73 changes: 73 additions & 0 deletions
73
apps/modernization-ui/src/design-system/select/single/SingleSelect.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { render } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { SinlgeSelect } from './SingleSelect'; | ||
|
||
describe('when selecting a single item from a specific set of items', () => { | ||
it('should display the SingleSelect without a value checked', () => { | ||
const { queryByRole } = render( | ||
<SinlgeSelect | ||
id="test-id" | ||
label="Test Label" | ||
options={[ | ||
{ name: 'name-one', value: 'value-one', label: 'label-one' }, | ||
{ name: 'name-two', value: 'value-two', label: 'label-two' }, | ||
{ name: 'name-three', value: 'value-three', label: 'label-three' }, | ||
{ name: 'name-four', value: 'value-four', label: 'label-four' } | ||
]} | ||
name="test-name" | ||
placeholder="place-holder-value" | ||
/> | ||
); | ||
|
||
const checked = queryByRole('option', { selected: true }); | ||
|
||
expect(checked).toHaveTextContent('place-holder-value'); | ||
}); | ||
|
||
it('should display the SingleSelect with the value checked', () => { | ||
const { getByRole } = render( | ||
<SinlgeSelect | ||
id="test-id" | ||
label="Test Label" | ||
options={[ | ||
{ name: 'name-one', value: 'value-one', label: 'label-one' }, | ||
{ name: 'name-two', value: 'value-two', label: 'label-two' }, | ||
{ name: 'name-three', value: 'value-three', label: 'label-three' }, | ||
{ name: 'name-four', value: 'value-four', label: 'label-four' } | ||
]} | ||
name="test-name" | ||
value={{ name: 'name-three', value: 'value-three', label: 'label-three' }} | ||
/> | ||
); | ||
|
||
const checked = getByRole('option', { name: 'name-three', selected: true }); | ||
|
||
expect(checked).toHaveTextContent('name-three'); | ||
}); | ||
}); | ||
|
||
describe('when one of the options is clicked', () => { | ||
it('should mark the option as checked', () => { | ||
const { getByRole } = render( | ||
<SinlgeSelect | ||
id="test-id" | ||
label="Test Label" | ||
options={[ | ||
{ name: 'name-one', value: 'value-one', label: 'label-one' }, | ||
{ name: 'name-two', value: 'value-two', label: 'label-two' }, | ||
{ name: 'name-three', value: 'value-three', label: 'label-three' }, | ||
{ name: 'name-four', value: 'value-four', label: 'label-four' } | ||
]} | ||
name="test-name" | ||
/> | ||
); | ||
|
||
const select = getByRole('combobox', { name: 'Test Label' }); | ||
|
||
userEvent.selectOptions(select, 'name-four'); | ||
|
||
const checked = getByRole('option', { selected: true }); | ||
|
||
expect(checked).toHaveTextContent('name-four'); | ||
}); | ||
}); |
70 changes: 70 additions & 0 deletions
70
apps/modernization-ui/src/design-system/select/single/SingleSelect.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { ChangeEvent } from 'react'; | ||
import { Select as TrussworksSelect } from '@trussworks/react-uswds'; | ||
import { EntryWrapper, Orientation } from 'components/Entry'; | ||
import { Selectable, findByValue } from 'options'; | ||
|
||
const renderOptions = (placeholder: string, options: Selectable[]) => ( | ||
<> | ||
<option value="">{placeholder}</option> | ||
{options?.map((item, index) => ( | ||
<option key={index} value={item.value}> | ||
{item.name} | ||
</option> | ||
))} | ||
</> | ||
); | ||
|
||
type Props = { | ||
id: string; | ||
orientation?: Orientation; | ||
label: string; | ||
options: Selectable[]; | ||
value?: Selectable | null; | ||
onChange?: (value?: Selectable) => void; | ||
error?: string; | ||
required?: boolean; | ||
} & Omit<JSX.IntrinsicElements['select'], 'defaultValue' | 'onChange' | 'value'>; | ||
|
||
const SinlgeSelect = ({ | ||
id, | ||
label, | ||
options, | ||
value, | ||
onChange, | ||
orientation = 'vertical', | ||
error, | ||
required, | ||
placeholder = '- Select -', | ||
...inputProps | ||
}: Props) => { | ||
const find = findByValue(options); | ||
|
||
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => { | ||
if (onChange) { | ||
const selected = find(event.target.value); | ||
onChange(selected); | ||
} | ||
}; | ||
|
||
// In order for the defaultValue to be applied the component has to be re-created when it goes from null to non null. | ||
const Wrapped = () => ( | ||
<TrussworksSelect | ||
{...inputProps} | ||
id={id} | ||
name={inputProps.name ?? id} | ||
defaultValue={value?.value} | ||
placeholder="-Select-" | ||
onChange={handleChange}> | ||
{renderOptions(placeholder, options)} | ||
</TrussworksSelect> | ||
); | ||
|
||
return ( | ||
<EntryWrapper orientation={orientation} label={label} htmlFor={id} required={required} error={error}> | ||
{value && <Wrapped />} | ||
{!value && <Wrapped />} | ||
</EntryWrapper> | ||
); | ||
}; | ||
|
||
export { SinlgeSelect }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { findByValue } from './findByValue'; | ||
|
||
describe('when searching for selectables by value', () => { | ||
it('should return selectable when searching by known value', () => { | ||
const selectables = [ | ||
{ name: 'name-one', value: 'value-one', label: 'label-one' }, | ||
{ name: 'name-two', value: 'value-two', label: 'label-two' }, | ||
{ name: 'name-three', value: 'value-three', label: 'label-three' }, | ||
{ name: 'name-four', value: 'value-four', label: 'label-four' } | ||
]; | ||
|
||
const actual = findByValue(selectables)('value-three'); | ||
|
||
expect(actual).toEqual(expect.objectContaining({ value: 'value-three' })); | ||
}); | ||
|
||
it('should not return selectable when searching by unknown value', () => { | ||
const selectables = [ | ||
{ name: 'name-one', value: 'value-one', label: 'label-one' }, | ||
{ name: 'name-two', value: 'value-two', label: 'label-two' }, | ||
{ name: 'name-three', value: 'value-three', label: 'label-three' }, | ||
{ name: 'name-four', value: 'value-four', label: 'label-four' } | ||
]; | ||
|
||
const actual = findByValue(selectables)('value-unknown'); | ||
|
||
expect(actual).toBeUndefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Selectable } from './selectable'; | ||
|
||
const findByValue = (selectables: Selectable[]) => (value: string) => | ||
selectables.find((selectable) => selectable.value === value); | ||
|
||
export { findByValue }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export type { Selectable } from './selectable'; | ||
export { findByValue } from './findByValue'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters