@mrshmllw/smores-react 16.10.0 → 16.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SearchInput/SearchInput.js +52 -25
- package/dist/SearchInput/SearchInput.js.map +1 -1
- package/dist/SearchInput/components/EmptyResults.d.ts +1 -0
- package/dist/SearchInput/components/EmptyResults.js +39 -6
- package/dist/SearchInput/components/EmptyResults.js.map +1 -1
- package/dist/SearchInput/components/SearchOptions.d.ts +10 -4
- package/dist/SearchInput/components/SearchOptions.js +4 -4
- package/dist/SearchInput/components/SearchOptions.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,6 +9,7 @@ import { useOnClickOutside } from '../hooks';
|
|
|
9
9
|
import { useUniqueId } from '../utils/id';
|
|
10
10
|
import { useControllableState } from '../utils/useControlledState';
|
|
11
11
|
import { SearchOptions } from './components/SearchOptions';
|
|
12
|
+
import { visuallyHidden } from '../utils/visuallyHidden';
|
|
12
13
|
import { IconContainer } from '../sharedStyles/shared.styles';
|
|
13
14
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
14
15
|
import { faXmark, faChevronDown, faSearch, } from '@awesome.me/kit-46ca99185c/icons/classic/regular';
|
|
@@ -40,20 +41,22 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
|
|
|
40
41
|
});
|
|
41
42
|
const [searchQuery, setSearchQuery] = useState(null);
|
|
42
43
|
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
44
|
+
const [emptyMessage, setEmptyMessage] = useState('');
|
|
43
45
|
const selectedValueLabel = searchList.find((option) => option.label === selectedValue || option.value === selectedValue)?.label;
|
|
44
46
|
const handleBlur = () => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
setSearchQuery(null);
|
|
48
|
+
};
|
|
49
|
+
// Every way of closing the list goes through here, so a reopened list
|
|
50
|
+
// never starts with a stale highlighted option.
|
|
51
|
+
const closeOptions = () => {
|
|
52
|
+
setShowOptions(false);
|
|
53
|
+
setHighlightedIndex(-1);
|
|
51
54
|
};
|
|
52
55
|
useOnClickOutside({
|
|
53
56
|
ref: wrapperRef,
|
|
54
57
|
callback: () => {
|
|
55
58
|
handleBlur();
|
|
56
|
-
|
|
59
|
+
closeOptions();
|
|
57
60
|
},
|
|
58
61
|
});
|
|
59
62
|
// oxlint-disable-next-line react/react-compiler
|
|
@@ -90,7 +93,7 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
|
|
|
90
93
|
setHighlightedIndex(-1);
|
|
91
94
|
if (query === null) {
|
|
92
95
|
setSelectedValue(null);
|
|
93
|
-
|
|
96
|
+
closeOptions();
|
|
94
97
|
}
|
|
95
98
|
else {
|
|
96
99
|
setShowOptions(true);
|
|
@@ -102,10 +105,6 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
|
|
|
102
105
|
updateSearchQuery(searchQuery);
|
|
103
106
|
setShowOptions(true);
|
|
104
107
|
}
|
|
105
|
-
else if (selectedValue !== null && searchQuery === null) {
|
|
106
|
-
setSearchQuery(selectedValueLabel || null);
|
|
107
|
-
setShowOptions(true);
|
|
108
|
-
}
|
|
109
108
|
};
|
|
110
109
|
const handleInputChange = (event) => {
|
|
111
110
|
const nextValue = event.currentTarget.value;
|
|
@@ -122,35 +121,63 @@ export const SearchInput = forwardRef(function SearchInput({ id: idProp, name =
|
|
|
122
121
|
onFound('');
|
|
123
122
|
};
|
|
124
123
|
const handleCaretClick = () => {
|
|
125
|
-
|
|
124
|
+
if (showOptions) {
|
|
125
|
+
closeOptions();
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
setShowOptions(true);
|
|
129
|
+
}
|
|
126
130
|
};
|
|
131
|
+
const listboxId = `${id}-listbox`;
|
|
132
|
+
const getOptionId = (index) => `${id}-option-${index}`;
|
|
133
|
+
const hasResults = showOptions && filteredList.length > 0;
|
|
134
|
+
const hasHighlight = hasResults &&
|
|
135
|
+
highlightedIndex >= 0 &&
|
|
136
|
+
highlightedIndex < filteredList.length;
|
|
137
|
+
const noResults = showOptions && filteredList.length === 0;
|
|
127
138
|
const handleKeyDown = (event) => {
|
|
128
|
-
if (event.key === '
|
|
139
|
+
if (event.key === 'Escape') {
|
|
140
|
+
if (!showOptions)
|
|
141
|
+
return;
|
|
129
142
|
event.preventDefault();
|
|
130
|
-
|
|
131
|
-
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
closeOptions();
|
|
145
|
+
}
|
|
146
|
+
else if (event.key === 'Enter') {
|
|
147
|
+
if (hasHighlight) {
|
|
148
|
+
event.preventDefault();
|
|
149
|
+
handleSelect(filteredList[highlightedIndex]);
|
|
150
|
+
}
|
|
132
151
|
}
|
|
133
152
|
else if (event.key === 'ArrowDown') {
|
|
134
153
|
event.preventDefault();
|
|
135
|
-
|
|
136
|
-
|
|
154
|
+
if (!showOptions) {
|
|
155
|
+
setShowOptions(true);
|
|
156
|
+
}
|
|
157
|
+
else if (filteredList.length > 0) {
|
|
158
|
+
setHighlightedIndex((highlightedIndex + 1) % filteredList.length);
|
|
159
|
+
}
|
|
137
160
|
}
|
|
138
161
|
else if (event.key === 'ArrowUp') {
|
|
139
162
|
event.preventDefault();
|
|
140
|
-
|
|
141
|
-
|
|
163
|
+
if (showOptions && filteredList.length > 0) {
|
|
164
|
+
setHighlightedIndex((highlightedIndex - 1 + filteredList.length) % filteredList.length);
|
|
165
|
+
}
|
|
142
166
|
}
|
|
143
167
|
};
|
|
144
168
|
const showClearSearchButton = !!clearSearch && (!!value || !!selectedValue || !!searchQuery);
|
|
145
|
-
return (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
169
|
+
return (_jsxs(Wrapper, { ref: wrapperRef, children: [_jsxs(Field, { className: className, renderAsTitle: renderAsTitle, htmlFor: id, ...otherProps, children: [_jsxs(Box, { flex: true, alignItems: "center", justifyContent: "flex-start", children: [showIcon && (_jsx(InputLeadingIconContainer, { $size: 20, children: _jsx(FontAwesomeIcon, { icon: faSearch, color: theme.color.text.subtle }) })), _jsx(Input, { id: id, name: name, ref: ref, placeholder: placeholder, $error: otherProps.error, $frontIcon: showIcon, $fallbackStyle: fallbackStyle, autoComplete: "off", role: "combobox", "aria-autocomplete": "list", "aria-expanded": hasResults, "aria-controls": hasResults ? listboxId : undefined, "aria-activedescendant": hasHighlight ? getOptionId(highlightedIndex) : undefined, value: displayedInputText, onFocus: handleClick, onChange: handleInputChange, selected: isSelected, onClick: handleClick, onKeyDown: handleKeyDown, onBlur: (e) => {
|
|
170
|
+
onBlur?.(e);
|
|
171
|
+
} }), _jsxs(Icons, { flex: true, alignItems: "center", gap: "space.100", $clearSearch: showClearSearchButton, children: [showClearSearchButton && (_jsx(IconContainer, { title: "Clear search", onClick: handleClearSearch, type: "button", as: "button", $size: 20, style: { cursor: 'pointer' }, children: _jsx(FontAwesomeIcon, { icon: faXmark, color: theme.color.illustration.neutral[400] }) })), _jsx(Line, {}), _jsx(IconContainer, { type: "button", title: "icon-button", as: "button", onClick: handleCaretClick, $size: 20, children: _jsx(FontAwesomeIcon, { style: {
|
|
172
|
+
rotate: showOptions ? '180deg' : '0deg',
|
|
173
|
+
}, icon: faChevronDown, color: theme.color.illustration.neutral[400] }) })] })] }), showOptions && (_jsx(SearchOptions, { displayedList: filteredList, selectedValue: selectedValue, highlightedIndex: highlightedIndex, setHighlightedIndex: setHighlightedIndex, onKeyDown: handleKeyDown, listboxId: listboxId, listboxLabel: otherProps.label ?? placeholder ?? 'Search results', getOptionId: getOptionId, searchTerm: searchQuery || '', onSelect: handleSelect, positionRelative: resultsRelativePosition, resultsBorder: resultsBorder, onNotFound: onNotFound, onEmptyMessageChange: setEmptyMessage, notFoundComponent: notFoundComponent?.(searchQuery ?? '') }))] }), _jsx(LiveRegion, { role: "status", children: noResults ? emptyMessage : '' })] }));
|
|
150
174
|
});
|
|
151
175
|
const Wrapper = styled(Box) `
|
|
152
176
|
position: relative;
|
|
153
177
|
`;
|
|
178
|
+
const LiveRegion = styled.div `
|
|
179
|
+
${visuallyHidden}
|
|
180
|
+
`;
|
|
154
181
|
const Line = styled(Box) `
|
|
155
182
|
background: ${({ theme }) => theme.color.border.subtle};
|
|
156
183
|
height: 24px;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchInput.js","sourceRoot":"","sources":["../../src/SearchInput/SearchInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import Fuse, { IFuseOptions } from 'fuse.js'\nimport {\n ChangeEvent,\n FocusEvent,\n ReactNode,\n forwardRef,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport styled, { useTheme } from 'styled-components'\nimport { Box } from '../Box'\nimport { Field } from '../fields/Field'\nimport { CommonFieldProps } from '../fields/commonFieldTypes'\nimport {\n Input,\n InputLeadingIconContainer,\n} from '../fields/components/CommonInput'\nimport { useOnClickOutside } from '../hooks'\nimport { useUniqueId } from '../utils/id'\nimport { useControllableState } from '../utils/useControlledState'\nimport { SearchOptions } from './components/SearchOptions'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faXmark,\n faChevronDown,\n faSearch,\n} from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\nexport type SearchInputItem = {\n label: string\n value: string\n tags?: any[]\n}\n\nexport interface SearchInputProps extends CommonFieldProps {\n /** Optional input className */\n name?: string\n /** Optional placeholder text */\n placeholder?: string\n /** List of input items to search on*/\n searchList: SearchInputItem[]\n /** callback to handle found item click */\n onFound: (element: string) => void\n /** optional callback to run when no results found */\n onNotFound?: (searchTerm: string) => void\n /** optional Component to render when no results found */\n notFoundComponent?: (searchTerm: string) => ReactNode\n /** optional boolean to show search icon */\n showIcon?: boolean\n /** optional boolean to show a clear search button */\n clearSearch?: boolean\n /** Optional callback to run on blur */\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n /** Optional default value for input */\n value?: string\n /** Optional boolean to move results to a realtive position */\n resultsRelativePosition?: boolean\n /** optional boolean to add border to results */\n resultsBorder?: boolean\n /** optional boolean to enable fuzzy search via fuse.js */\n enableFuzzySearch?: boolean\n /** optional config of fuzzy search\n * passing a value to this prop, automatically enables fuzzy search\n */\n fuzzySearchOptions?: IFuseOptions<SearchInputItem>\n}\n\nconst defaultFuzzySearchOptions = {\n keys: [\n {\n name: 'label',\n weight: 0.6,\n },\n {\n name: 'tags',\n weight: 0.4,\n },\n ],\n findAllMatches: true,\n minMatchCharLength: 1,\n location: 0,\n threshold: 0.45,\n distance: 55,\n}\n\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(\n function SearchInput(\n {\n id: idProp,\n name = 'search_input',\n className = '',\n placeholder,\n searchList,\n showIcon = false,\n renderAsTitle = false,\n value,\n onBlur,\n onFound,\n onNotFound,\n notFoundComponent,\n fallbackStyle,\n resultsRelativePosition = false,\n resultsBorder = true,\n enableFuzzySearch = false,\n fuzzySearchOptions,\n clearSearch,\n ...otherProps\n },\n ref,\n ) {\n const wrapperRef = useRef(null)\n const theme = useTheme()\n const id = useUniqueId(idProp)\n const [showOptions, setShowOptions] = useState(false)\n const [selectedValue, setSelectedValue] = useControllableState<\n string | null\n >({\n initialState: null,\n stateProp: value,\n })\n const [searchQuery, setSearchQuery] = useState<string | null>(null)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n\n const selectedValueLabel = searchList.find(\n (option) =>\n option.label === selectedValue || option.value === selectedValue,\n )?.label\n\n const handleBlur = () => {\n if (selectedValue) {\n setSearchQuery(selectedValueLabel ?? null)\n } else if (!selectedValue) {\n setSearchQuery(null)\n }\n }\n\n useOnClickOutside({\n ref: wrapperRef,\n callback: () => {\n handleBlur()\n setShowOptions(false)\n },\n })\n\n // oxlint-disable-next-line react/react-compiler\n const fuse = useMemo(() => {\n return new Fuse(searchList, {\n ...defaultFuzzySearchOptions,\n ...fuzzySearchOptions,\n })\n }, [searchList])\n\n // oxlint-disable-next-line react/react-compiler\n const filteredList = useMemo(() => {\n if (searchQuery === null || searchQuery === '') {\n return searchList\n }\n\n if (enableFuzzySearch || !!fuzzySearchOptions) {\n return fuse.search(searchQuery).map(({ item }) => item)\n }\n\n return searchList.filter(({ label }) =>\n label.toLowerCase().includes(searchQuery.toLocaleLowerCase()),\n )\n // oxlint-disable-next-line react/react-compiler\n }, [searchQuery, enableFuzzySearch, !!fuzzySearchOptions])\n\n const getDisplayedInputText = () => {\n if (searchQuery !== null) {\n return searchQuery\n }\n if (selectedValue !== null) {\n return selectedValueLabel || ''\n }\n return ''\n }\n\n const isSelected = selectedValue !== null\n const displayedInputText = getDisplayedInputText()\n\n const updateSearchQuery = (query: string | null) => {\n setSearchQuery(query)\n setHighlightedIndex(-1)\n\n if (query === null) {\n setSelectedValue(null)\n setShowOptions(false)\n } else {\n setShowOptions(true)\n }\n }\n\n const handleClick = () => {\n setShowOptions(true)\n if (searchQuery !== null) {\n updateSearchQuery(searchQuery)\n setShowOptions(true)\n } else if (selectedValue !== null && searchQuery === null) {\n setSearchQuery(selectedValueLabel || null)\n setShowOptions(true)\n }\n }\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {\n const nextValue = event.currentTarget.value\n updateSearchQuery(nextValue)\n }\n\n const handleSelect = (nextValue: SearchInputItem): void => {\n updateSearchQuery(null)\n setSelectedValue(nextValue.label)\n onFound(nextValue.value)\n }\n\n const handleClearSearch = () => {\n updateSearchQuery(null)\n setSelectedValue(null)\n onFound('')\n }\n\n const handleCaretClick = () => {\n setShowOptions(!showOptions)\n }\n\n const handleKeyDown = (event: {\n key: string\n preventDefault: () => void\n }) => {\n if (event.key === 'Enter' && highlightedIndex !== -1) {\n event.preventDefault()\n const focusedItem = filteredList[highlightedIndex]\n handleSelect(focusedItem)\n } else if (event.key === 'ArrowDown') {\n event.preventDefault()\n const nextIndex = (highlightedIndex + 1) % filteredList.length\n setHighlightedIndex(nextIndex)\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n const prevIndex =\n (highlightedIndex - 1 + filteredList.length) % filteredList.length\n setHighlightedIndex(prevIndex)\n }\n }\n\n const showClearSearchButton =\n !!clearSearch && (!!value || !!selectedValue || !!searchQuery)\n\n return (\n <Wrapper ref={wrapperRef}>\n <Field\n className={className}\n renderAsTitle={renderAsTitle}\n htmlFor={id}\n {...otherProps}\n >\n <Box flex alignItems=\"center\" justifyContent=\"flex-start\">\n {showIcon && (\n <InputLeadingIconContainer $size={20}>\n <FontAwesomeIcon\n icon={faSearch}\n color={theme.color.text.subtle}\n />\n </InputLeadingIconContainer>\n )}\n <Input\n id={id}\n name={name}\n ref={ref}\n placeholder={placeholder}\n $error={otherProps.error}\n $frontIcon={showIcon}\n $fallbackStyle={fallbackStyle}\n autoComplete=\"off\"\n value={displayedInputText}\n onFocus={handleClick}\n onChange={handleInputChange}\n selected={isSelected}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onBlur={(e) => {\n onBlur?.(e)\n }}\n />\n <Icons\n flex\n alignItems=\"center\"\n gap=\"space.100\"\n $clearSearch={showClearSearchButton}\n >\n {showClearSearchButton && (\n <IconContainer\n title=\"Clear search\"\n onClick={handleClearSearch}\n type=\"button\"\n as=\"button\"\n $size={20}\n style={{ cursor: 'pointer' }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n )}\n <Line />\n <IconContainer\n type=\"button\"\n title=\"icon-button\"\n as=\"button\"\n onClick={handleCaretClick}\n $size={20}\n >\n <FontAwesomeIcon\n style={{\n rotate: showOptions ? '180deg' : '0deg',\n }}\n icon={faChevronDown}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n </Icons>\n </Box>\n\n {showOptions && (\n <SearchOptions\n displayedList={filteredList}\n selectedValue={selectedValue}\n highlightedIndex={highlightedIndex}\n setHighlightedIndex={setHighlightedIndex}\n onKeyDown={handleKeyDown}\n searchTerm={searchQuery || ''}\n onSelect={handleSelect}\n positionRelative={resultsRelativePosition}\n resultsBorder={resultsBorder}\n onNotFound={onNotFound}\n notFoundComponent={notFoundComponent?.(searchQuery ?? '')}\n />\n )}\n </Field>\n </Wrapper>\n )\n },\n)\n\nconst Wrapper = styled(Box)`\n position: relative;\n`\n\nconst Line = styled(Box)`\n background: ${({ theme }) => theme.color.border.subtle};\n height: 24px;\n width: 1px;\n`\n\nconst Icons = styled(Box)<{ $clearSearch: boolean }>`\n position: relative;\n right: ${({ $clearSearch }) => ($clearSearch ? '80px' : '48px')};\n margin-right: ${({ $clearSearch }) => ($clearSearch ? '-80px' : '-48px')};\n`\n"]}
|
|
1
|
+
{"version":3,"file":"SearchInput.js","sourceRoot":"","sources":["../../src/SearchInput/SearchInput.tsx"],"names":[],"mappings":"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","sourcesContent":["import Fuse, { IFuseOptions } from 'fuse.js'\nimport {\n ChangeEvent,\n FocusEvent,\n ReactNode,\n forwardRef,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport styled, { useTheme } from 'styled-components'\nimport { Box } from '../Box'\nimport { Field } from '../fields/Field'\nimport { CommonFieldProps } from '../fields/commonFieldTypes'\nimport {\n Input,\n InputLeadingIconContainer,\n} from '../fields/components/CommonInput'\nimport { useOnClickOutside } from '../hooks'\nimport { useUniqueId } from '../utils/id'\nimport { useControllableState } from '../utils/useControlledState'\nimport { SearchKeyEvent, SearchOptions } from './components/SearchOptions'\nimport { visuallyHidden } from '../utils/visuallyHidden'\nimport { IconContainer } from '../sharedStyles/shared.styles'\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome'\nimport {\n faXmark,\n faChevronDown,\n faSearch,\n} from '@awesome.me/kit-46ca99185c/icons/classic/regular'\n\nexport type SearchInputItem = {\n label: string\n value: string\n tags?: any[]\n}\n\nexport interface SearchInputProps extends CommonFieldProps {\n /** Optional input className */\n name?: string\n /** Optional placeholder text */\n placeholder?: string\n /** List of input items to search on*/\n searchList: SearchInputItem[]\n /** callback to handle found item click */\n onFound: (element: string) => void\n /** optional callback to run when no results found */\n onNotFound?: (searchTerm: string) => void\n /** optional Component to render when no results found */\n notFoundComponent?: (searchTerm: string) => ReactNode\n /** optional boolean to show search icon */\n showIcon?: boolean\n /** optional boolean to show a clear search button */\n clearSearch?: boolean\n /** Optional callback to run on blur */\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n /** Optional default value for input */\n value?: string\n /** Optional boolean to move results to a realtive position */\n resultsRelativePosition?: boolean\n /** optional boolean to add border to results */\n resultsBorder?: boolean\n /** optional boolean to enable fuzzy search via fuse.js */\n enableFuzzySearch?: boolean\n /** optional config of fuzzy search\n * passing a value to this prop, automatically enables fuzzy search\n */\n fuzzySearchOptions?: IFuseOptions<SearchInputItem>\n}\n\nconst defaultFuzzySearchOptions = {\n keys: [\n {\n name: 'label',\n weight: 0.6,\n },\n {\n name: 'tags',\n weight: 0.4,\n },\n ],\n findAllMatches: true,\n minMatchCharLength: 1,\n location: 0,\n threshold: 0.45,\n distance: 55,\n}\n\nexport const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(\n function SearchInput(\n {\n id: idProp,\n name = 'search_input',\n className = '',\n placeholder,\n searchList,\n showIcon = false,\n renderAsTitle = false,\n value,\n onBlur,\n onFound,\n onNotFound,\n notFoundComponent,\n fallbackStyle,\n resultsRelativePosition = false,\n resultsBorder = true,\n enableFuzzySearch = false,\n fuzzySearchOptions,\n clearSearch,\n ...otherProps\n },\n ref,\n ) {\n const wrapperRef = useRef(null)\n const theme = useTheme()\n const id = useUniqueId(idProp)\n const [showOptions, setShowOptions] = useState(false)\n const [selectedValue, setSelectedValue] = useControllableState<\n string | null\n >({\n initialState: null,\n stateProp: value,\n })\n const [searchQuery, setSearchQuery] = useState<string | null>(null)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n const [emptyMessage, setEmptyMessage] = useState('')\n\n const selectedValueLabel = searchList.find(\n (option) =>\n option.label === selectedValue || option.value === selectedValue,\n )?.label\n\n const handleBlur = () => {\n setSearchQuery(null)\n }\n\n // Every way of closing the list goes through here, so a reopened list\n // never starts with a stale highlighted option.\n const closeOptions = () => {\n setShowOptions(false)\n setHighlightedIndex(-1)\n }\n\n useOnClickOutside({\n ref: wrapperRef,\n callback: () => {\n handleBlur()\n closeOptions()\n },\n })\n\n // oxlint-disable-next-line react/react-compiler\n const fuse = useMemo(() => {\n return new Fuse(searchList, {\n ...defaultFuzzySearchOptions,\n ...fuzzySearchOptions,\n })\n }, [searchList])\n\n // oxlint-disable-next-line react/react-compiler\n const filteredList = useMemo(() => {\n if (searchQuery === null || searchQuery === '') {\n return searchList\n }\n\n if (enableFuzzySearch || !!fuzzySearchOptions) {\n return fuse.search(searchQuery).map(({ item }) => item)\n }\n\n return searchList.filter(({ label }) =>\n label.toLowerCase().includes(searchQuery.toLocaleLowerCase()),\n )\n // oxlint-disable-next-line react/react-compiler\n }, [searchQuery, enableFuzzySearch, !!fuzzySearchOptions])\n\n const getDisplayedInputText = () => {\n if (searchQuery !== null) {\n return searchQuery\n }\n if (selectedValue !== null) {\n return selectedValueLabel || ''\n }\n return ''\n }\n\n const isSelected = selectedValue !== null\n const displayedInputText = getDisplayedInputText()\n\n const updateSearchQuery = (query: string | null) => {\n setSearchQuery(query)\n setHighlightedIndex(-1)\n\n if (query === null) {\n setSelectedValue(null)\n closeOptions()\n } else {\n setShowOptions(true)\n }\n }\n\n const handleClick = () => {\n setShowOptions(true)\n if (searchQuery !== null) {\n updateSearchQuery(searchQuery)\n setShowOptions(true)\n }\n }\n\n const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {\n const nextValue = event.currentTarget.value\n updateSearchQuery(nextValue)\n }\n\n const handleSelect = (nextValue: SearchInputItem): void => {\n updateSearchQuery(null)\n setSelectedValue(nextValue.label)\n onFound(nextValue.value)\n }\n\n const handleClearSearch = () => {\n updateSearchQuery(null)\n setSelectedValue(null)\n onFound('')\n }\n\n const handleCaretClick = () => {\n if (showOptions) {\n closeOptions()\n } else {\n setShowOptions(true)\n }\n }\n\n const listboxId = `${id}-listbox`\n const getOptionId = (index: number) => `${id}-option-${index}`\n const hasResults = showOptions && filteredList.length > 0\n const hasHighlight =\n hasResults &&\n highlightedIndex >= 0 &&\n highlightedIndex < filteredList.length\n const noResults = showOptions && filteredList.length === 0\n\n const handleKeyDown = (event: SearchKeyEvent) => {\n if (event.key === 'Escape') {\n if (!showOptions) return\n event.preventDefault()\n event.stopPropagation()\n closeOptions()\n } else if (event.key === 'Enter') {\n if (hasHighlight) {\n event.preventDefault()\n handleSelect(filteredList[highlightedIndex])\n }\n } else if (event.key === 'ArrowDown') {\n event.preventDefault()\n if (!showOptions) {\n setShowOptions(true)\n } else if (filteredList.length > 0) {\n setHighlightedIndex((highlightedIndex + 1) % filteredList.length)\n }\n } else if (event.key === 'ArrowUp') {\n event.preventDefault()\n if (showOptions && filteredList.length > 0) {\n setHighlightedIndex(\n (highlightedIndex - 1 + filteredList.length) % filteredList.length,\n )\n }\n }\n }\n\n const showClearSearchButton =\n !!clearSearch && (!!value || !!selectedValue || !!searchQuery)\n\n return (\n <Wrapper ref={wrapperRef}>\n <Field\n className={className}\n renderAsTitle={renderAsTitle}\n htmlFor={id}\n {...otherProps}\n >\n <Box flex alignItems=\"center\" justifyContent=\"flex-start\">\n {showIcon && (\n <InputLeadingIconContainer $size={20}>\n <FontAwesomeIcon\n icon={faSearch}\n color={theme.color.text.subtle}\n />\n </InputLeadingIconContainer>\n )}\n <Input\n id={id}\n name={name}\n ref={ref}\n placeholder={placeholder}\n $error={otherProps.error}\n $frontIcon={showIcon}\n $fallbackStyle={fallbackStyle}\n autoComplete=\"off\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={hasResults}\n aria-controls={hasResults ? listboxId : undefined}\n aria-activedescendant={\n hasHighlight ? getOptionId(highlightedIndex) : undefined\n }\n value={displayedInputText}\n onFocus={handleClick}\n onChange={handleInputChange}\n selected={isSelected}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onBlur={(e) => {\n onBlur?.(e)\n }}\n />\n <Icons\n flex\n alignItems=\"center\"\n gap=\"space.100\"\n $clearSearch={showClearSearchButton}\n >\n {showClearSearchButton && (\n <IconContainer\n title=\"Clear search\"\n onClick={handleClearSearch}\n type=\"button\"\n as=\"button\"\n $size={20}\n style={{ cursor: 'pointer' }}\n >\n <FontAwesomeIcon\n icon={faXmark}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n )}\n <Line />\n <IconContainer\n type=\"button\"\n title=\"icon-button\"\n as=\"button\"\n onClick={handleCaretClick}\n $size={20}\n >\n <FontAwesomeIcon\n style={{\n rotate: showOptions ? '180deg' : '0deg',\n }}\n icon={faChevronDown}\n color={theme.color.illustration.neutral[400]}\n />\n </IconContainer>\n </Icons>\n </Box>\n\n {showOptions && (\n <SearchOptions\n displayedList={filteredList}\n selectedValue={selectedValue}\n highlightedIndex={highlightedIndex}\n setHighlightedIndex={setHighlightedIndex}\n onKeyDown={handleKeyDown}\n listboxId={listboxId}\n listboxLabel={otherProps.label ?? placeholder ?? 'Search results'}\n getOptionId={getOptionId}\n searchTerm={searchQuery || ''}\n onSelect={handleSelect}\n positionRelative={resultsRelativePosition}\n resultsBorder={resultsBorder}\n onNotFound={onNotFound}\n onEmptyMessageChange={setEmptyMessage}\n notFoundComponent={notFoundComponent?.(searchQuery ?? '')}\n />\n )}\n </Field>\n <LiveRegion role=\"status\">{noResults ? emptyMessage : ''}</LiveRegion>\n </Wrapper>\n )\n },\n)\n\nconst Wrapper = styled(Box)`\n position: relative;\n`\n\nconst LiveRegion = styled.div`\n ${visuallyHidden}\n`\n\nconst Line = styled(Box)`\n background: ${({ theme }) => theme.color.border.subtle};\n height: 24px;\n width: 1px;\n`\n\nconst Icons = styled(Box)<{ $clearSearch: boolean }>`\n position: relative;\n right: ${({ $clearSearch }) => ($clearSearch ? '80px' : '48px')};\n margin-right: ${({ $clearSearch }) => ($clearSearch ? '-80px' : '-48px')};\n`\n"]}
|
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect } from 'react';
|
|
3
|
-
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
3
|
+
const DEFAULT_MESSAGE = 'No results';
|
|
4
|
+
const BLOCK_SELECTOR = 'address, article, aside, blockquote, dd, div, dl, dt, figcaption, footer, h1, h2, h3, h4, h5, h6, header, li, ol, p, section, ul';
|
|
5
|
+
// Reads the element's text as a screen reader would: skipping hidden parts,
|
|
6
|
+
// and adding a space where it moves to another block or line, so a heading
|
|
7
|
+
// and a hint don't run together.
|
|
8
|
+
const getText = (element) => {
|
|
9
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
|
|
10
|
+
let text = '';
|
|
11
|
+
let previousBlock = null;
|
|
12
|
+
while (walker.nextNode()) {
|
|
13
|
+
const node = walker.currentNode;
|
|
14
|
+
if (node.nodeName === 'BR') {
|
|
15
|
+
text += ' ';
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (node.nodeType !== Node.TEXT_NODE)
|
|
19
|
+
continue;
|
|
20
|
+
const parent = node.parentElement;
|
|
21
|
+
if (parent?.closest('[aria-hidden="true"], [hidden]'))
|
|
22
|
+
continue;
|
|
23
|
+
const block = parent?.closest(BLOCK_SELECTOR) ?? null;
|
|
24
|
+
if (previousBlock && block !== previousBlock) {
|
|
25
|
+
text += ' ';
|
|
26
|
+
}
|
|
27
|
+
text += node.textContent ?? '';
|
|
28
|
+
previousBlock = block;
|
|
29
|
+
}
|
|
30
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
31
|
+
};
|
|
32
|
+
export const EmptyResults = ({ onNotFound, onMessageChange, searchTerm, notFoundComponent, }) => {
|
|
33
|
+
const ref = useRef(null);
|
|
4
34
|
useEffect(() => {
|
|
5
35
|
onNotFound?.(searchTerm);
|
|
6
36
|
}, []);
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
37
|
+
// Report the text people see, so the live region announces a custom
|
|
38
|
+
// notFoundComponent too. A layout effect updates it before paint.
|
|
39
|
+
useLayoutEffect(() => {
|
|
40
|
+
const text = ref.current ? getText(ref.current) : '';
|
|
41
|
+
onMessageChange(text || DEFAULT_MESSAGE);
|
|
42
|
+
}, [searchTerm, notFoundComponent, onMessageChange]);
|
|
43
|
+
return _jsx("li", { ref: ref, children: notFoundComponent || DEFAULT_MESSAGE });
|
|
11
44
|
};
|
|
12
45
|
//# sourceMappingURL=EmptyResults.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyResults.js","sourceRoot":"","sources":["../../../src/SearchInput/components/EmptyResults.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"EmptyResults.js","sourceRoot":"","sources":["../../../src/SearchInput/components/EmptyResults.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAiB,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEzE,MAAM,eAAe,GAAG,YAAY,CAAA;AASpC,MAAM,cAAc,GAClB,kIAAkI,CAAA;AAEpI,4EAA4E;AAC5E,2EAA2E;AAC3E,iCAAiC;AACjC,MAAM,OAAO,GAAG,CAAC,OAAoB,EAAE,EAAE;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CACtC,OAAO,EACP,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC,YAAY,CAC/C,CAAA;IACD,IAAI,IAAI,GAAG,EAAE,CAAA;IACb,IAAI,aAAa,GAAmB,IAAI,CAAA;IACxC,OAAO,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAA;QAC/B,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC3B,IAAI,IAAI,GAAG,CAAA;YACX,SAAQ;QACV,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS;YAAE,SAAQ;QAE9C,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAA;QACjC,IAAI,MAAM,EAAE,OAAO,CAAC,gCAAgC,CAAC;YAAE,SAAQ;QAE/D,MAAM,KAAK,GAAG,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI,IAAI,CAAA;QACrD,IAAI,aAAa,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;YAC7C,IAAI,IAAI,GAAG,CAAA;QACb,CAAC;QACD,IAAI,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,CAAA;QAC9B,aAAa,GAAG,KAAK,CAAA;IACvB,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AACzC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,YAAY,GAAc,CAAC,EACtC,UAAU,EACV,eAAe,EACf,UAAU,EACV,iBAAiB,GAClB,EAAE,EAAE;IACH,MAAM,GAAG,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAA;IAEvC,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,EAAE,CAAC,UAAU,CAAC,CAAA;IAC1B,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,oEAAoE;IACpE,kEAAkE;IAClE,eAAe,CAAC,GAAG,EAAE;QACnB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACpD,eAAe,CAAC,IAAI,IAAI,eAAe,CAAC,CAAA;IAC1C,CAAC,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAA;IAEpD,OAAO,aAAI,GAAG,EAAE,GAAG,YAAG,iBAAiB,IAAI,eAAe,GAAM,CAAA;AAClE,CAAC,CAAA","sourcesContent":["import { FC, ReactNode, useEffect, useLayoutEffect, useRef } from 'react'\n\nconst DEFAULT_MESSAGE = 'No results'\n\ninterface Props {\n onNotFound?: (searchTerm: string) => void\n onMessageChange: (message: string) => void\n searchTerm: string\n notFoundComponent?: ReactNode\n}\n\nconst BLOCK_SELECTOR =\n 'address, article, aside, blockquote, dd, div, dl, dt, figcaption, footer, h1, h2, h3, h4, h5, h6, header, li, ol, p, section, ul'\n\n// Reads the element's text as a screen reader would: skipping hidden parts,\n// and adding a space where it moves to another block or line, so a heading\n// and a hint don't run together.\nconst getText = (element: HTMLElement) => {\n const walker = document.createTreeWalker(\n element,\n NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,\n )\n let text = ''\n let previousBlock: Element | null = null\n while (walker.nextNode()) {\n const node = walker.currentNode\n if (node.nodeName === 'BR') {\n text += ' '\n continue\n }\n if (node.nodeType !== Node.TEXT_NODE) continue\n\n const parent = node.parentElement\n if (parent?.closest('[aria-hidden=\"true\"], [hidden]')) continue\n\n const block = parent?.closest(BLOCK_SELECTOR) ?? null\n if (previousBlock && block !== previousBlock) {\n text += ' '\n }\n text += node.textContent ?? ''\n previousBlock = block\n }\n return text.replace(/\\s+/g, ' ').trim()\n}\n\nexport const EmptyResults: FC<Props> = ({\n onNotFound,\n onMessageChange,\n searchTerm,\n notFoundComponent,\n}) => {\n const ref = useRef<HTMLLIElement>(null)\n\n useEffect(() => {\n onNotFound?.(searchTerm)\n }, [])\n\n // Report the text people see, so the live region announces a custom\n // notFoundComponent too. A layout effect updates it before paint.\n useLayoutEffect(() => {\n const text = ref.current ? getText(ref.current) : ''\n onMessageChange(text || DEFAULT_MESSAGE)\n }, [searchTerm, notFoundComponent, onMessageChange])\n\n return <li ref={ref}>{notFoundComponent || DEFAULT_MESSAGE}</li>\n}\n"]}
|
|
@@ -3,19 +3,25 @@ type Option = {
|
|
|
3
3
|
label: string;
|
|
4
4
|
value: string;
|
|
5
5
|
};
|
|
6
|
+
export type SearchKeyEvent = {
|
|
7
|
+
key: string;
|
|
8
|
+
preventDefault: () => void;
|
|
9
|
+
stopPropagation: () => void;
|
|
10
|
+
};
|
|
6
11
|
type SearchOptionsProps = {
|
|
7
12
|
displayedList: Array<Option>;
|
|
8
13
|
selectedValue: string | null;
|
|
9
14
|
highlightedIndex: number;
|
|
10
15
|
setHighlightedIndex: (arg: number) => void;
|
|
11
16
|
onSelect: (option: Option) => void;
|
|
12
|
-
onKeyDown: (e:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
onKeyDown: (e: SearchKeyEvent) => void;
|
|
18
|
+
listboxId: string;
|
|
19
|
+
listboxLabel: string;
|
|
20
|
+
getOptionId: (index: number) => string;
|
|
16
21
|
positionRelative: boolean;
|
|
17
22
|
resultsBorder: boolean;
|
|
18
23
|
onNotFound?: (searchTerm: string) => void;
|
|
24
|
+
onEmptyMessageChange: (message: string) => void;
|
|
19
25
|
searchTerm: string;
|
|
20
26
|
notFoundComponent?: ReactNode;
|
|
21
27
|
};
|
|
@@ -4,7 +4,7 @@ import styled, { css } from 'styled-components';
|
|
|
4
4
|
import { Box } from '../../Box';
|
|
5
5
|
import { Icon } from '../../Icon';
|
|
6
6
|
import { EmptyResults } from './EmptyResults';
|
|
7
|
-
export const SearchOptions = ({ displayedList, selectedValue, highlightedIndex, setHighlightedIndex, onSelect, onKeyDown, positionRelative, resultsBorder, onNotFound, searchTerm, notFoundComponent, }) => {
|
|
7
|
+
export const SearchOptions = ({ displayedList, selectedValue, highlightedIndex, setHighlightedIndex, onSelect, onKeyDown, listboxId, listboxLabel, getOptionId, positionRelative, resultsBorder, onNotFound, onEmptyMessageChange, searchTerm, notFoundComponent, }) => {
|
|
8
8
|
const itemRefs = useRef([]);
|
|
9
9
|
const resultsListRef = useRef(null);
|
|
10
10
|
useEffect(() => {
|
|
@@ -25,16 +25,16 @@ export const SearchOptions = ({ displayedList, selectedValue, highlightedIndex,
|
|
|
25
25
|
}
|
|
26
26
|
setHighlightedIndex(-1);
|
|
27
27
|
}, [searchTerm, setHighlightedIndex]);
|
|
28
|
-
return (_jsx(BoxWithPositionRelative, { children: _jsx(StyledResultsContainer, { $positionRelative: positionRelative, children: _jsx(ResultsList, { ref: resultsListRef, $resultsBorder: resultsBorder, onKeyDown: onKeyDown, children: displayedList.length ? (
|
|
28
|
+
return (_jsx(BoxWithPositionRelative, { children: _jsx(StyledResultsContainer, { $positionRelative: positionRelative, children: _jsx(ResultsList, { ref: resultsListRef, id: listboxId, role: displayedList.length ? 'listbox' : undefined, "aria-label": displayedList.length ? listboxLabel : undefined, $resultsBorder: resultsBorder, onKeyDown: onKeyDown, children: displayedList.length ? (
|
|
29
29
|
// oxlint-disable-next-line react/react-compiler
|
|
30
30
|
displayedList.map((el, i) => {
|
|
31
31
|
const isSelected = selectedValue === el.label || selectedValue === el.value;
|
|
32
|
-
return (_jsxs(ListButton, { "aria-
|
|
32
|
+
return (_jsxs(ListButton, { id: getOptionId(i), role: "option", "aria-selected": highlightedIndex === i, ref: itemRefs.current[i], onClick: () => onSelect(el), $isSelected: isSelected, $showBg: highlightedIndex === i, onMouseEnter: () => {
|
|
33
33
|
setHighlightedIndex(i);
|
|
34
34
|
}, onFocus: () => {
|
|
35
35
|
setHighlightedIndex(i);
|
|
36
36
|
}, children: [el.label, isSelected && (_jsx(Icon, { render: "tick", size: 16, color: "color.surface.brand.300" }))] }, el.label + '_list_item'));
|
|
37
|
-
})) : (_jsx(EmptyResults, { onNotFound: onNotFound, searchTerm: searchTerm, notFoundComponent: notFoundComponent })) }) }) }));
|
|
37
|
+
})) : (_jsx(EmptyResults, { onNotFound: onNotFound, onMessageChange: onEmptyMessageChange, searchTerm: searchTerm, notFoundComponent: notFoundComponent })) }) }) }));
|
|
38
38
|
};
|
|
39
39
|
const StyledResultsContainer = styled.div `
|
|
40
40
|
box-sizing: border-box;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchOptions.js","sourceRoot":"","sources":["../../../src/SearchInput/components/SearchOptions.tsx"],"names":[],"mappings":";AAAA,OAAO,EAA4B,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAC9E,OAAO,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"SearchOptions.js","sourceRoot":"","sources":["../../../src/SearchInput/components/SearchOptions.tsx"],"names":[],"mappings":"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","sourcesContent":["import { FC, ReactNode, RefObject, createRef, useEffect, useRef } from 'react'\nimport styled, { css } from 'styled-components'\nimport { TransientProps } from '../../utils/utilTypes'\nimport { Box } from '../../Box'\nimport { Icon } from '../../Icon'\nimport { EmptyResults } from './EmptyResults'\n\ntype Option = {\n label: string\n value: string\n}\n\nexport type SearchKeyEvent = {\n key: string\n preventDefault: () => void\n stopPropagation: () => void\n}\n\ntype SearchOptionsProps = {\n displayedList: Array<Option>\n selectedValue: string | null\n highlightedIndex: number\n setHighlightedIndex: (arg: number) => void\n onSelect: (option: Option) => void\n onKeyDown: (e: SearchKeyEvent) => void\n listboxId: string\n listboxLabel: string\n getOptionId: (index: number) => string\n positionRelative: boolean\n resultsBorder: boolean\n onNotFound?: (searchTerm: string) => void\n onEmptyMessageChange: (message: string) => void\n searchTerm: string\n notFoundComponent?: ReactNode\n}\n\nexport const SearchOptions: FC<SearchOptionsProps> = ({\n displayedList,\n selectedValue,\n highlightedIndex,\n setHighlightedIndex,\n onSelect,\n onKeyDown,\n listboxId,\n listboxLabel,\n getOptionId,\n positionRelative,\n resultsBorder,\n onNotFound,\n onEmptyMessageChange,\n searchTerm,\n notFoundComponent,\n}) => {\n const itemRefs = useRef<RefObject<HTMLLIElement | null>[]>([])\n const resultsListRef = useRef<HTMLUListElement>(null)\n\n useEffect(() => {\n itemRefs.current = displayedList.map(\n (_, i) => itemRefs.current[i] ?? createRef<HTMLLIElement>(),\n )\n }, [displayedList.length])\n\n useEffect(() => {\n const focusedListItem = itemRefs?.current[highlightedIndex]?.current\n if (highlightedIndex >= 0 && focusedListItem) {\n focusedListItem.scrollIntoView({\n block: 'nearest',\n inline: 'start',\n })\n }\n }, [highlightedIndex])\n\n useEffect(() => {\n if (resultsListRef.current) {\n resultsListRef.current.scrollTop = 0\n }\n setHighlightedIndex(-1)\n }, [searchTerm, setHighlightedIndex])\n\n return (\n <BoxWithPositionRelative>\n <StyledResultsContainer $positionRelative={positionRelative}>\n <ResultsList\n ref={resultsListRef}\n id={listboxId}\n role={displayedList.length ? 'listbox' : undefined}\n aria-label={displayedList.length ? listboxLabel : undefined}\n $resultsBorder={resultsBorder}\n onKeyDown={onKeyDown}\n >\n {displayedList.length ? (\n // oxlint-disable-next-line react/react-compiler\n displayedList.map((el, i) => {\n const isSelected =\n selectedValue === el.label || selectedValue === el.value\n\n return (\n <ListButton\n key={el.label + '_list_item'}\n id={getOptionId(i)}\n role=\"option\"\n aria-selected={highlightedIndex === i}\n ref={itemRefs.current[i]}\n onClick={() => onSelect(el)}\n $isSelected={isSelected}\n $showBg={highlightedIndex === i}\n onMouseEnter={() => {\n setHighlightedIndex(i)\n }}\n onFocus={() => {\n setHighlightedIndex(i)\n }}\n >\n {el.label}\n {isSelected && (\n <Icon\n render=\"tick\"\n size={16}\n color=\"color.surface.brand.300\"\n />\n )}\n </ListButton>\n )\n })\n ) : (\n <EmptyResults\n onNotFound={onNotFound}\n onMessageChange={onEmptyMessageChange}\n searchTerm={searchTerm}\n notFoundComponent={notFoundComponent}\n />\n )}\n </ResultsList>\n </StyledResultsContainer>\n </BoxWithPositionRelative>\n )\n}\n\nconst StyledResultsContainer = styled.div<\n TransientProps<Pick<SearchOptionsProps, 'positionRelative'>>\n>`\n box-sizing: border-box;\n overflow: hidden;\n margin: -16px;\n padding: ${({ theme }) => theme.space[200]};\n ${({ $positionRelative }) => !$positionRelative && 'position: absolute;'}\n width: calc(100% + 32px);\n left: 0px;\n top: -8px;\n\n ul {\n max-height: 192px;\n }\n`\n\nconst ResultsList = styled.ul<\n TransientProps<Pick<SearchOptionsProps, 'resultsBorder'>>\n>`\n position: relative;\n list-style: none;\n overflow-y: auto;\n padding: 0;\n margin: 0;\n background-color: ${({ theme }) => theme.color.background['000']};\n border-radius: 12px;\n margin-top: 14px;\n z-index: 1000;\n border: 1px solid ${({ theme }) => theme.color.border.subtle};\n box-shadow:\n 0px 10px 18px rgba(18, 18, 23, 0.03),\n 0px 5px 8px rgba(18, 18, 23, 0.04),\n 0px 2px 4px rgba(18, 18, 23, 0.04);\n\n li {\n padding: 16px 14px;\n box-sizing: border-box;\n font-size: 16px;\n color: ${({ theme }) => theme.color.text.base};\n cursor: pointer;\n }\n`\n\nconst ListButton = styled.li<{ $isSelected: boolean; $showBg: boolean }>`\n display: flex;\n justify-content: space-between;\n\n &:focus {\n outline: none;\n }\n &:focus-visible {\n outline: none;\n }\n\n ${({ $showBg }) =>\n $showBg &&\n css`\n background-color: ${({ theme }) => theme.color.background[200]};\n `}\n\n ${({ $isSelected }) =>\n $isSelected &&\n css`\n background-color: ${({ theme }) => theme.color.surface.base[300]};\n `}\n`\n\nconst BoxWithPositionRelative = styled(Box)`\n position: relative;\n`\n"]}
|