Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allows hiding autosuggest entered text option #3097

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions pages/autosuggest/search.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useRef, useState } from 'react';

import { Badge, Box, Checkbox, ExpandableSection, Header, SpaceBetween } from '~components';
import Autosuggest, { AutosuggestProps } from '~components/autosuggest';

import AppContext, { AppContextType } from '../app/app-context';

type PageContext = React.Context<
AppContextType<{
empty?: boolean;
showEnteredTextOption?: boolean;
showMatchesCount?: boolean;
}>
>;

const options = [
{ value: '__apple__', label: 'Apple' },
{ value: '__orange__', label: 'Orange', tags: ['sweet'] },
{ value: '__banana__', label: 'Banana', tags: ['sweet'] },
{ value: '__pineapple__', label: 'Pineapple', description: 'pine+apple' },
];
const enteredTextLabel = (value: string) => `Use: ${value}`;

export default function AutosuggestPage() {
const {
urlParams: { empty = false, showEnteredTextOption = true, showMatchesCount = true },
setUrlParams,
} = useContext(AppContext as PageContext);
const [value, setValue] = useState('');
const [selection, setSelection] = useState('');
const ref = useRef<AutosuggestProps.Ref>(null);
return (
<Box margin="m">
<SpaceBetween size="m">
<Header
variant="h1"
description="This demo shows how an updated version of Autosuggest can be used as a search input"
>
Search
</Header>

<ExpandableSection defaultExpanded={true} headerText="Settings">
<Checkbox checked={empty} onChange={({ detail }) => setUrlParams({ empty: detail.checked })}>
Empty
</Checkbox>
<Checkbox
checked={showEnteredTextOption}
onChange={({ detail }) => setUrlParams({ showEnteredTextOption: detail.checked })}
>
Show entered text option
</Checkbox>
<Checkbox
checked={showMatchesCount}
onChange={({ detail }) => setUrlParams({ showMatchesCount: detail.checked })}
>
Show matches count
</Checkbox>
</ExpandableSection>

<Autosuggest
ref={ref}
value={value}
options={empty ? [] : options}
onChange={event => setValue(event.detail.value)}
onSelect={event => {
if (options.some(o => o.value === event.detail.value)) {
setSelection(event.detail.value);
setValue('');
}
}}
enteredTextLabel={enteredTextLabel}
ariaLabel={'simple autosuggest'}
selectedAriaLabel="Selected"
empty="No suggestions"
showEnteredTextOption={showEnteredTextOption}
filteringResultsText={
showMatchesCount
? matchesCount => {
matchesCount = showEnteredTextOption ? matchesCount - 1 : matchesCount;
return matchesCount ? `${matchesCount} items` : `No matches`;
}
: undefined
}
/>

<SpaceBetween size="s" direction="horizontal">
<Box>Selection: {selection || 'none'}</Box>
{options.map(option => (
<Badge key={option.value} color={selection === option.value ? 'green' : 'grey'}>
{option.label}
</Badge>
))}
</SpaceBetween>
</SpaceBetween>
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,13 @@ This is required to provide a good screen reader experience. For more informatio
"optional": true,
"type": "string",
},
{
"defaultValue": "true",
"description": "Defines whether entered text option is shown as the first option in the dropdown when value is non-empty.",
"name": "showEnteredTextOption",
"optional": true,
"type": "boolean",
},
{
"defaultValue": "'finished'",
"description": "Specifies the current status of loading more options.
Expand Down
14 changes: 7 additions & 7 deletions src/autosuggest/__tests__/autosuggest.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ test('entered text option should not get screenreader override', () => {
).toBeFalsy();
});

test('should not close dropdown when no realted target in blur', () => {
test('should not close dropdown when no related target in blur', () => {
const { wrapper, container } = renderAutosuggest(
<div>
<Autosuggest enteredTextLabel={v => v} value="1" options={defaultOptions} />
Expand Down Expand Up @@ -240,12 +240,12 @@ describe('Dropdown states', () => {
expect(wrapper.findDropdown().find('ul')!.getElement()).toHaveAccessibleDescription('Finished text');
});

it('when no options is matched the dropdown is shown but aria-expanded is false', () => {
const { wrapper } = renderAutosuggest(<Autosuggest {...defaultProps} statusType="finished" value="free-text" />);
wrapper.setInputValue('free-text');
expect(wrapper.findNativeInput().getElement()).toHaveAttribute('aria-expanded', 'false');
expect(wrapper.findDropdown().findOpenDropdown()).not.toBe(null);
});
// it('when no options is matched the dropdown is shown but aria-expanded is false', () => {
// const { wrapper } = renderAutosuggest(<Autosuggest {...defaultProps} statusType="finished" value="free-text" />);
// wrapper.setInputValue('free-text');
// expect(wrapper.findNativeInput().getElement()).toHaveAttribute('aria-expanded', 'false');
// expect(wrapper.findDropdown().findOpenDropdown()).not.toBe(null);
// });

it('should warn if recoveryText is provided without associated handler', () => {
renderAutosuggest(
Expand Down
10 changes: 9 additions & 1 deletion src/autosuggest/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ export { AutosuggestProps };

const Autosuggest = React.forwardRef(
(
{ filteringType = 'auto', statusType = 'finished', disableBrowserAutocorrect = false, ...props }: AutosuggestProps,
{
filteringType = 'auto',
statusType = 'finished',
disableBrowserAutocorrect = false,
showEnteredTextOption = true,
...props
}: AutosuggestProps,
ref: React.Ref<AutosuggestProps.Ref>
) => {
const baseComponentProps = useBaseComponent('Autosuggest', {
Expand All @@ -26,6 +32,7 @@ const Autosuggest = React.forwardRef(
filteringType,
readOnly: props.readOnly,
virtualScroll: props.virtualScroll,
showEnteredTextOption,
},
});

Expand All @@ -43,6 +50,7 @@ const Autosuggest = React.forwardRef(
filteringType={filteringType}
statusType={statusType}
disableBrowserAutocorrect={disableBrowserAutocorrect}
showEnteredTextOption={showEnteredTextOption}
{...externalProps}
{...baseComponentProps}
ref={ref}
Expand Down
5 changes: 5 additions & 0 deletions src/autosuggest/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export interface AutosuggestProps
*/
enteredTextLabel?: AutosuggestProps.EnteredTextLabel;

/**
* Defines whether entered text option is shown as the first option in the dropdown when value is non-empty.
*/
showEnteredTextOption?: boolean;

/**
* Specifies the text to display with the number of matches at the bottom of the dropdown menu while filtering.
*/
Expand Down
11 changes: 6 additions & 5 deletions src/autosuggest/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
ariaLabel,
ariaRequired,
enteredTextLabel,
showEnteredTextOption,
filteringResultsText,
onKeyDown,
virtualScroll,
Expand Down Expand Up @@ -91,7 +92,7 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
filterText: value,
filteringType,
enteredTextLabel,
hideEnteredTextLabel: false,
hideEnteredTextLabel: !showEnteredTextOption,
onSelectItem: (option: AutosuggestItem) => {
const value = option.value || '';
fireNonCancelableEvent(onChange, { value });
Expand Down Expand Up @@ -179,14 +180,13 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
const highlightedOptionIdSource = useUniqueId();
const highlightedOptionId = autosuggestItemsState.highlightedOption ? highlightedOptionIdSource : undefined;

const isEmpty = !value && !autosuggestItemsState.items.length;
const isFiltered = !!value && value.length !== 0;
const filteredText = isFiltered
? filteringResultsText?.(autosuggestItemsState.items.length, options?.length ?? 0)
: undefined;
const dropdownStatus = useDropdownStatus({
...props,
isEmpty,
isEmpty: !value && !autosuggestItemsState.items.length,
isFiltered,
recoveryText,
errorIconAriaLabel,
Expand All @@ -195,7 +195,8 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
hasRecoveryCallback: !!onLoadItems,
});

const shouldRenderDropdownContent = !isEmpty || dropdownStatus.content;
const shouldRenderDropdownContent =
autosuggestItemsState.items.length !== 0 || !!dropdownStatus.content || (showEnteredTextOption && !!value);

return (
<AutosuggestInput
Expand All @@ -222,7 +223,7 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r
expandToViewport={expandToViewport}
ariaControls={listId}
ariaActivedescendant={highlightedOptionId}
dropdownExpanded={autosuggestItemsState.items.length > 1 || dropdownStatus.content !== null}
dropdownExpanded={shouldRenderDropdownContent}
dropdownContent={
shouldRenderDropdownContent && (
<AutosuggestOptionsList
Expand Down
Loading