go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/nodes/static/_nextjs/src/components/selectStrings.tsx (about) 1 /** 2 * Copyright (c) 2024 - Present. Will Charczuk. All rights reserved. 3 * Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 4 */ 5 import { Button, MenuItem } from "@blueprintjs/core"; 6 import { ItemPredicate, ItemRenderer, Select } from "@blueprintjs/select"; 7 import { useEffect, useState } from "react"; 8 9 export interface SelectStringsProps { 10 id?: string; 11 items: string[]; 12 placeholder?: string; 13 value?: string; 14 disabled?: boolean; 15 fill?: boolean; 16 tabIndex?: number; 17 autoFocus?: boolean; 18 onItemSelect: (item: string) => void; 19 } 20 21 declare interface IndexedItem { 22 index: number; 23 item: string; 24 } 25 26 export function SelectStrings(props: SelectStringsProps) { 27 const indexedItems: Array<IndexedItem> = props.items.map((v, i) => { 28 return { 29 index: i, 30 item: v, 31 } 32 }); 33 34 const [selectedItem, setSelectedItem] = useState<IndexedItem | undefined>(); 35 36 const filterItem: ItemPredicate<IndexedItem> = (query, item, _index, exactMatch) => { 37 const normalizedItem = item.item.toLowerCase(); 38 const normalizedQuery = query.toLowerCase(); 39 40 if (exactMatch) { 41 return normalizedItem === normalizedQuery; 42 } else { 43 return `${item.index}. ${item.item}`.indexOf(normalizedQuery) >= 0; 44 } 45 }; 46 47 const renderItem: ItemRenderer<IndexedItem> = (item, { handleClick, handleFocus, modifiers, query }) => { 48 if (!modifiers.matchesPredicate) { 49 return null; 50 } 51 return ( 52 <MenuItem 53 active={modifiers.active} 54 disabled={modifiers.disabled} 55 key={item.index} 56 label={item.item} 57 onClick={handleClick} 58 onFocus={handleFocus} 59 roleStructure="listoption" 60 text={`${item.item}`} 61 /> 62 ); 63 }; 64 65 const onItemSelect = (item: IndexedItem) => { 66 props.onItemSelect(item.item); 67 setSelectedItem(item); 68 } 69 70 useEffect(() => { 71 if (props.value) { 72 setSelectedItem(indexedItems.find(i => i.item === props.value)) 73 } 74 }, [props.value]) 75 76 return ( 77 <Select<IndexedItem> 78 items={indexedItems} 79 itemPredicate={filterItem} 80 itemRenderer={renderItem} 81 noResults={<MenuItem disabled={true} text="No results." roleStructure="listoption" />} 82 onItemSelect={onItemSelect} 83 disabled={props.disabled || indexedItems.length === 1} 84 fill={props.fill} 85 > 86 <Button autoFocus={props.autoFocus} id={props.id} tabIndex={props.tabIndex} text={selectedItem ? selectedItem.item : props.placeholder ? props.placeholder : 'Please select an item'} fill={props.fill} disabled={props.disabled || indexedItems.length === 1} rightIcon="double-caret-vertical" /> 87 </Select> 88 ) 89 }