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  }