Combobox Component 
The Combobox component provides a powerful searchable select input with autocomplete functionality built with Vue 3 and Headless UI. It supports asynchronous data loading, single/multiple selection, custom rendering, and various customization options.
Installation 
To use the Combobox component you need to install the @vueuse/core and @headlessui/vue packages.
yarn add @robuust-digital/vue-components @vueuse/core @headlessui/vueImport the component from the combobox package 
import { Combobox } from '@robuust-digital/vue-components/combobox';Import CSS 
@import "@robuust-digital/vue-components/combobox/css";Basic Usage 
<template>
  <Combobox
    v-model="single"
    id="basic-example"
    :on-search="mockSearch"
    placeholder="Search fruits..."
  />
</template>API Integration Example 
Real-world example using JSONPlaceholder API to search users:
<template>
  <Combobox
    v-model="apiUsers"
    id="api-example"
    endpoint="users"
    :on-search="comboboxSearch"
    :display-value="customDisplayValue"
    :option-text="customOptionText"
    placeholder="Search users..."
    clearable
  />
</template>
<script setup>
/**
 * API search example
 * 
 * @param {String} searchValue - Search query
 * @param {Object} params - Additional request params
 * @param {String} endpoint - API endpoint
 */
const comboboxSearch = async (searchValue, params, endpoint) => {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/${endpoint}${searchValue ? `?username=${searchValue}` : ''}`
  );
  if (response.ok) {
    const data = await response.json();
    return { data };
  }
  return { data: [], error: 'An error occurred' };
};
</script>Props 
| Prop | Type | Default | Description | 
|---|---|---|---|
| id | String | Required | Unique identifier for the combobox | 
| modelValue | Any | null | v-model binding value | 
| endpoint | String | undefined | API endpoint for search requests | 
| requestParams | Object | {} | Additional params for API requests | 
| manualInput | Boolean | false | Allow manual input without selection | 
| responseData | Function | data => data | Transform API response data | 
| displayValue | Function | item => item?.name | Format selected value display | 
| optionText | Function | item => item?.name | Format option text in dropdown | 
| disabled | Boolean | false | Disable the combobox | 
| minLength | Number | 2 | Minimum characters before search | 
| itemKey | String | 'id' | Unique key for options | 
| clearable | Boolean | false | Show clear button | 
| multiple | Boolean | false | Enable multiple selection | 
| searchOnly | Boolean | false | Search-only mode without selection | 
| icon | Object,Function | null | Custom icon component | 
| prefixIcon | Object,Function | null | Custom left icon component | 
| size | String | 'base' | Size variant ( 'sm'or'base') | 
| debounce | Number | 150 | Debounce time for search in milliseconds | 
| minLoadingTime | Number | 0 | Minimum loading indicator display time | 
| onCancel | Function | null | Callback to cancel pending requests | 
Events 
| Event | Payload | Description | 
|---|---|---|
| update:modelValue | Any | Emitted when selection changes | 
| update:requestParams | Object | Emitted when request params change | 
| combobox:noResults | String | Emitted when search returns no results | 
| combobox:error | Object,String | Emitted when search encounters an error | 
Advanced Examples 
Multiple Selection with Custom Display 
<template>
  <Combobox
    v-model="multiple"
    id="multiple-custom"
    :on-search="comboboxSearch"
    endpoint="users"
    multiple
    clearable
    :display-value="customDisplayValue"
    :option-text="customOptionText"
    placeholder="Select multiple users..."
  >
    <template #chip="{ optionText, option, removeOption }">
      <Badge color="blue" size="sm">
        {{ optionText }}
        <button @click="removeOption(option)">×</button>
      </Badge>
    </template>
  </Combobox>
</template>Custom Option Rendering 
<template>
  <Combobox
    v-model="single"
    id="custom-option"
    :on-search="comboboxSearch"
    endpoint="users"
    clearable
  >
    <template #option="{ option }">
      <div class="flex flex-col">
        <strong>{{ option.username }}</strong>
        <small class="text-slate-500">{{ option.email }}</small>
      </div>
    </template>
  </Combobox>
</template>Request Cancellation 
Example showing how to cancel pending requests when a new search is initiated:
<template>
  <Combobox
    v-model="single"
    id="cancel-example"
    :on-search="searchWithCancel"
    :on-cancel="(controller) => controller.abort()"
    placeholder="Search with cancellation..."
  />
</template>
<script setup>
/**
 * API search example with request cancellation
 * 
 * @param {String} searchValue - Search query
 * @param {Object} params - Additional request params
 * @param {String} endpoint - API endpoint
 */
const searchWithCancel = async (searchValue, params, endpoint) => {
  const controller = new AbortController();
  const { signal } = controller;
  const response = await fetch(`https://jsonplaceholder.typicode.com/${endpoint}${searchValue ? `?username=${searchValue}` : ''}`, { signal });
  const data = await response.json();
  return {
    data,
    cancel: controller,
  };
};
</script>This example demonstrates how to implement request cancellation when a new search is initiated before the previous one completes.
Slots 
| Slot | Props | Description | 
|---|---|---|
| prefixIcon | { icon } | Custom left icon | 
| icon | { icon } | Custom right icon | 
| spinner | { spinning } | Custom loading indicator | 
| clear | - | Custom clear button | 
| chip | { optionText, option, removeOption } | Custom chip for multiple selection | 
| option | { option, isActive } | Custom option rendering | 
| optionSuffix | { option, isActive } | Additional option metadata | 
| optionPrefix | { option, isActive } | Prefix metadata for options | 
Improved Documentation
We are planning to provide more examples and advanced use cases for the Combobox component. Stay tuned!
CSS Customization ⚡️ 
To customize the combobox styles global
:root {
  /* Available variables */
  --rvc-combobox-border-radius: var(--rvc-base-border-radius);
  --rvc-combobox-border-width: var(--rvc-base-border-width);
  --rvc-combobox-border-color: var(--rvc-base-border-color);
  --rvc-combobox-font-size: var(--rvc-base-input-font-size);
  --rvc-combobox-font-weight: var(--rvc-base-input-font-weight);
  --rvc-combobox-box-shadow: var(--rvc-base-box-shadow);
  --rvc-combobox-color: var(--rvc-base-input-color);
  --rvc-combobox-bg-color: var(--rvc-base-input-bg-color);
  --rvc-combobox-bg-color-disabled: var(--rvc-base-input-bg-color-disabled);
  --rvc-combobox-disabled-opacity: var(--rvc-base-input-disabled-opacity);
  --rvc-combobox-padding-x: var(--rvc-base-input-padding-x);
  --rvc-combobox-height: var(--rvc-base-input-height);
  --rvc-combobox-icon-size: var(--rvc-base-input-icon-size);
  --rvc-combobox-icon-color: var(--rvc-base-input-icon-color);
  --rvc-combobox-icon-loading-animation: var(--rvc-base-loading-animation);
  --rvc-combobox-placeholder-color: var(--rvc-base-input-placeholder-color);
  --rvc-combobox-clear-color: var(--rvc-base-input-icon-color);
  --rvc-combobox-clear-color-hover: var(--color-slate-700);
  --rvc-combobox-chip-icon-size: calc(var(--spacing) * 4);
  --rvc-combobox-chip-color: var(--rvc-base-input-color);
  --rvc-combobox-chip-color-hover: var(--color-slate-700);
  --rvc-combobox-chips-spacing: calc(var(--spacing) * 2);
  --rvc-combobox-options-offset: calc(var(--spacing) * 1);
  --rvc-combobox-options-z-index: 10;
  --rvc-combobox-options-max-height: calc(var(--spacing) * 60);
  --rvc-combobox-options-padding-x: 0.1875rem;
  --rvc-combobox-options-padding-y: 0.1875rem;
  --rvc-combobox-option-padding-x: calc(var(--spacing) * 2);
  --rvc-combobox-option-padding-y: calc(var(--spacing) * 1.5);
  --rvc-combobox-option-bg-color-hover: var(--color-slate-100);
  --rvc-combobox-option-bg-color-active: transparent;
  --rvc-combobox-option-color-active: var(--rvc-base-input-color);
  --rvc-combobox-option-font-weight-active: var(--font-weight-semibold);
  /* Small variant */
  --rvc-combobox-height-sm: 1.875rem;
  --rvc-combobox-font-size-sm: calc(var(--rvc-base-input-font-size) * 0.9);
  --rvc-combobox-padding-x-sm: calc(var(--rvc-base-input-padding-x) * 0.85);
  --rvc-combobox-icon-size-sm: calc(var(--rvc-base-input-icon-size) * 0.85);
}Roadmap 
- Add more examples and advanced use cases
- Improve Docs with more detailed explanations
- Implement infinite loading for large datasets