Skip to content

Commit

Permalink
Add openOnFocus prop to AutocompleteInput (#4555)
Browse files Browse the repository at this point in the history
* Add `openOnFocus` prop to `AutocompleteInput`
Towards github/accessibility-audits#7437

* Add changeset

* Fix JSON formatting

* Allow up and down to open menu if not already open

* update hook

* Update packages/react/src/Autocomplete/AutocompleteInput.tsx

Co-authored-by: Kendall Gassner <kendallgassner@github.com>

---------

Co-authored-by: Kendall Gassner <kendallgassner@github.com>
  • Loading branch information
JoyceZhu and kendallgassner authored May 21, 2024
1 parent 68e6326 commit 9b63299
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-weeks-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': patch
---

Add `openInFocus` prop (default: true) to `AutocompleteInput`
6 changes: 6 additions & 0 deletions packages/react/src/Autocomplete/Autocomplete.docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
"name": "as",
"type": "React.ElementType",
"defaultValue": "TextInput"
},
{
"name": "openOnFocus",
"type": "boolean",
"defaultValue": "true",
"description": "Whether the associated autocomplete menu should open on an input focus event"
}
],
"passthrough": {
Expand Down
28 changes: 25 additions & 3 deletions packages/react/src/Autocomplete/Autocomplete.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const getArgsByChildComponent = ({
menuLoading,
selectionVariant,

// Autocomplete.Input
openOnFocus,

// Autocomplete.Overlay
anchorSide,
height,
Expand Down Expand Up @@ -65,6 +68,7 @@ any) => {
}
return {
menuArgs: {emptyStateText, loading: menuLoading, selectionVariant},
inputArgs: {openOnFocus},
overlayArgs: {anchorSide, height, maxHeight: overlayMaxHeight, width},
textInputArgs,
textInputWithTokensArgs: {
Expand Down Expand Up @@ -131,6 +135,7 @@ const autocompleteStoryMeta: Meta = {
emptyStateText: 'No selectable options',
menuLoading: false,
selectionVariant: 'single',
openOnFocus: true,
anchorSide: undefined,
height: 'auto',
overlayMaxHeight: undefined,
Expand Down Expand Up @@ -161,6 +166,16 @@ const autocompleteStoryMeta: Meta = {
},
},

// Autocomplete.Input
openOnFocus: {
control: {
type: 'boolean',
},
table: {
category: 'Autocomplete.Input',
},
},

// Autocomplete.Overlay
anchorSide: {
control: {
Expand Down Expand Up @@ -217,7 +232,7 @@ const autocompleteStoryMeta: Meta = {

export const Default = (args: FormControlArgs<AutocompleteArgs>) => {
const {parentArgs, labelArgs, captionArgs, validationArgs} = getFormControlArgsByChildComponent(args)
const {menuArgs, overlayArgs, textInputArgs} = getArgsByChildComponent(args)
const {menuArgs, inputArgs, overlayArgs, textInputArgs} = getArgsByChildComponent(args)
const isMultiselect = menuArgs.selectionVariant === 'multiple'
const [selectedItemIds, setSelectedItemIds] = useState<Array<string>>([])
const onSelectedChange = (newlySelectedItems: Datum | Datum[]) => {
Expand All @@ -228,12 +243,19 @@ export const Default = (args: FormControlArgs<AutocompleteArgs>) => {
setSelectedItemIds(newlySelectedItems.map(item => item.id))
}

const autocompleteInput = {...inputArgs, ...textInputArgs}
const formValidationId = 'validation-field'
return (
<Box as="form" sx={{p: 3}} onSubmit={event => event.preventDefault()}>
<FormControl {...parentArgs}>
<FormControl.Label id="autocompleteLabel" {...labelArgs} />
<Autocomplete>
<Autocomplete.Input {...textInputArgs} size={textInputArgs.inputSize} data-testid="autocompleteInput" />
<Autocomplete.Input
aria-describedby={formValidationId}
{...autocompleteInput}
size={textInputArgs.inputSize}
data-testid="autocompleteInput"
/>
<Autocomplete.Overlay {...overlayArgs}>
<Autocomplete.Menu
items={items}
Expand All @@ -246,7 +268,7 @@ export const Default = (args: FormControlArgs<AutocompleteArgs>) => {
</Autocomplete>
{captionArgs.children && <FormControl.Caption {...captionArgs} />}
{validationArgs.children && validationArgs.variant && (
<FormControl.Validation {...validationArgs} variant={validationArgs.variant} />
<FormControl.Validation id={formValidationId} {...validationArgs} variant={validationArgs.variant} />
)}
</FormControl>
</Box>
Expand Down
31 changes: 26 additions & 5 deletions packages/react/src/Autocomplete/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,27 @@ import useSafeTimeout from '../hooks/useSafeTimeout'
type InternalAutocompleteInputProps = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
as?: React.ComponentType<React.PropsWithChildren<any>>
// When false, the autocomplete menu will not render either on mouse click or
// keyboard focus.
openOnFocus?: boolean
}

const ARROW_KEYS_NAV = new Set(['ArrowUp', 'ArrowDown'])

const AutocompleteInput = React.forwardRef(
(
{as: Component = TextInput, onFocus, onBlur, onChange, onKeyDown, onKeyUp, onKeyPress, value, ...props},
{
as: Component = TextInput,
onFocus,
onBlur,
onChange,
onKeyDown,
onKeyUp,
onKeyPress,
value,
openOnFocus = true,
...props
},
forwardedRef,
) => {
const autocompleteContext = useContext(AutocompleteContext)
Expand All @@ -38,10 +54,12 @@ const AutocompleteInput = React.forwardRef(

const handleInputFocus: FocusEventHandler<HTMLInputElement> = useCallback(
event => {
onFocus && onFocus(event)
setShowMenu(true)
if (openOnFocus) {
onFocus?.(event)
setShowMenu(true)
}
},
[onFocus, setShowMenu],
[onFocus, setShowMenu, openOnFocus],
)

const handleInputBlur: FocusEventHandler<HTMLInputElement> = useCallback(
Expand Down Expand Up @@ -83,8 +101,11 @@ const AutocompleteInput = React.forwardRef(
setInputValue('')
inputRef.current.value = ''
}
if (!showMenu && ARROW_KEYS_NAV.has(event.key) && !event.altKey) {
setShowMenu(true)
}
},
[inputRef, setInputValue, setHighlightRemainingText, onKeyDown],
[inputRef, setInputValue, setHighlightRemainingText, onKeyDown, showMenu, setShowMenu],
)

const handleInputKeyUp: KeyboardEventHandler<HTMLInputElement> = useCallback(
Expand Down

0 comments on commit 9b63299

Please sign in to comment.