From d1bcc70ceda1ec7d274f88c91594a3864452ae47 Mon Sep 17 00:00:00 2001 From: Felix Habib Date: Mon, 15 Jul 2024 16:44:20 +1000 Subject: [PATCH] Add automaticHighlight prop --- packages/braid-design-system/package.json | 2 +- .../Autosuggest/Autosuggest.docs.tsx | 111 +++++++++++++----- .../components/Autosuggest/Autosuggest.tsx | 47 +++++++- pnpm-lock.yaml | 18 +-- 4 files changed, 137 insertions(+), 41 deletions(-) diff --git a/packages/braid-design-system/package.json b/packages/braid-design-system/package.json index 90075b65feb..3d7ca2d96e7 100644 --- a/packages/braid-design-system/package.json +++ b/packages/braid-design-system/package.json @@ -193,7 +193,7 @@ "@vanilla-extract/dynamic": "^2.0.3", "@vanilla-extract/sprinkles": "^1.5.1", "assert": "^2.0.0", - "autosuggest-highlight": "^3.1.1", + "autosuggest-highlight": "^3.3.4", "clsx": "^2.1.1", "csstype": "^3.0.6", "dedent": "^1.5.3", diff --git a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx index 07eebfb55b5..6a3229a4782 100644 --- a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx +++ b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.docs.tsx @@ -490,47 +490,100 @@ const docs: ComponentDocs = { label: 'Suggestion highlights', description: ( <> - {/* Todo - callout autosuggest-highlight? */} - Suggestion items can optionally contain a highlight range. In other - examples on this page, highlights are automatically handled by the{' '} - filterSuggestions function, highlighting the - portion of each suggestion that matches the search value. + While the filterSuggestions function will handle + highlights for you, you may need to separate the logic of + highlighting from filtering. - If you are not using filterSuggestions, you can - provide a highlight range to each suggestion, as shown in the - example below. + You can use the automaticHighlights prop to + automatically handle highlighting for you. By default, this will not + search within words. However, you can customise this and more + behaviour by providing it an options object. + + + If you require more custom behaviour, you can instead provide + explicit highlight ranges for each suggestion - The following example does not use accept input and does not use - the filterSuggestions function. + The following examples do not use the{' '} + filterSuggestions function, and the suggestions + will not be filtered. ), - Example: ({ id }) => + Example: ({ id, setDefaultState, setState, getState, resetState }) => source( <> - {}} - suggestions={[ - { - text: 'Apples', - value: 1, - highlights: [{ start: 0, end: 3 }], - }, - { - text: 'Apples and bananas', - value: 2, - highlights: [{ start: 0, end: 3 }], - }, - ]} - /> + {setDefaultState('field1', { text: '' })} + {setDefaultState('field2', { text: '' })} + {setDefaultState('field3', { text: '' })} + + + resetState('field1')} + automaticHighlights + suggestions={[ + { text: 'Apples' }, + { text: 'Bananas' }, + { text: 'Carrots' }, + ]} + /> + resetState('field2')} + automaticHighlights={{ + insideWords: true, + findAllOccurrences: true, + }} + suggestions={[ + { text: 'Apples' }, + { text: 'Bananas' }, + { text: 'Carrots' }, + ]} + /> + resetState('field3')} + suggestions={[ + { + text: 'Apples', + value: 1, + highlights: [{ start: 0, end: 2 }], + }, + { + text: 'Bananas', + value: 2, + highlights: [{ start: 0, end: 2 }], + }, + { + text: 'Broccoli', + value: 3, + highlights: [{ start: 0, end: 2 }], + }, + { + text: 'Carrots', + value: 4, + highlights: [{ start: 0, end: 2 }], + }, + ]} + /> + , ), }, diff --git a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx index 1cf891af4c1..37a902d685e 100644 --- a/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx +++ b/packages/braid-design-system/src/lib/components/Autosuggest/Autosuggest.tsx @@ -14,6 +14,7 @@ import React, { } from 'react'; import dedent from 'dedent'; import parseHighlights from 'autosuggest-highlight/parse'; +import matchHighlights from 'autosuggest-highlight/match'; import { Box } from '../Box/Box'; import { Text } from '../Text/Text'; import { Strong } from '../Strong/Strong'; @@ -277,6 +278,8 @@ interface LegacyMessageSuggestion { message: string; } +type MatchHighlightsOptions = Parameters[2]; + export type AutosuggestBaseProps = Omit< FieldBaseProps, 'value' | 'autoComplete' | 'prefix' @@ -292,6 +295,7 @@ export type AutosuggestBaseProps = Omit< onChange: (value: AutosuggestValue) => void; clearLabel?: string; automaticSelection?: boolean; + automaticHighlights?: boolean | MatchHighlightsOptions; hideSuggestionsOnSelection?: boolean; showMobileBackdrop?: boolean; scrollToTopOnMobile?: boolean; @@ -337,6 +341,15 @@ function normaliseNoSuggestionMessage( } } +function getAutomaticHighlights( + suggestion: string, + value: string, + options?: MatchHighlightsOptions, +): SuggestionMatch { + const matches = matchHighlights(suggestion, value, options); + return matches.map(([start, end]) => ({ start, end })); +} + export const Autosuggest = forwardRef(function ( { id, @@ -345,6 +358,7 @@ export const Autosuggest = forwardRef(function ( noSuggestionsMessage: noSuggestionsMessageProp, onChange = noop, automaticSelection = false, + automaticHighlights = false, showMobileBackdrop = false, scrollToTopOnMobile = true, hideSuggestionsOnSelection = true, @@ -373,6 +387,23 @@ export const Autosuggest = forwardRef(function ( ); const hasItems = suggestions.length > 0 || Boolean(noSuggestionsMessage); + const hasExplicitHighlights = suggestions.some( + (suggestion) => 'highlights' in suggestion, + ); + + if (automaticHighlights && hasExplicitHighlights) { + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn( + dedent` + In Autosuggest, you are using the "automaticHighlight" prop with suggestions that already have highlights. + The provided highlights will be overridden. + If you want to use your own highlights, set "automaticHighlight" to false. + `, + ); + } + } + // We need a ref regardless so we can imperatively // focus the field when clicking the clear button const defaultRef = useRef(null); @@ -568,7 +599,7 @@ export const Autosuggest = forwardRef(function ( } } // re-running this effect if the suggestionCount changes - // to ensure asychronous updates aren't left out of view. + // to ensure asynchronous updates aren't left out of view. }, [isOpen, isMobile, suggestionCount]); const inputProps = { @@ -804,6 +835,15 @@ export const Autosuggest = forwardRef(function ( ? normalisedSuggestions.map((suggestion, index) => { const { text } = suggestion; const groupHeading = groupHeadingIndexes.get(index); + const highlights = automaticHighlights + ? getAutomaticHighlights( + suggestion.text, + value.text, + typeof automaticHighlights === 'boolean' + ? {} + : automaticHighlights, + ) + : undefined; return ( @@ -811,7 +851,10 @@ export const Autosuggest = forwardRef(function ( {groupHeading} ) : null} { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a039926c121..456868a9e28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,8 +201,8 @@ importers: specifier: ^2.0.0 version: 2.1.0 autosuggest-highlight: - specifier: ^3.1.1 - version: 3.2.1 + specifier: ^3.3.4 + version: 3.3.4 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -6887,10 +6887,10 @@ packages: postcss: 8.4.38 postcss-value-parser: 4.2.0 - /autosuggest-highlight@3.2.1: - resolution: {integrity: sha512-PZk89g6W6cyE+fbnWF2CCIUAmP55o5wceKVqqL6oosgvbWHKa2Mes6nRC/PGj7OwqBgIy65Nd21Dw3h6qodOmg==} + /autosuggest-highlight@3.3.4: + resolution: {integrity: sha512-j6RETBD2xYnrVcoV1S5R4t3WxOlWZKyDQjkwnggDPSjF5L4jV98ZltBpvPvbkM1HtoSe5o+bNrTHyjPbieGeYA==} dependencies: - diacritic: 0.0.2 + remove-accents: 0.4.4 dev: false /available-typed-arrays@1.0.7: @@ -8644,10 +8644,6 @@ packages: transitivePeerDependencies: - supports-color - /diacritic@0.0.2: - resolution: {integrity: sha512-iQCeDkSPwkfwWPr+HZZ49WRrM2FSI9097Q9w7agyRCdLcF9Eh2Ek0sHKcmMWx2oZVBjRBE/sziGFjZu0uf1Jbg==} - dev: false - /didyoumean2@6.0.1: resolution: {integrity: sha512-PSy0zQwMg5O+LjT5Mz7vnKC8I7DfWLPF6M7oepqW7WP5mn2CY3hz46xZOa1GJY+KVfyXhdmz6+tdgXwrHlZc5g==} engines: {node: ^16.14.0 || >=18.12.0} @@ -15430,6 +15426,10 @@ packages: - supports-color dev: false + /remove-accents@0.4.4: + resolution: {integrity: sha512-EpFcOa/ISetVHEXqu+VwI96KZBmq+a8LJnGkaeFw45epGlxIZz5dhEEnNZMsQXgORu3qaMoLX4qJCzOik6ytAg==} + dev: false + /renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} dependencies: