Skip to content

Commit

Permalink
FCT-1191: filters: select-inputs: display checkboxes for options in m…
Browse files Browse the repository at this point in the history
…ultiselectable select inputs (#2975)

* feat: add custom option component + prop-config for react select to select-utils package

* feat: add new checkbox optionStyle to select-input

* fix: make checkbox optionStyle utils exportatble

* fix: add missing dependency

* feat(filters): add filter appearance options

* feat(filters): introduce filter appearance for filters visual features

* feat(filters): fix issue with placeholder disappearance

* feat(filters): extract intl

* feat(filters): readme and changesets

* feat(filters): use controlShouldRenderValue to determine for input display options

* feat(filters): remove unused comments

* feat(filters): rename file

* feat(filters): adding persisted menuopen and additional select option text

* feat(filters): select option styles border-radius, condensed, box-shadow, font-size

* feat(filters): use design tokens variables

---------

Co-authored-by: Ddouglasz <[email protected]>
Co-authored-by: Douglas Egiemeh <[email protected]>
  • Loading branch information
3 people authored Nov 7, 2024
1 parent 3ca51bc commit 32f444f
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 45 deletions.
9 changes: 9 additions & 0 deletions .changeset/young-fishes-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@commercetools-uikit/select-input': minor
'@commercetools-uikit/select-utils': minor
'@commercetools-uikit/tag': minor
'@commercetools-uikit/i18n': minor
---

As the filters component is being built, there are some visual modifications that need to happen in the select input to support the designs and ux of the filters pattern.
This changes provide the updates needed for the select-input to support the visual styling of filter patterns.
4 changes: 3 additions & 1 deletion packages/components/inputs/select-input/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default Example;

| Props | Type | Required | Default | Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------- | :------: | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `appearance` | `union`<br/>Possible values:<br/>`'default' , 'quiet'` | | `'default'` | Indicates the appearance of the input.&#xA;Quiet appearance is meant to be used with the `horizontalConstraint="auto"`. |
| `appearance` | `union`<br/>Possible values:<br/>`'default' , 'quiet' , 'filter'` | | `'default'` | Indicates the appearance of the input.&#xA;`quiet` appearance is meant to be used with the `horizontalConstraint="auto"`.&#xA;`filter` appearance provides a different look and feel for the select input when it is used as part of a filter component. |
| `horizontalConstraint` | `union`<br/>Possible values:<br/>`, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | | |
| `hasError` | `boolean` | | | Indicates that input has errors |
| `isReadOnly` | `boolean` | | | Is the select read-only |
Expand Down Expand Up @@ -98,13 +98,15 @@ export default Example;
| `onFocus` | `ReactSelectProps['onFocus']` | | | Handle focus events on the control&#xA;<br>&#xA;[Props from React select was used](https://react-select.com/props) |
| `onInputChange` | `ReactSelectProps['onInputChange']` | | | Handle change events on the input&#xA;<br>&#xA;[Props from React select was used](https://react-select.com/props) |
| `options` | `union`<br/>Possible values:<br/>`TOption[] , TOptionObject[]` | | `[]` | Array of options that populate the select menu |
| `optionStyle` | `union`<br/>Possible values:<br/>`'list' , 'checkbox'` | | `'list'` | defines how options are rendered |
| `showOptionGroupDivider` | `boolean` | | | |
| `placeholder` | `ReactSelectProps['placeholder']` | | | Placeholder text for the select value&#xA;<br>&#xA;[Props from React select was used](https://react-select.com/props) |
| `tabIndex` | `ReactSelectProps['tabIndex']` | | | Sets the tabIndex attribute on the input&#xA;<br>&#xA;[Props from React select was used](https://react-select.com/props) |
| `tabSelectsValue` | `ReactSelectProps['tabSelectsValue']` | | | Select the currently focused option when the user presses tab&#xA;<br>&#xA;[Props from React select was used](https://react-select.com/props) |
| `value` | `ReactSelectProps['value']` | | | The value of the select; reflected by the selected option&#xA;<br>&#xA;[Props from React select was used](https://react-select.com/props) |
| `minMenuWidth` | `union`<br/>Possible values:<br/>`, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | | The min width (a range of values from the horizontalConrtaint set of values) for which the select-input menu&#xA;is allowed to shrink. If unset, the menu will shrink to a default value. |
| `maxMenuWidth` | `union`<br/>Possible values:<br/>`, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 'scale', 'auto'` | | | The max width (a range of values from the horizontalConrtaint set of values) for which the select-input menu&#xA;is allowed to grow. If unset, the menu will grow horrizontally to fill its parent. |
| `count` | `number` | | | An additional value displayed on the select options menu. This value is only available in the checkbox option style when appearance is set to filter. |

## Signatures

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,29 @@ const options = [
{
label: 'Animals 1',
options: [
{ value: 'platypus', label: 'Platypus' },
{ value: 'goat', label: 'Goat' },
{ value: 'platypus', label: 'Platypus', count: 103 },
{ value: 'goat', label: 'Goat', count: 12.365 },
{ value: 'giraffe', label: 'Giraffe' },
{ value: 'whale', label: 'Whale' },
{ value: 'killer-whale', label: 'Killer Whale', isDisabled: true },
{ value: 'otter', label: 'Otter' },
{ value: 'whale', label: 'Whale', count: 1123 },
{
value: 'killer-whale',
label: 'Killer Whale',
isDisabled: true,
count: 1,
},
{ value: 'otter', label: 'Otter', count: 10.356 },
{ value: 'elephant', label: 'Elephant' },
{ value: 'rat', label: 'Rat' },
{ value: 'anteater', label: 'Anteater' },
{ value: 'alligator', label: 'Alligator' },
{ value: 'dog', label: 'Dog' },
{ value: 'rat', label: 'Rat', count: 0 },
{ value: 'anteater', label: 'Anteater', count: 100335456413 },
{ value: 'alligator', label: 'Alligator', count: 1 },
{ value: 'dog', label: 'Dog', count: 5 },
{ value: 'pig', label: 'Pig' },
{ value: 'hippopotamus', label: 'Hippopotamus' },
{ value: 'lion', label: 'Lion' },
{ value: 'monkey', label: 'Monkey' },
{ value: 'hippopotamus', label: 'Hippopotamus', count: 10 },
{ value: 'lion', label: 'Lion', count: 111 },
{ value: 'monkey', label: 'Monkey', count: 57 },
{ value: 'kangaroo', label: 'Kangaroo' },
{ value: 'flamingo', label: 'Flamingo' },
{ value: 'moose', label: 'Moose' },
{ value: 'flamingo', label: 'Flamingo', count: 3 },
{ value: 'moose', label: 'Moose', count: 1003 },
],
},
{
Expand Down Expand Up @@ -99,19 +104,19 @@ const options = [
label: 'Animals 3',
options: [
{ value: 'llama', label: 'Llama' },
{ value: 'seal', label: 'Seal' },
{ value: 'hawk', label: 'Hawk' },
{ value: 'wolf', label: 'Wolf' },
{ value: 'yak', label: 'Yak' },
{ value: 'rhinoceros', label: 'Rhinoceros' },
{ value: 'alpaca', label: 'Alpaca' },
{ value: 'zebra', label: 'Zebra' },
{ value: 'cat', label: 'Cat' },
{ value: 'seal', label: 'Seal', count: 245 },
{ value: 'hawk', label: 'Hawk', count: 23 },
{ value: 'wolf', label: 'Wolf', count: 89 },
{ value: 'yak', label: 'Yak', count: 6 },
{ value: 'rhinoceros', label: 'Rhinoceros', count: 9 },
{ value: 'alpaca', label: 'Alpaca', count: 54 },
{ value: 'zebra', label: 'Zebra', count: 302 },
{ value: 'cat', label: 'Cat', count: 1 },
{ value: 'rabbit', label: 'Rabbit' },
{ value: 'turtle', label: 'Turtle' },
{ value: 'cow', label: 'Cow' },
{ value: 'turkey', label: 'Turkey' },
{ value: 'deer', label: 'Deer' },
{ value: 'deer', label: 'Deer', count: 12 },
],
},
];
Expand Down Expand Up @@ -140,3 +145,32 @@ BasicExample.args = {
options,
horizontalConstraint: 7,
};

export const CheckboxOptionStyle: Story = (args) => {
const [value, setValue] = useState<string | string[] | null | undefined>([]);

useEffect(() => {
setValue([]);
}, [args.isMulti]);

return (
<div>
<SelectInput
{...args}
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<pre>{JSON.stringify(value, null, 2)}</pre>
</div>
);
};

CheckboxOptionStyle.args = {
options,
horizontalConstraint: 7,
optionStyle: 'checkbox',
isMulti: true,
appearance: 'filter',
};
58 changes: 50 additions & 8 deletions packages/components/inputs/select-input/src/select-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import {
createSelectStyles,
messages,
warnIfMenuPortalPropsAreMissing,
optionStyleCheckboxComponents,
optionsStyleCheckboxSelectProps,
} from '@commercetools-uikit/select-utils';
import { filterDataAttributes } from '@commercetools-uikit/utils';
import { SearchIcon } from '@commercetools-uikit/icons';

const customizedComponents = {
DropdownIndicator,
Expand All @@ -27,6 +30,7 @@ const customizedComponents = {
export type TOption = {
value: string;
label?: ReactNode;
isDisabled?: boolean;
};

export type TOptionObject = {
Expand All @@ -47,9 +51,10 @@ export type TCustomEvent = {
export type TSelectInputProps = {
/**
* Indicates the appearance of the input.
* Quiet appearance is meant to be used with the `horizontalConstraint="auto"`.
* `quiet` appearance is meant to be used with the `horizontalConstraint="auto"`.
* `filter` appearance provides a different look and feel for the select input when it is used as part of a filter component.
*/
appearance?: 'default' | 'quiet';
appearance?: 'default' | 'quiet' | 'filter';
horizontalConstraint?:
| 3
| 4
Expand Down Expand Up @@ -308,6 +313,8 @@ export type TSelectInputProps = {
* Array of options that populate the select menu
*/
options: TOptions;
/** defines how options are rendered */
optionStyle: 'list' | 'checkbox';
showOptionGroupDivider?: boolean;
// pageSize: PropTypes.number,
/**
Expand Down Expand Up @@ -379,16 +386,25 @@ export type TSelectInputProps = {
| 16
| 'scale'
| 'auto';
/**
* An additional value displayed on the select options menu. This value is only available in the checkbox option style when appearance is set to filter.
*/
count?: number;
};

const defaultProps: Pick<
TSelectInputProps,
'appearance' | 'maxMenuHeight' | 'menuPortalZIndex' | 'options'
| 'appearance'
| 'maxMenuHeight'
| 'menuPortalZIndex'
| 'options'
| 'optionStyle'
> = {
appearance: 'default',
maxMenuHeight: 220,
menuPortalZIndex: 1,
options: [],
optionStyle: 'list',
};

const isOptionObject = (
Expand All @@ -405,7 +421,9 @@ const SelectInput = (props: TSelectInputProps) => {
});

const placeholder =
props.placeholder || intl.formatMessage(messages.placeholder);
props.appearance === 'filter' && !props.placeholder
? intl.formatMessage(messages.selectInputAsFilterPlaceholder)
: props.placeholder || intl.formatMessage(messages.placeholder);
// Options can be grouped as
// const colourOptions = [{ value: 'green', label: 'Green' }];
// const flavourOptions = [{ value: 'vanilla', label: 'Vanilla' }];
Expand Down Expand Up @@ -462,10 +480,23 @@ const SelectInput = (props: TSelectInputProps) => {
),
}
: {}),
...(props.appearance === 'filter' && {
DropdownIndicator: () => <SearchIcon color="neutral60" />,
ClearIndicator: null,
}),
...(props.optionStyle === 'checkbox'
? optionStyleCheckboxComponents(props.appearance)
: {}),
...props.components,
} as ReactSelectProps['components']
}
menuIsOpen={props.isReadOnly ? false : props.menuIsOpen}
menuIsOpen={
props.isReadOnly
? false
: props.appearance === 'filter'
? true
: props.menuIsOpen
}
styles={
createSelectStyles({
hasWarning: props.hasWarning,
Expand All @@ -475,7 +506,8 @@ const SelectInput = (props: TSelectInputProps) => {
appearance: props.appearance,
isDisabled: props.isDisabled,
isReadOnly: props.isReadOnly,
isCondensed: props.isCondensed,
isCondensed:
props.appearance === 'filter' ? true : props.isCondensed,
iconLeft: props.iconLeft,
isMulti: props.isMulti,
hasValue: !isEmpty(selectedOptions),
Expand All @@ -496,7 +528,9 @@ const SelectInput = (props: TSelectInputProps) => {
isClearable={props.isReadOnly ? false : props.isClearable}
isDisabled={props.isDisabled}
isOptionDisabled={props.isOptionDisabled}
hideSelectedOptions={props.hideSelectedOptions}
{...(props.optionStyle === 'checkbox'
? optionsStyleCheckboxSelectProps()
: { hideSelectedOptions: props.hideSelectedOptions })}
// @ts-ignore
isReadOnly={props.isReadOnly}
isMulti={props.isMulti}
Expand All @@ -505,6 +539,7 @@ const SelectInput = (props: TSelectInputProps) => {
maxMenuHeight={props.maxMenuHeight}
menuPortalTarget={props.menuPortalTarget}
menuShouldBlockScroll={props.menuShouldBlockScroll}
// @ts-expect-error: optionStyle 'checkbox' will override this property (if set)
closeMenuOnSelect={props.closeMenuOnSelect}
name={props.name}
noOptionsMessage={
Expand Down Expand Up @@ -572,8 +607,15 @@ const SelectInput = (props: TSelectInputProps) => {
tabSelectsValue={props.tabSelectsValue}
value={selectedOptions}
iconLeft={props.iconLeft}
controlShouldRenderValue={props.controlShouldRenderValue}
controlShouldRenderValue={
props.appearance === 'filter'
? false
: props.controlShouldRenderValue
}
menuPlacement="auto"
{...(props.optionStyle === 'checkbox'
? optionsStyleCheckboxSelectProps()
: {})}
/>
</div>
</Constraints.Horizontal>
Expand Down
Loading

0 comments on commit 32f444f

Please sign in to comment.