Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
3.1 kB · 114 lines
TSX
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115import deepEqual from "fast-deep-equal";import { matchSorter } from "match-sorter";import { Fragment, type CSSProperties, type ReactNode } from "react";import { useMemo, useState } from "react";
const capitalCase = (str: string) => { return str[0].toUpperCase() + str.slice(1);};
type OnlyString<T> = T extends string ? T : never;
interface SearchableListProps<ID extends string | number = string, L extends ListItem = ListItem> { items: L[]; selectedItem?: ID | null; selectedItems?: ID[]; setSelectedItem?: (item: L) => void; renderItem?: (item: L) => ReactNode; getItemClassName?: (item: L) => string; getItemStyle?: (item: L) => CSSProperties; searchKeys?: OnlyString<keyof L>[]; showSearchLabel?: boolean; searchLabel?: string; searchPlaceholder?: string; categorySort?: (a: [string, L[]], b: [string, L[]]) => number;}interface ListItem { id: any; [key: string]: any;}function SearchableList< ID extends Item["id"], Item extends ListItem = { id: unknown; label: string; category: string },>({ items, selectedItem, selectedItems, setSelectedItem, renderItem, getItemClassName, getItemStyle, searchKeys = ["label", "category"] as OnlyString<keyof Item>[], showSearchLabel = true, searchLabel = "Search", searchPlaceholder = "Search...", categorySort = ([a], [b]) => { if (a > b) return 1; if (b > a) return -1; return 0; },}: SearchableListProps<ID, Item>) { const [search, setSearch] = useState<string>(""); const filteredObjects = useMemo( () => (search ? matchSorter(items, search, { keys: searchKeys }) : items), [items, search, searchKeys], ); const sortedIntoCategories = filteredObjects.reduce( (prev: { [key: string]: Item[] }, next: Item) => { const cat = next.category || ""; prev[cat] = prev[cat] || []; prev[cat].push(next); return prev; }, {}, ); return ( <> <div className="form-control"> {showSearchLabel ? <span className="label">{searchLabel}</span> : null} <input type="search" value={search} onChange={(e) => setSearch(e.currentTarget.value)} className="input" placeholder={searchPlaceholder} /> </div> <ul className="flex-1 overflow-y-auto select-none"> {Object.entries(sortedIntoCategories) .concat() .sort(categorySort) .map(([key, items]) => ( <Fragment key={key}> {key && ( <li className="list-group-item list-group-item-small disabled"> {capitalCase(key)} </li> )} {items.map((c) => { return ( <li key={JSON.stringify(c.id)} className={`list-group-item ${ deepEqual(c.id, selectedItem) || selectedItems?.some((id) => deepEqual(c.id, id)) ? "selected" : "" } ${getItemClassName?.(c) ?? ""}`} style={getItemStyle?.(c)} onClick={() => { setSelectedItem?.(c); }} > {renderItem ? renderItem(c) : c.label} </li> ); })} </Fragment> ))} </ul> </> );}
export default SearchableList;