0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(console): add support for searching in select component (#4638)

Implement search functionality in the select component for better user experience.
This commit is contained in:
ratnaraj7 2023-10-28 10:31:37 +05:30 committed by GitHub
parent d9a469dee3
commit f2b3f39422
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 2 deletions

View file

@ -129,3 +129,29 @@
font: var(--font-body-2);
padding: _.unit(2);
}
.searchInputContainer {
display: flex;
align-items: center;
.search {
margin-right: _.unit(2);
color: var(--color-text-secondary);
width: 20px;
height: 20px;
}
.searchInput {
height: 100%;
width: 100%;
appearance: none;
color: var(--color-text);
font: var(--font-body-2);
background: transparent;
padding: 0;
&::placeholder {
color: var(--color-placeholder);
}
}
}

View file

@ -1,10 +1,13 @@
import classNames from 'classnames';
import type { ReactEventHandler, ReactNode } from 'react';
import { useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Close from '@/assets/icons/close.svg';
import KeyboardArrowDown from '@/assets/icons/keyboard-arrow-down.svg';
import KeyboardArrowUp from '@/assets/icons/keyboard-arrow-up.svg';
import SearchIcon from '@/assets/icons/search.svg';
import useWindowResize from '@/hooks/use-window-resize';
import { onKeyDownHandler } from '@/utils/a11y';
import Dropdown, { DropdownItem } from '../Dropdown';
@ -27,6 +30,7 @@ type Props<T> = {
placeholder?: ReactNode;
isClearable?: boolean;
size?: 'small' | 'medium' | 'large';
isSearchEnabled?: boolean;
};
function Select<T extends string>({
@ -39,10 +43,21 @@ function Select<T extends string>({
placeholder,
isClearable,
size = 'large',
isSearchEnabled,
}: Props<T>) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isOpen, setIsOpen] = useState(false);
const [searchInputValue, setSearchInputValue] = useState('');
const [searchInputContainerStyles, setSearchInputContainerStyles] = useState({});
const anchorRef = useRef<HTMLInputElement>(null);
const current = options.find((option) => value && option.value === value);
const filteredOptions = useMemo(() => {
return searchInputValue
? options.filter(({ value }) =>
value.toLocaleLowerCase().includes(searchInputValue.toLocaleLowerCase())
)
: options;
}, [searchInputValue, options]);
const handleSelect = (value: T) => {
onChange?.(value);
@ -55,6 +70,45 @@ function Select<T extends string>({
event.stopPropagation();
};
const getSearchInputContainerStyles = () => {
if (!anchorRef.current) {
return {};
}
const element = anchorRef.current;
const cs = getComputedStyle(element);
const paddingX = Number.parseFloat(cs.paddingLeft) + Number.parseFloat(cs.paddingRight);
const paddingY = Number.parseFloat(cs.paddingTop) + Number.parseFloat(cs.paddingBottom);
const borderX = Number.parseFloat(cs.borderLeftWidth) + Number.parseFloat(cs.borderRightWidth);
const borderY = Number.parseFloat(cs.borderTopWidth) + Number.parseFloat(cs.borderBottomWidth);
return {
position: 'fixed',
width: `${element.offsetWidth - paddingX - borderX}px`,
height: `${element.offsetHeight - paddingY - borderY}px`,
top: `${
element.getBoundingClientRect().top +
Number.parseFloat(cs.borderTopWidth) +
Number.parseFloat(cs.paddingTop)
}px`,
left: `${
element.getBoundingClientRect().left +
Number.parseFloat(cs.borderLeftWidth) +
Number.parseFloat(cs.paddingLeft)
}px`,
backgroundColor: cs.backgroundColor,
};
};
useWindowResize(() => {
setSearchInputContainerStyles(getSearchInputContainerStyles());
});
useEffect(() => {
if (isOpen) {
anchorRef.current?.scrollIntoView({ block: 'nearest' });
setSearchInputContainerStyles(getSearchInputContainerStyles());
}
}, [isOpen]);
return (
<>
<div
@ -102,9 +156,29 @@ function Select<T extends string>({
isOpen={isOpen}
onClose={() => {
setIsOpen(false);
setSearchInputValue('');
}}
>
{options.map(({ value, title }) => (
{isSearchEnabled && isOpen && (
<div style={searchInputContainerStyles} className={styles.searchInputContainer}>
<SearchIcon className={styles.search} />
<input
ref={(input) => input?.focus()}
className={styles.searchInput}
value={searchInputValue}
role="searchbox"
autoComplete="off"
placeholder={t('general.type_to_search')}
onChange={(event) => {
setSearchInputValue(event.target.value);
}}
onClick={(event) => {
event.stopPropagation();
}}
/>
</div>
)}
{filteredOptions.map(({ value, title }) => (
<DropdownItem
key={value}
onClick={() => {