Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
felixhabib committed Jul 16, 2024
1 parent 3c23cff commit d5beb35
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
TextLink,
Text,
Strong,
Alert,
List,
Stack,
Heading,
Expand Down Expand Up @@ -460,53 +459,16 @@ const docs: ComponentDocs = {
),
},
{
label: 'Client-side filtering',
label: 'Suggestion highlights',
description: (
<>
<Text>
The logic for filtering suggestions typically lives on the server
rather than the client because it’s impractical to send all possible
suggestions over the network. However, when prototyping in Playroom
or working with smaller datasets, you may want to perform this
filtering on the client instead. For this case, we provide a{' '}
<Strong>filterSuggestions</Strong> function to make this as painless
as possible.
</Text>
<Text>
If filtering is being performed on the server, this can be safely
omitted.
A portion of each suggestion item can be highlighted to indicate
portions matching the input value. You can use the{' '}
<Strong>automaticHighlights</Strong> prop to highlight either the{' '}
<Strong>matching</Strong> portion of each suggestion or the{' '}
<Strong>remaining</Strong> portion.
</Text>
<Alert tone="info">
<Text>
All examples on this page, except where noted, use the{' '}
<Strong>filterSuggestions</Strong> function to demonstrate
real-world filtering behaviour.
</Text>
</Alert>
</>
),
},
{
label: 'Automatic suggestion highlights',
description: (
<>
<Text>
While the <Strong>filterSuggestions</Strong> function will handle
highlights for you, you may need to separate the logic of
highlighting from filtering.
</Text>
<Text>
You can use the <Strong>automaticHighlights</Strong> prop to
automatically handle highlighting for you. In the following example,
while <Strong>filterSuggestions</Strong> is not used, highlights
work as expected.
</Text>
<Notice tone="info">
<Text>
<Strong>automaticHighlights</Strong> can be configured further by
providing an <Strong>options</Strong> object.
</Text>
</Notice>
</>
),
Example: ({ id, setDefaultState, setState, getState, resetState }) =>
Expand All @@ -516,11 +478,11 @@ const docs: ComponentDocs = {

<Autosuggest
label="Label"
id={`${id}_highlights1`}
id={`${id}_automaticHighlights`}
value={getState('value')}
onChange={setState('value')}
onClear={() => resetState('value')}
automaticHighlights
automaticHighlights="matching"
suggestions={[
{ text: 'Apples' },
{ text: 'Bananas' },
Expand All @@ -531,48 +493,66 @@ const docs: ComponentDocs = {
),
},
{
label: 'Custom suggestion highlights',
description: (
<>
<Text>
If <Strong>automaticHighlights</Strong> is not suitable for your use
case, you can provide explicit highlight ranges for each suggestion.
</Text>
</>
),
Example: ({ id }) =>
source(
<Autosuggest
label="Label"
id={`${id}_highlights`}
value={{ text: 'App' }}
onChange={() => {}}
suggestions={[
{
text: 'Apples',
value: 1,
highlights: [{ start: 2, end: 6 }],
},
{
text: 'Bananas',
value: 2,
highlights: [{ start: 0, end: 3 }],
},
]}
/>,
),
},
{
label: 'Client-side filtering',
description: (
<>
<Text>
The logic for filtering suggestions typically lives on the server
rather than the client because it’s impractical to send all possible
suggestions over the network. However, when prototyping in Playroom
or working with smaller datasets, you may want to perform this
filtering on the client instead.
</Text>
<Text>
For this case, we provide a <Strong>filterSuggestions</Strong>{' '}
function to make this as painless as possible. This also handles
highlights for you, using <Strong>automaticHighlights</Strong> set
to <Strong>matching</Strong>.
</Text>
<Text>
If filtering is being performed on the server, this can be safely
omitted.
</Text>
<Notice tone="info">
<Text>
This is a simplified example that does not use{' '}
<Strong>filterSuggestions</Strong>. Suggestions will not change
from your input.
Most examples on this page use the{' '}
<Strong>filterSuggestions</Strong> function to demonstrate
real-world filtering behaviour.
</Text>
</Notice>
</>
),
Example: ({ id, setDefaultState, setState, getState, resetState }) =>
source(
<>
{setDefaultState('value', { text: '' })}

<Autosuggest
label="Label"
id={`${id}_highlights3`}
value={getState('value')}
onChange={setState('value')}
onClear={() => resetState('value')}
suggestions={[
{
text: 'Apples',
value: 1,
highlights: [{ start: 0, end: 2 }],
},
{
text: 'Bananas',
value: 2,
highlights: [{ start: 0, end: 2 }],
},
]}
/>
</>,
),
},
{
label: 'Clearable suggestions',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ interface LegacyMessageSuggestion {
message: string;
}

type MatchHighlightsOptions = Parameters<typeof matchHighlights>[2];
type HighlightOptions = 'matching' | 'remaining';

export type AutosuggestBaseProps<Value> = Omit<
FieldBaseProps,
Expand All @@ -295,7 +295,7 @@ export type AutosuggestBaseProps<Value> = Omit<
onChange: (value: AutosuggestValue<Value>) => void;
clearLabel?: string;
automaticSelection?: boolean;
automaticHighlights?: boolean | MatchHighlightsOptions;
automaticHighlights?: HighlightOptions;
hideSuggestionsOnSelection?: boolean;
showMobileBackdrop?: boolean;
scrollToTopOnMobile?: boolean;
Expand Down Expand Up @@ -341,13 +341,19 @@ function normaliseNoSuggestionMessage<Value>(
}
}

function getAutomaticHighlights(
export function highlightSuggestions(
suggestion: string,
value: string,
options?: MatchHighlightsOptions,
variant: HighlightOptions = 'matching',
): SuggestionMatch {
const matches = matchHighlights(suggestion, value, options);
return matches.map(([start, end]) => ({ start, end }));
const matches = matchHighlights(suggestion, value);

const formattedMatches =
variant === 'remaining'
? matches.map(([_, end]) => ({ start: end, end: suggestion.length }))
: matches.map(([start, end]) => ({ start, end }));

return formattedMatches;
}

export const Autosuggest = forwardRef(function <Value>(
Expand All @@ -358,7 +364,7 @@ export const Autosuggest = forwardRef(function <Value>(
noSuggestionsMessage: noSuggestionsMessageProp,
onChange = noop,
automaticSelection = false,
automaticHighlights = false,
automaticHighlights,
showMobileBackdrop = false,
scrollToTopOnMobile = true,
hideSuggestionsOnSelection = true,
Expand Down Expand Up @@ -836,14 +842,12 @@ export const Autosuggest = forwardRef(function <Value>(
const { text } = suggestion;
const groupHeading = groupHeadingIndexes.get(index);
const highlights = automaticHighlights
? getAutomaticHighlights(
? highlightSuggestions(
suggestion.text,
value.text,
typeof automaticHighlights === 'boolean'
? {}
: automaticHighlights,
automaticHighlights,
)
: undefined;
: suggestion.highlights;

return (
<Fragment key={index + text}>
Expand All @@ -853,7 +857,7 @@ export const Autosuggest = forwardRef(function <Value>(
<SuggestionItem
suggestion={{
...suggestion,
...(highlights && { highlights }),
highlights,
}}
highlighted={highlightedIndex === index}
selected={value === suggestion}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import assert from 'assert';
import matchHighlights from 'autosuggest-highlight/match';
import type {
AutosuggestValue,
Suggestion,
Suggestions,
GroupedSuggestions,
import {
type AutosuggestValue,
type Suggestion,
type Suggestions,
type GroupedSuggestions,
highlightSuggestions,
} from './Autosuggest';

type FilterableSuggestion<Value> = Omit<Suggestion<Value>, 'highlights'>;
Expand All @@ -14,17 +14,16 @@ type FilterableGroupedSuggestions<Value> = Omit<
> & { suggestions: Array<FilterableSuggestion<Value>> };

function matchSuggestion<Value>(suggestion: Suggestion<Value>, query: string) {
const groupMatches = matchHighlights(
const highlights = highlightSuggestions(
suggestion.label ?? suggestion.text,
query,
) as Array<[number, number]>;

return !groupMatches.length
? null
: {
);
return highlights.length
? {
...suggestion,
highlights: groupMatches.map(([start, end]) => ({ start, end })),
};
highlights,
}
: null;
}

type InputValue<Value> = string | AutosuggestValue<Value>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ exports[`Autosuggest 1`] = `
aria-labelledby: string
autoFocus?: boolean
automaticSelection?: boolean
automaticHighlights?:
| "matching"
| "remaining"
clearLabel?: string
data?: DataAttributeMap
description?: ReactNode
Expand Down

0 comments on commit d5beb35

Please sign in to comment.