> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Combobox API Updates and Bug Fixes

export const BlogMeta = ({date, author, tags}) => {
  const formatList = items => {
    if (!Array.isArray(items)) return items;
    if (items.length === 1) return items[0];
    if (items.length === 2) return `${items[0]} & ${items[1]}`;
    return `${items.slice(0, -1).join(", ")}, & ${items[items.length - 1]}`;
  };
  return <div className="flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
      <span>{date}</span>
      <span>|</span>
      <span>{formatList(author)}</span>
      {tags && tags.length > 0 && <>
          <span>|</span>
          <div className="flex flex-wrap gap-1.5">
            {tags.map(tag => <span key={tag} className="px-2 py-1 rounded bg-gray-100 dark:bg-neutral-800 text-xs">
                {tag}
              </span>)}
          </div>
        </>}
    </div>;
};

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

export const CodePreviewPlaceholder = ({double, fullWidth}) => {
  const single = <div style={{
    width: fullWidth ? "100%" : "50%",
    borderRadius: "1rem",
    display: "flex",
    padding: "1rem",
    flexDirection: "column",
    gap: "0.5rem",
    height: "10rem",
    marginBlockEnd: "1rem"
  }} className="border-width-default border-color-subdued">
      <div className="bg-strong border-radius-large" style={{
    width: "100%",
    flexGrow: "1"
  }} />
      <div className="bg-strong border-radius-large" style={{
    width: "100%",
    flexGrow: "1"
  }} />
    </div>;
  return double ? <div style={{
    display: "flex",
    gap: "1rem"
  }}>
      {single}
      {single}
    </div> : single;
};

<BlogMeta date="December 17, 2024" author="Ryan De La Torre" tags={["Engineering", "New Features", "Web"]} />

In case you missed it, as of version `1.16.8` of Anvil2, the `Combobox` has received quite a number of highly requested features and fixes!

### A new select-only version of the Combobox

The new `Combobox.Select` component allows you to create a version of the `Combobox` that the user doesn't type into, much like a native HTML `select` element. If you use this component, you should also use it with the `Combobox.SelectTrigger` child component (instead of `Combobox.SearchField`).

Most of the props are very similar between the regular `Combobox` and the `Combobox.Select` variant, though there are some small differences to be aware of. For instance, the `Combobox.Select` variant has no props regarding `inputValue` since it's not an input field.

<CodePreviewPlaceholder fullWidth />

### The Combobox popover can be kept open when selecting multiple items

The new `disableCloseOnSelectItem` enables the `Combobox` to remain open after a user makes a selection. If you use this prop, we recommend pairing it with `disableClearInputValueOnSelectItem` to preserve the user's search term as well.

<CodePreviewPlaceholder fullWidth />

### The Combobox popover can be a different size from the search field

The new `disableMatchReferenceWidth` allows the Combobox dropdown to conform to the width of the item content instead of matching the width of the input.

<CodePreviewPlaceholder fullWidth />

### Combobox items can be compared by ID (instead of by reference)

The `Combobox` defaults to comparing items by reference equality, but if you need your items to be compared by something like an `id`, you can enable this using the `itemToKey` prop:

<CodePreviewPlaceholder fullWidth />

### Update combobox items dynamically

Dynamically updating the items array now works as expected. This is useful, for example, if you wanted to asynchronously load in new items:

<CodePreviewPlaceholder fullWidth />

### Use flex properties with combobox items

The `Combobox.Item` component now works somewhat like a `Flex` component, so if you want to control the spacing in your `Combobox.Item` component, you can now directly use props like `justifyContent` and `alignItems`:

<CodePreviewPlaceholder fullWidth />

### Set max rows greater than one in the search field

<CodePreviewPlaceholder fullWidth />

We’ve been working hard to fix bugs, such as making sure Combobox works smoothly with TypeScript `enum`s and inside shadow DOMs. And stay tuned—exciting new Combobox features are on the way, including the ability to add items that aren’t in the search, infinite loading, and integration with paginated backend endpoints.

Please continue to [share your feedback](/docs/about-anvil2#getting-help-and-sharing-feedback) and use cases so we can continue to enhance the Combobox!"
