admin(ui): fix up SearchableSelect.tsx
This commit is contained in:
parent
f790404845
commit
3971c4499d
7 changed files with 157 additions and 58 deletions
|
@ -1,31 +1,35 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import { debounce } from 'debounce';
|
||||
import styled from 'styled-components/macro';
|
||||
import tw from 'twin.macro';
|
||||
import Input from '@/components/elements/Input';
|
||||
import Label from '@/components/elements/Label';
|
||||
import InputSpinner from '@/components/elements/InputSpinner';
|
||||
import { debounce } from 'debounce';
|
||||
|
||||
const Dropdown = styled.div<{ expanded: boolean }>`
|
||||
${tw`absolute mt-1 w-full rounded-md bg-neutral-900 shadow-lg z-10`};
|
||||
${props => !props.expanded && tw`hidden`};
|
||||
`;
|
||||
|
||||
interface Props<T> {
|
||||
interface SearchableSelectProps<T> {
|
||||
id: string;
|
||||
name: string;
|
||||
nullable: boolean;
|
||||
|
||||
selected: T | null;
|
||||
|
||||
items: T[];
|
||||
setItems: (items: T[]) => void;
|
||||
|
||||
onSearch: (query: string) => Promise<void>;
|
||||
onSelect: (item: T) => void;
|
||||
|
||||
getSelectedText: (item: T | null) => string;
|
||||
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function SearchableSelect<T> ({ id, name, items, setItems, onSearch, children }: Props<T>) {
|
||||
function SearchableSelect<T> ({ id, name, selected, items, setItems, onSearch, onSelect, getSelectedText, children }: SearchableSelectProps<T>) {
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const [ expanded, setExpanded ] = useState(false);
|
||||
|
||||
|
@ -51,15 +55,10 @@ function SearchableSelect<T> ({ id, name, items, setItems, onSearch, children }:
|
|||
onSearch(query).then(() => setLoading(false));
|
||||
}, 250);
|
||||
|
||||
/* const selectItem = (item: any) => {
|
||||
onSelect(item);
|
||||
}; */
|
||||
|
||||
useEffect(() => {
|
||||
// setInputText(location.short);
|
||||
setInputText(getSelectedText(selected) || '');
|
||||
setExpanded(false);
|
||||
}, [ ]);
|
||||
// }, [ location ]);
|
||||
}, [ selected ]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
|
@ -67,7 +66,7 @@ function SearchableSelect<T> ({ id, name, items, setItems, onSearch, children }:
|
|||
return;
|
||||
}
|
||||
|
||||
// setInputText(location.short);
|
||||
setInputText(getSelectedText(selected) || '');
|
||||
setExpanded(false);
|
||||
};
|
||||
|
||||
|
@ -77,6 +76,16 @@ function SearchableSelect<T> ({ id, name, items, setItems, onSearch, children }:
|
|||
};
|
||||
}, [ expanded ]);
|
||||
|
||||
const onClick = (item: T) => (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
onSelect(item);
|
||||
};
|
||||
|
||||
// This shit is really stupid but works, so is it really stupid?
|
||||
const c = React.Children.map(children, child => React.cloneElement(child as ReactElement, {
|
||||
onClick: onClick.bind(child),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label htmlFor={id}>{name}</Label>
|
||||
|
@ -108,7 +117,7 @@ function SearchableSelect<T> ({ id, name, items, setItems, onSearch, children }:
|
|||
</div>
|
||||
:
|
||||
<ul tabIndex={-1} css={tw`max-h-56 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm`}>
|
||||
{children}
|
||||
{c}
|
||||
</ul>
|
||||
}
|
||||
</Dropdown>
|
||||
|
@ -117,4 +126,49 @@ function SearchableSelect<T> ({ id, name, items, setItems, onSearch, children }:
|
|||
);
|
||||
}
|
||||
|
||||
interface OptionProps<T> {
|
||||
id: string | number;
|
||||
item: T;
|
||||
active: boolean;
|
||||
|
||||
onClick?: (item: T) => (e: React.MouseEvent) => void;
|
||||
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Option<T> ({ id, item, active, onClick, children }: OptionProps<T>) {
|
||||
if (onClick === undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onClick = () => () => {};
|
||||
}
|
||||
|
||||
if (active) {
|
||||
return (
|
||||
<li id={'select-item-' + id} role="option" css={tw`text-neutral-200 cursor-pointer select-none relative py-2 pl-3 pr-9 hover:bg-neutral-700`} onClick={onClick(item)}>
|
||||
<div css={tw`flex items-center`}>
|
||||
<span css={tw`block font-medium truncate`}>
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span css={tw`absolute inset-y-0 right-0 flex items-center pr-4`}>
|
||||
<svg css={tw`h-5 w-5 text-primary-400`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path clipRule="evenodd" fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li id={'select-item-' + id} role="option" css={tw`text-neutral-200 cursor-pointer select-none relative py-2 pl-3 pr-9 hover:bg-neutral-700`} onClick={onClick(item)}>
|
||||
<div css={tw`flex items-center`}>
|
||||
<span css={tw`block font-normal truncate`}>
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default SearchableSelect;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue