Skip to content

AutoSuggest

To create one of the search patterns (Combobox, AutoSuggest) you will need the following:

Combobox

tsx
import { Box } from '@semcore/ui/base-components';
import Input from '@semcore/ui/input';
import Select from '@semcore/ui/select';
import { Text } from '@semcore/ui/typography';
import React from 'react';

const options = Array(12)
  .fill(0)
  .map((_, i) => ({
    value: `${i}:00`.padStart(5, '0'),
    title: `${i}:00`.padStart(5, '0'),
  }));

const Demo = () => {
  const [highlightedIndex, setHighlightedIndex] = React.useState<number | null>(null);
  const [value, setValue] = React.useState('');

  React.useEffect(() => {
    const exactValueIndex = options.findIndex((option) => option.value === value);

    if (exactValueIndex > -1) {
      setHighlightedIndex(exactValueIndex);
    }
  }, [value]);

  const handleHighlightedIndexChange = (index: number | null) => {
    setHighlightedIndex(index);
  };

  return (
    <>
      <Text tag='label' size={200} htmlFor='release-time-picker'>
        Release time
      </Text>
      <Box mt={2}>
        <Select
          interaction='focus'
          onChange={setValue}
          value={value}
          highlightedIndex={highlightedIndex}
          onHighlightedIndexChange={handleHighlightedIndexChange}
        >
          <Select.Trigger tag={Input} w={150}>
            {() => <Input.Value value={value} onChange={setValue} id='release-time-picker' />}
          </Select.Trigger>
          <Select.Menu>
            {options.map((option) => (
              <Select.Option value={option.value} key={option.value}>
                {option.title}
              </Select.Option>
            ))}
          </Select.Menu>
        </Select>
      </Box>
    </>
  );
};

export default Demo;

AutoSuggest

tsx
import { Box } from '@semcore/ui/base-components';
import Input from '@semcore/ui/input';
import Select from '@semcore/ui/select';
import { Text } from '@semcore/ui/typography';
import React from 'react';

const Highlight = ({ highlight, children }: { highlight: string; children: string }) => {
  let html = children;
  if (highlight) {
    try {
      const re = new RegExp(highlight.toLowerCase(), 'g');
      html = html.replace(
        re,
        `<span style="font-weight: bold; padding: 2px 0">${highlight}</span>`,
      );
    } catch (e) {}
  }
  return <span dangerouslySetInnerHTML={{ __html: html }} />;
};

const debounce = (func: Function, timeout: number) => {
  let timer: number;
  return (...args: any[]) => {
    window.clearTimeout(timer);
    timer = window.setTimeout(() => {
      func(...args);
    }, timeout);
  };
};

type Suggestion = {
  value: string;
  title: string;
};

const fakeFetch = async (query: string): Promise<Suggestion[]> => {
  if (!query) return [];

  return [
    'persian',
    'maine coon',
    'ragdoll',
    'sphynx',
    'siamese',
    'bengal',
    'british shorthair',
    'abyssinian',
    'birman',
    'oriental shorthair',
    'scottish fold',
    'devon rex',
    'norwegian forest',
    'siberian',
    'russian blue',
    'savannah',
    'american shorthair',
    'exotic shorthair',
    'ragamuffin',
    'balinese',
  ]
    .filter((breed) => breed.toLowerCase().includes(query.toLowerCase()))
    .map((value) => ({ value, title: value }));
};

const Demo = () => {
  const [highlightedIndex, setHighlightedIndex] = React.useState<number | null>(-1);
  const [visible, setVisible] = React.useState(false);
  const [query, setQuery] = React.useState('');
  const [suggestions, setSuggestions] = React.useState<Suggestion[]>([]);
  const loadSuggestions = React.useCallback(
    debounce(
      (query: string) => fakeFetch(query).then((suggestions) => setSuggestions(suggestions)),
      300,
    ),
    [],
  );
  React.useEffect(() => {
    loadSuggestions(query);
  }, [query]);
  const handleSelect = React.useCallback((x: string) => {
    setQuery(x);
    setVisible(false);
  }, []);

  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (!e.key.startsWith('Array')) {
      setHighlightedIndex(-1);
    }
  };

  const handleHighlightedIndexChange = (index: number | null) => {
    setHighlightedIndex(index);
  };

  const handleChangeVisible = (visible: boolean) => {
    setVisible(visible);
  };

  return (
    <>
      <Text tag='label' size={200} htmlFor='website-autosuggest'>
        Your pet breed
      </Text>
      <Box mt={2}>
        <Select
          interaction='focus'
          onChange={handleSelect}
          value={query}
          visible={visible}
          onVisibleChange={handleChangeVisible}
          highlightedIndex={highlightedIndex}
          onHighlightedIndexChange={handleHighlightedIndexChange}
          defaultHighlightedIndex={null}
        >
          <Select.Trigger tag={Input} w={250}>
            {() => (
              <Input.Value
                value={query}
                role='combobox'
                placeholder='Start typing for options'
                onChange={setQuery}
                id='website-autosuggest'
                onKeyDown={handleKeyDown}
                autoComplete='off'
              />
            )}
          </Select.Trigger>
          {suggestions.length > 0 && (
            <Select.Menu>
              {suggestions.map((option) => (
                <Select.Option value={option.value} key={option.value}>
                  <Highlight highlight={query}>{option.title}</Highlight>
                </Select.Option>
              ))}
            </Select.Menu>
          )}
        </Select>
      </Box>
    </>
  );
};

export default Demo;

Released under the MIT License.

Released under the MIT License.