Skip to content

Commit

Permalink
feat(combobox): expose downshift actions through downshiftActions ref…
Browse files Browse the repository at this point in the history
… prop (#17118)

* chore(wip): expose actions using downshiftActions ref prop

* docs(combobox): add downshiftActions

* fix(combobox): fix typings

* chore: update snaps

* fix: test story

* fix(a11y): lock a11y checker to previous ruleset
  • Loading branch information
tay1orjones committed Aug 9, 2024
1 parent 43a514c commit 6bd73bf
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 49 deletions.
2 changes: 1 addition & 1 deletion achecker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
const path = require('path');

module.exports = {
ruleArchive: 'latest',
ruleArchive: '17June2024',
policies: ['Custom_Ruleset'],
failLevels: ['violation'],
reportLevels: [
Expand Down
10 changes: 10 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,16 @@ Map {
"disabled": Object {
"type": "bool",
},
"downshiftActions": Object {
"args": Array [
Object {
"current": Object {
"type": "any",
},
},
],
"type": "exact",
},
"downshiftProps": Object {
"type": "object",
},
Expand Down
60 changes: 53 additions & 7 deletions packages/react/src/components/ComboBox/ComboBox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,64 @@ next element.
<Combobox disabled />
```

## Combobox `downshiftProps`
## Combobox uses Downshift

Our `Combobox` component utilizes [Downshift](https://www.downshift-js.com/)
under the hood to help provide complex yet accessible custom dropdown
components. We provide access to the built in Downshift features with this prop.
components.

Use with caution: anything you define here overrides the components' internal
handling of that prop. Downshift internals are subject to change, and in some
cases they can not be shimmed to shield you from potentially breaking changes.
### `downshiftProps`

For more information, checkout the Downshift prop
[documentation](https://www.downshift-js.com/downshift#props-used-in-examples)
`downshiftProps` is made available as a passthrough to the underlying downshift
`useCombobox` hook.

**Use with caution:** anything you define here overrides the components'
internal handling of that prop. Downshift internals are subject to change, and
in some cases they can not be shimmed to shield you from potentially breaking
changes.

For more information, checkout the Downshift `useCombobox` props
[documentation](https://github.com/downshift-js/downshift/tree/v9.0.7/src/hooks/useCombobox#basic-props)

### Combobox `downshiftActions`

The downshift action methods are made available through the `downshiftActions`
prop. While not recommended, this prop allows you to modify downshift's internal
state without having to fully control the component.

**Use with caution:** calling these actions modifies the internal state of
downshift. It may conflict with or override the state management used within
Combobox. Downshift APIs and internals are subject to change, and in some cases
they can not be shimmed by Carbon to shield you from potentially breaking
changes.

#### `downshiftActions` Usage

Provide a ref that will be mutated to contain an object of downshift action
functions. These can be called to change the internal state of the downshift
useCombobox hook.

```
const downshiftActions = useRef();
return (
<ComboBox
...
downshiftActions={downshiftActions}
downshiftProps={{
onStateChange: (changes) => {
if (changes.selectedItem === null) {
downshiftActions?.current?.openMenu?.();
return;
}
},
}}
/>
);
```

For more information, checkout the Downshift `useCombobox` action functions
[documentation](https://github.com/downshift-js/downshift/tree/v9.0.7/src/hooks/useCombobox#actions)

## Placeholders and Labeling

Expand Down
38 changes: 37 additions & 1 deletion packages/react/src/components/ComboBox/ComboBox.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import React, { useRef } from 'react';

import { WithLayer } from '../../../.storybook/templates/WithLayer';

Expand Down Expand Up @@ -61,6 +61,42 @@ export default {
},
};

export const DownshiftActionsTest = () => {
const downshiftActions = useRef();

return (
<div style={{ width: 300 }}>
<ComboBox
onChange={() => {}}
id="carbon-combobox"
items={items}
itemToString={(item) => (item ? item.text : '')}
titleText="ComboBox title"
helperText="Combobox helper text"
downshiftActions={downshiftActions}
downshiftProps={{
onStateChange: (changes) => {
console.log('onStateChange changes', changes);

if (changes.selectedItem === null) {
downshiftActions?.current?.openMenu?.();
return;
}
if (changes?.isOpen && changes?.inputValue === 'Option 1') {
downshiftActions?.current?.setInputValue?.('');
return;
}
if (!changes?.isOpen && changes?.inputValue !== 'Option 1') {
downshiftActions?.current?.setInputValue?.('Option 1');
return;
}
},
}}
/>
</div>
);
};

export const Default = () => (
<div style={{ width: 300 }}>
<ComboBox
Expand Down
93 changes: 81 additions & 12 deletions packages/react/src/components/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import cx from 'classnames';
import { useCombobox, UseComboboxProps } from 'downshift';
import { useCombobox, UseComboboxProps, UseComboboxActions } from 'downshift';
import PropTypes from 'prop-types';
import React, {
useContext,
Expand Down Expand Up @@ -188,13 +188,31 @@ export interface ComboBoxProps<ItemType>
disabled?: boolean;

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*
*/
downshiftProps?: Partial<UseComboboxProps<ItemType>>;

/**
* Provide a ref that will be mutated to contain an object of downshift
* action functions. These can be called to change the internal state of the
* downshift useCombobox hook.
*
* **Use with caution:** calling these actions modifies the internal state of
* downshift. It may conflict with or override the state management used within
* Combobox. Downshift APIs and internals are subject to change, and in some
* cases they can not be shimmed by Carbon to shield you from potentially breaking
* changes.
*/
downshiftActions?: React.MutableRefObject<
UseComboboxActions<ItemType> | undefined
>;

/**
* Provide helper text that is used alongside the control label for
* additional help
Expand Down Expand Up @@ -336,6 +354,7 @@ const ComboBox = forwardRef(
className: containerClassName,
direction = 'bottom',
disabled = false,
downshiftActions,
downshiftProps,
helperText,
id,
Expand Down Expand Up @@ -589,17 +608,26 @@ const ComboBox = forwardRef(
}

const {
// Prop getters
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,

// State
isOpen,
highlightedIndex,
selectItem,
selectedItem,
toggleMenu,

// Actions
closeMenu,
openMenu,
reset,
selectItem,
setHighlightedIndex,
setInputValue: downshiftSetInputValue,
toggleMenu,
} = useCombobox({
items: filterItems(items, itemToString, inputValue),
inputValue: inputValue,
Expand Down Expand Up @@ -636,6 +664,31 @@ const ComboBox = forwardRef(
...downshiftProps,
});

useEffect(() => {
// Used to expose the downshift actions to consumers for use with downshiftProps
// An odd pattern, here we mutate the value stored in the ref provided from the consumer.
// A riff of https://gist.github.com/gaearon/1a018a023347fe1c2476073330cc5509
if (downshiftActions) {
downshiftActions.current = {
closeMenu,
openMenu,
reset,
selectItem,
setHighlightedIndex,
setInputValue: downshiftSetInputValue,
toggleMenu,
};
}
}, [
closeMenu,
openMenu,
reset,
selectItem,
setHighlightedIndex,
downshiftSetInputValue,
toggleMenu,
]);

const buttonProps = getToggleButtonProps({
disabled: disabled || readOnly,
onClick: handleToggleClick(isOpen),
Expand Down Expand Up @@ -728,7 +781,7 @@ const ComboBox = forwardRef(
{...getInputProps({
'aria-controls': isOpen ? undefined : menuProps.id,
placeholder,
ref: { ...mergeRefs(textInput, ref) },
ref: mergeRefs(textInput, ref),
onKeyDown: (
event: KeyboardEvent<HTMLInputElement> & {
preventDownshiftDefault: boolean;
Expand Down Expand Up @@ -937,14 +990,30 @@ ComboBox.propTypes = {
disabled: PropTypes.bool,

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
downshiftProps: PropTypes.object as React.Validator<
UseComboboxProps<unknown>
>,

/**
* Provide a ref that will be mutated to contain an object of downshift
* action functions. These can be called to change the internal state of the
* downshift useCombobox hook.
*
* **Use with caution:** calling these actions modifies the internal state of
* downshift. It may conflict with or override the state management used within
* Combobox. Downshift APIs and internals are subject to change, and in some
* cases they can not be shimmed by Carbon to shield you from potentially breaking
* changes.
*/
downshiftActions: PropTypes.exact({ current: PropTypes.any }),

/**
* Provide helper text that is used alongside the control label for
* additional help
Expand Down
20 changes: 12 additions & 8 deletions packages/react/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ export interface DropdownProps<ItemType>
disabled?: boolean;

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
downshiftProps?: Partial<UseSelectProps<ItemType>>;

Expand Down Expand Up @@ -662,10 +664,12 @@ Dropdown.propTypes = {
disabled: PropTypes.bool,

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
downshiftProps: PropTypes.object as React.Validator<UseSelectProps<unknown>>,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ FluidMultiSelect.propTypes = {
disabled: PropTypes.bool,

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
downshiftProps: PropTypes.object,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,12 @@ export interface FilterableMultiSelectProps<ItemType>
disabled?: boolean;

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
downshiftProps?: UseMultipleSelectionProps<ItemType>;

Expand Down Expand Up @@ -959,10 +961,12 @@ FilterableMultiSelect.propTypes = {
disabled: PropTypes.bool,

/**
* Additional props passed to Downshift. Use with caution: anything you define
* here overrides the components' internal handling of that prop. Downshift
* internals are subject to change, and in some cases they can not be shimmed
* to shield you from potentially breaking changes.
* Additional props passed to Downshift.
*
* **Use with caution:** anything you define here overrides the components'
* internal handling of that prop. Downshift APIs and internals are subject to
* change, and in some cases they can not be shimmed by Carbon to shield you
* from potentially breaking changes.
*/
// @ts-ignore
downshiftProps: PropTypes.shape(Downshift.propTypes),
Expand Down
Loading

0 comments on commit 6bd73bf

Please sign in to comment.