or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdmulti-select.mdomnibar.mdquery-list.mdselect.mdsuggest.mdutilities.md
tile.json

multi-select.mddocs/

Multiple Item Selection

The MultiSelect component allows users to choose multiple items from a list, displaying selected items as removable tags.

Capabilities

MultiSelect Component

React component for selecting multiple items with tag-based display.

/**
 * MultiSelect component for choosing multiple items with tag display
 * @template T - Type of items in the list
 */
class MultiSelect<T> extends React.Component<MultiSelectProps<T>, MultiSelectState> {
  /** Generic factory method for type inference */
  static ofType<U>(): new (props: MultiSelectProps<U>) => MultiSelect<U>;
  
  /** Reference to the tag input element */
  input: HTMLInputElement | null;
  /** Reference to the internal QueryList component */
  queryList: QueryList<T> | null;
}

interface MultiSelectProps<T> extends ListItemsProps<T>, SelectPopoverProps {
  /** Array of currently selected items */
  selectedItems: T[];
  /** Function to render each selected item as a tag */
  tagRenderer: (item: T) => React.ReactNode;
  /** Custom render function for the trigger element. Providing this moves search functionality to the popover */
  customTarget?: (selectedItems: T[], isOpen: boolean) => React.ReactNode;
  /** Whether the multi-select is disabled */
  disabled?: boolean;
  /** Whether the multi-select should fill its container */
  fill?: boolean;
  /** Props for the dropdown menu container */
  menuProps?: React.HTMLAttributes<HTMLUListElement>;
  /** Callback when all items are cleared */
  onClear?: () => void;
  /** Callback when an item tag is removed */
  onRemove?: (value: T, index: number) => void;
  /** Whether to open dropdown on keydown in the input (ignored if customTarget is provided) */
  openOnKeyDown?: boolean;
  /** Placeholder text for the input */
  placeholder?: string;
  /** Props passed to the internal TagInput component with some limitations */
  tagInputProps?: Partial<Omit<TagInputProps, "inputValue" | "onInputChange">>;
}

interface MultiSelectState {
  /** Whether the dropdown is currently open */
  isOpen: boolean;
}

Usage Examples:

import React, { useState } from "react";
import { MultiSelect, ItemRenderer } from "@blueprintjs/select";
import { MenuItem, Tag } from "@blueprintjs/core";

interface Technology {
  name: string;
  category: string;
  color: string;
}

const technologies: Technology[] = [
  { name: "React", category: "Frontend", color: "#61DAFB" },
  { name: "TypeScript", category: "Language", color: "#3178C6" },
  { name: "Node.js", category: "Backend", color: "#339933" },
  { name: "Python", category: "Language", color: "#3776AB" },
  { name: "PostgreSQL", category: "Database", color: "#336791" },
];

const renderTechnology: ItemRenderer<Technology> = (tech, { handleClick, modifiers }) => (
  <MenuItem
    key={tech.name}
    text={tech.name}
    label={tech.category}
    onClick={handleClick}
    active={modifiers.active}
    disabled={modifiers.disabled}
  />
);

const renderTechTag = (tech: Technology) => (
  <Tag style={{ backgroundColor: tech.color, color: "white" }}>
    {tech.name}
  </Tag>
);

const TechnologyMultiSelect = () => {
  const [selectedTechs, setSelectedTechs] = useState<Technology[]>([]);

  const handleItemSelect = (tech: Technology) => {
    if (!selectedTechs.find(t => t.name === tech.name)) {
      setSelectedTechs([...selectedTechs, tech]);
    }
  };

  const handleRemove = (techToRemove: Technology, index: number) => {
    setSelectedTechs(selectedTechs.filter((_, i) => i !== index));
  };

  return (
    <MultiSelect<Technology>
      items={technologies}
      selectedItems={selectedTechs}
      itemRenderer={renderTechnology}
      tagRenderer={renderTechTag}
      onItemSelect={handleItemSelect}
      onRemove={handleRemove}
      onClear={() => setSelectedTechs([])}
      placeholder="Select technologies..."
      noResults={<MenuItem disabled text="No technologies found." />}
      resetOnSelect
    />
  );
};

// With custom filtering to exclude selected items
const getFilteredTechnologies = (query: string, selectedTechs: Technology[]) => {
  const selectedNames = new Set(selectedTechs.map(t => t.name));
  
  return technologies
    .filter(tech => !selectedNames.has(tech.name))
    .filter(tech => 
      tech.name.toLowerCase().includes(query.toLowerCase()) ||
      tech.category.toLowerCase().includes(query.toLowerCase())
    );
};

const FilteredTechnologyMultiSelect = () => {
  const [selectedTechs, setSelectedTechs] = useState<Technology[]>([]);

  return (
    <MultiSelect<Technology>
      items={technologies}
      selectedItems={selectedTechs}
      itemRenderer={renderTechnology}
      tagRenderer={renderTechTag}
      itemListPredicate={(query) => getFilteredTechnologies(query, selectedTechs)}
      onItemSelect={(tech) => setSelectedTechs([...selectedTechs, tech])}
      onRemove={(_, index) => setSelectedTechs(selectedTechs.filter((_, i) => i !== index))}
      placeholder="Select technologies..."
    />
  );
};

Tag Customization

MultiSelect provides extensive customization options for tag display and behavior.

// Tag renderer function type
type TagRenderer<T> = (item: T) => React.ReactNode;

// TagInput props (subset available to MultiSelect)
interface TagInputProps {
  /** Whether tags can be added by typing */
  addOnBlur?: boolean;
  /** Whether tags can be added by pasting */
  addOnPaste?: boolean;
  /** Whether the input should fill its container */
  fill?: boolean;
  /** Props for the input element */
  inputProps?: React.HTMLAttributes<HTMLInputElement>;
  /** Whether the input is disabled */
  disabled?: boolean;
  /** Large size variant */
  large?: boolean;
  /** Callback when input loses focus */
  onInputChange?: React.ChangeEventHandler<HTMLInputElement>;
  /** Callback when a tag is removed */
  onRemove?: (value: string, index: number) => void;
  /** Placeholder text */
  placeholder?: string;
  /** Whether tags should have a remove button */
  tagProps?: TagProps;
  /** Array of tag values */
  values: React.ReactNode[];
}

Usage Examples:

import { MultiSelect } from "@blueprintjs/select";
import { Tag, Intent } from "@blueprintjs/core";

// Custom tag renderer with different intents
const renderPriorityTag = (item: { name: string; priority: "high" | "medium" | "low" }) => {
  const intent = {
    high: Intent.DANGER,
    medium: Intent.WARNING,
    low: Intent.SUCCESS,
  }[item.priority];

  return (
    <Tag intent={intent} minimal>
      {item.name}
    </Tag>
  );
};

// With custom TagInput props
const CustomTagInputMultiSelect = () => (
  <MultiSelect<string>
    items={["Apple", "Banana", "Cherry", "Date"]}
    selectedItems={[]}
    itemRenderer={(item, { handleClick, modifiers }) => (
      <div onClick={handleClick}>{item}</div>
    )}
    tagRenderer={(item) => <Tag>{item}</Tag>}
    onItemSelect={() => {}}
    tagInputProps={{
      large: true,
      fill: true,
      placeholder: "Type to search...",
      tagProps: {
        minimal: true,
        intent: Intent.PRIMARY,
      },
    }}
  />
);

Create New Items

MultiSelect supports creating new items from user input.

// From ListItemsProps
interface ListItemsProps<T> {
  /** Function to create new items from query string */
  createNewItemFromQuery?: (query: string) => T | T[];
  /** Function to render the "create new item" option */
  createNewItemRenderer?: (
    query: string,
    active: boolean,
    handleClick: React.MouseEventHandler<HTMLElement>,
  ) => React.JSX.Element | undefined;
  /** Where to position the create new item option */
  createNewItemPosition?: "first" | "last";
}

Usage Examples:

const CreatableMultiSelect = () => {
  const [tags, setTags] = useState<string[]>(["existing-tag"]);
  const [selectedTags, setSelectedTags] = useState<string[]>([]);

  const createNewTag = (query: string): string => {
    return query.trim();
  };

  const renderCreateTagOption = (query: string, active: boolean, handleClick: React.MouseEventHandler) => (
    <MenuItem
      icon="add"
      text={`Create "${query}"`}
      active={active}
      onClick={handleClick}
      shouldDismissPopover={false}
    />
  );

  return (
    <MultiSelect<string>
      items={tags}
      selectedItems={selectedTags}
      itemRenderer={(tag, { handleClick, modifiers }) => (
        <MenuItem
          text={tag}
          onClick={handleClick}
          active={modifiers.active}
        />
      )}
      tagRenderer={(tag) => <Tag>{tag}</Tag>}
      onItemSelect={(tag) => {
        if (!selectedTags.includes(tag)) {
          setSelectedTags([...selectedTags, tag]);
        }
      }}
      createNewItemFromQuery={createNewTag}
      createNewItemRenderer={renderCreateTagOption}
      createNewItemPosition="first"
      noResults={<MenuItem disabled text="Type to create a new tag..." />}
    />
  );
};

Custom Target Rendering

MultiSelect allows complete customization of the input area display.

interface MultiSelectProps<T> {
  /** Custom render function for the entire input area */
  customTarget?: (selectedItems: T[], isOpen: boolean) => React.ReactNode;
}

Usage Examples:

import { MultiSelect } from "@blueprintjs/select";
import { Button, Tag } from "@blueprintjs/core";

const CustomTargetMultiSelect = () => {
  const [selectedItems, setSelectedItems] = useState<string[]>([]);

  const customTarget = (items: string[], isOpen: boolean) => (
    <Button
      text={items.length > 0 ? `${items.length} items selected` : "Select items..."}
      rightIcon={isOpen ? "caret-up" : "caret-down"}
      active={isOpen}
      style={{ minWidth: "200px", justifyContent: "space-between" }}
    />
  );

  return (
    <MultiSelect<string>
      items={["Option 1", "Option 2", "Option 3", "Option 4"]}
      selectedItems={selectedItems}
      itemRenderer={(item, { handleClick, modifiers }) => (
        <div onClick={handleClick} style={{ padding: "8px" }}>
          {item}
        </div>
      )}
      tagRenderer={(item) => <Tag>{item}</Tag>}
      onItemSelect={(item) => setSelectedItems([...selectedItems, item])}
      customTarget={customTarget}
    />
  );
};