# Autocomplete

## Introduction

Autocomplete is an enhanced input component that provides real-time suggestions as users type, helping them quickly find and select values from a predefined list. It combines the flexibility of a text input with the convenience of a dropdown selection, supporting features like filtering, keyboard navigation, custom option rendering, and grouping. Autocomplete is ideal for scenarios where users need to select from a large set of options, such as searching for countries, products, or users.

```tsx
<Autocomplete options={['Option1', 'Option2']} />
```

| Field   | Description | Default |
| ------- | ----------- | ------- |
| label   | —           | —       |
| loading | —           | —       |

> ⚠️ **Usage Warning** ⚠️
>
> Choose the right input component for your use case:
>
> - **Autocomplete**: For searchable selection from large option lists (20+ items)
> - **Select**: For simple selection from small lists (under 20 items)
> - **Dropdown**: For action menus, not form value selection
> - **Input**: For free-text entry without predefined options

> 💡 **Use built-in form props**
>
> This component natively supports form elements such as `label` and `helperText` props.
> When building forms, use these built-in props instead of manually composing labels and helper text with Typography.

## Usage

```tsx
import { Autocomplete } from '@ceed/ads';

function CountrySelector() {
  const [country, setCountry] = useState<string | undefined>();

  return (
    <Autocomplete
      label="Country"
      placeholder="Search countries..."
      options={['United States', 'United Kingdom', 'Canada', 'Australia']}
      value={country}
      onChange={(e) => setCountry(e.target.value)}
    />
  );
}
```

## Examples

### Playground

Interactive example with basic string options.

```tsx
<Autocomplete options={['Option1', 'Option2']} />
```

### Sizes

Available size options: `sm`, `md`, `lg`.

```tsx
<div style={{
  display: 'flex',
  gap: '10px'
}}>
<Autocomplete {...args} size="sm" />
<Autocomplete {...args} size="md" />
<Autocomplete {...args} size="lg" />
</div>
```

### Option Groups

Group options into categories using the `groupBy` function.

```tsx
<Autocomplete
  options={[{
  value: "Diaper: Johnson's baby",
  label: "Johnson's baby",
  startDecorator: <Chip color="success">Active</Chip>
}, {
  value: 'Diaper: Haggis Magic',
  label: 'Haggis Magic',
  startDecorator: <Chip color="danger">Terminated</Chip>
}, {
  value: 'Wiki: Wiki How',
  label: 'Wiki How',
  startDecorator: <Chip color="success">Active</Chip>
}, {
  value: 'Wiki: Wikipedia',
  label: 'Wikipedia',
  startDecorator: <Chip color="danger">Terminated</Chip>
}]}
  groupBy={(option: any) => option.value.split(':')[0]}
  defaultValue="Wiki: Wiki How"
/>
```

### Custom Options

Options can include decorators for rich content display.

```tsx
<Autocomplete
  options={[{
  value: "Johnson's baby",
  label: "Johnson's baby",
  startDecorator: <Chip color="success">Active</Chip>
  // endDecorator: <Chip color='neutral'>inbound</Chip>,
}, {
  value: 'Haggis Magic',
  label: 'Haggis Magic',
  startDecorator: <Chip color="danger">Terminated</Chip>
  // endDecorator: <Chip color='neutral'>outbound</Chip>,
}]}
  value="Johnson's baby"
/>
```

### Loading State

Show a loading indicator while fetching options.

```tsx
<Autocomplete
  options={[]}
  loading
/>
```

### Controlled

Manage value externally with controlled state.

```tsx
<Stack gap={4}>
  <Autocomplete value={value} label="Select a brand" options={[{
    value: "Johnson's baby",
    label: "Johnson's baby",
    startDecorator: <Chip color="success">Active</Chip>,
    endDecorator: <Chip color="neutral">inbound</Chip>
  }, {
    value: 'Haggis Magic',
    label: 'Haggis Magic',
    startDecorator: <Chip color="danger">Terminated</Chip>,
    endDecorator: <Chip color="neutral">outbound</Chip>
  }, {
    value: 'Hogwarts Magic',
    label: 'Hogwarts Magic',
    startDecorator: <Chip color="danger">Magic</Chip>,
    endDecorator: <Chip color="neutral">Muggle</Chip>
  }]} onChange={e => setValue(e.target.value)} />
  <Button onClick={() => setValue("Johnson's baby")}>Set Johnson's baby</Button>
</Stack>
```

## When to Use

### ✅ Good Use Cases

- **Large option lists**: When users need to search through 20+ options
- **Dynamic data**: When options come from an API or database
- **User search**: Finding users, contacts, or entities by name
- **Location selection**: Countries, cities, addresses
- **Product search**: Searching product catalogs
- **Tag/category selection**: When users can search for categories
- **Form fields with many choices**: When a simple Select would be overwhelming

### ❌ When Not to Use

- **Few options (\< 10)**: Use Select or RadioGroup instead
- **Free text entry**: Use Input if no predefined options exist
- **Action menus**: Use Dropdown or MenuButton for actions
- **Yes/No choices**: Use Switch or Checkbox
- **Numeric ranges**: Use Slider or NumberInput
- **Date selection**: Use DatePicker

## Common Use Cases

### Country Selector

```tsx
function CountrySelector({ value, onChange }) {
  const countries = [
    { value: 'us', label: 'United States' },
    { value: 'uk', label: 'United Kingdom' },
    { value: 'ca', label: 'Canada' },
    { value: 'au', label: 'Australia' },
    { value: 'de', label: 'Germany' },
    { value: 'fr', label: 'France' },
    { value: 'jp', label: 'Japan' },
    { value: 'kr', label: 'South Korea' },
  ];

  return (
    <Autocomplete
      label="Country"
      placeholder="Select a country..."
      options={countries}
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}
```

### User Search

```tsx
function UserSearch({ onSelect }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [search, setSearch] = useState('');

  useEffect(() => {
    if (!search) {
      setUsers([]);
      return;
    }

    const fetchUsers = async () => {
      setLoading(true);
      try {
        const response = await api.searchUsers(search);
        setUsers(
          response.data.map((user) => ({
            value: user.id,
            label: user.name,
            secondaryText: user.email,
            startDecorator: <Avatar src={user.avatar} size="sm" />,
          }))
        );
      } finally {
        setLoading(false);
      }
    };

    const debounce = setTimeout(fetchUsers, 300);
    return () => clearTimeout(debounce);
  }, [search]);

  return (
    <Autocomplete
      label="Search Users"
      placeholder="Type to search..."
      options={users}
      loading={loading}
      onInputChange={(e) => setSearch(e.target.value)}
      onChange={(e) => onSelect(e.target.value)}
    />
  );
}
```

### Product Search with Categories

```tsx
function ProductSearch({ products, onSelect }) {
  const productOptions = products.map((product) => ({
    value: product.id,
    label: product.name,
    startDecorator: (
      <Chip size="sm" color={product.inStock ? 'success' : 'neutral'}>
        {product.inStock ? 'In Stock' : 'Out of Stock'}
      </Chip>
    ),
    endDecorator: (
      <Typography level="body-sm" color="neutral">
        ${product.price}
      </Typography>
    ),
  }));

  return (
    <Autocomplete
      label="Search Products"
      placeholder="Enter product name..."
      options={productOptions}
      groupBy={(option) => option.category}
      onChange={(e) => onSelect(e.target.value)}
    />
  );
}
```

### Address Autocomplete

```tsx
function AddressAutocomplete({ onAddressSelect }) {
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);

  const handleInputChange = async (e) => {
    const query = e.target.value;
    if (query.length < 3) {
      setOptions([]);
      return;
    }

    setLoading(true);
    try {
      const results = await geocodingApi.search(query);
      setOptions(
        results.map((result) => ({
          value: result.placeId,
          label: result.formattedAddress,
          secondaryText: result.city + ', ' + result.country,
        }))
      );
    } finally {
      setLoading(false);
    }
  };

  return (
    <Autocomplete
      label="Address"
      placeholder="Start typing an address..."
      options={options}
      loading={loading}
      onInputChange={handleInputChange}
      onChange={(e) => {
        const selected = options.find((opt) => opt.value === e.target.value);
        onAddressSelect(selected);
      }}
    />
  );
}
```

### Tag Selection (Multiple)

```tsx
function TagSelector({ availableTags, selectedTags, onChange }) {
  const tagOptions = availableTags.map((tag) => ({
    value: tag.id,
    label: tag.name,
    startDecorator: (
      <Box
        sx={{
          width: 12,
          height: 12,
          borderRadius: '50%',
          bgcolor: tag.color,
        }}
      />
    ),
  }));

  return (
    <Autocomplete
      label="Tags"
      placeholder="Add tags..."
      options={tagOptions}
      value={selectedTags}
      onChange={(e) => onChange(e.target.value)}
      multiple
    />
  );
}
```

### Grouped Options

```tsx
function CategorySelector() {
  const items = [
    { value: 'electronics-phone', label: 'Smartphones', category: 'Electronics' },
    { value: 'electronics-laptop', label: 'Laptops', category: 'Electronics' },
    { value: 'electronics-tablet', label: 'Tablets', category: 'Electronics' },
    { value: 'clothing-shirt', label: 'Shirts', category: 'Clothing' },
    { value: 'clothing-pants', label: 'Pants', category: 'Clothing' },
    { value: 'clothing-shoes', label: 'Shoes', category: 'Clothing' },
    { value: 'home-furniture', label: 'Furniture', category: 'Home' },
    { value: 'home-decor', label: 'Decor', category: 'Home' },
  ];

  return (
    <Autocomplete
      label="Category"
      placeholder="Select a category..."
      options={items}
      groupBy={(option) => option.category}
    />
  );
}
```

### With Secondary Text

```tsx
function ContactSelector() {
  const contacts = [
    { value: 'emily', label: 'Emily Carter', secondaryText: '(415) 555-0198' },
    { value: 'daniel', label: 'Daniel Kim', secondaryText: '(212) 555-0421' },
    { value: 'sophia', label: 'Sophia Martinez', secondaryText: '(646) 555-0734' },
    { value: 'michael', label: 'Michael Chen', secondaryText: '(646) 555-0876' },
  ];

  return (
    <Autocomplete
      label="Contact"
      placeholder="Search contacts..."
      options={contacts}
    />
  );
}
```

### Lazy Loading Options

```tsx
function LazyAutocomplete({ fetchOptions }) {
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [hasLoaded, setHasLoaded] = useState(false);

  const handleFocus = async () => {
    if (hasLoaded) return;

    setLoading(true);
    try {
      const data = await fetchOptions();
      setOptions(data);
      setHasLoaded(true);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Autocomplete
      label="Select Option"
      placeholder="Click to load options..."
      options={options}
      loading={loading}
      onFocus={handleFocus}
    />
  );
}
```

## Props and Customization

### Key Props

| Prop            | Type                                     | Default | Description                           |
| --------------- | ---------------------------------------- | ------- | ------------------------------------- |
| `options`       | `string[] \| OptionObject[]`             | `[]`    | Array of options (strings or objects) |
| `value`         | `string \| string[]`                     | -       | Selected value(s) for controlled mode |
| `defaultValue`  | `string \| string[]`                     | -       | Initial value for uncontrolled mode   |
| `onChange`      | `(event: { target: { value } }) => void` | -       | Callback when selection changes       |
| `onInputChange` | `(event: { target: { value } }) => void` | -       | Callback when input text changes      |
| `label`         | `string`                                 | -       | Label text above the input            |
| `placeholder`   | `string`                                 | -       | Placeholder text when empty           |
| `loading`       | `boolean`                                | `false` | Show loading indicator                |
| `multiple`      | `boolean`                                | `false` | Allow multiple selections             |
| `groupBy`       | `(option) => string`                     | -       | Function to group options             |
| `size`          | `'sm' \| 'md' \| 'lg'`                   | `'md'`  | Input size                            |
| `disabled`      | `boolean`                                | `false` | Disable the input                     |

### Option Object Structure

```tsx
interface OptionObject {
  value: string;              // Unique value identifier
  label: string;              // Display text
  secondaryText?: string;     // Secondary line of text
  startDecorator?: ReactNode; // Content before label
  endDecorator?: ReactNode;   // Content after label
}

// Example option objects
const options = [
  {
    value: 'user-1',
    label: 'John Doe',
    secondaryText: 'john@example.com',
    startDecorator: <Avatar src="/john.jpg" size="sm" />,
    endDecorator: <Chip size="sm">Admin</Chip>,
  },
  {
    value: 'user-2',
    label: 'Jane Smith',
    secondaryText: 'jane@example.com',
    startDecorator: <Avatar src="/jane.jpg" size="sm" />,
  },
];
```

### Simple String Options

```tsx
// Simplest usage with string array
<Autocomplete
  label="Fruit"
  options={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']}
/>
```

### Controlled vs Uncontrolled

```tsx
// Controlled - manage value externally
function ControlledExample() {
  const [value, setValue] = useState<string | undefined>();

  return (
    <Autocomplete
      value={value}
      onChange={(e) => setValue(e.target.value)}
      options={options}
    />
  );
}

// Uncontrolled - internal state management
function UncontrolledExample() {
  return (
    <Autocomplete
      defaultValue="option-1"
      options={options}
    />
  );
}
```

### Size Options

```tsx
// Small
<Autocomplete options={options} size="sm" />

// Medium (default)
<Autocomplete options={options} size="md" />

// Large
<Autocomplete options={options} size="lg" />
```

### With Grouping

```tsx
<Autocomplete
  options={[
    { value: 'a1', label: 'Apple', category: 'Fruits' },
    { value: 'b1', label: 'Banana', category: 'Fruits' },
    { value: 'c1', label: 'Carrot', category: 'Vegetables' },
    { value: 'b2', label: 'Broccoli', category: 'Vegetables' },
  ]}
  groupBy={(option) => option.category}
/>
```

### Multiple Selection

```tsx
<Autocomplete
  multiple
  value={['option-1', 'option-2']}
  options={options}
  onChange={(e) => setValues(e.target.value)}
/>
```

## Accessibility

Autocomplete includes comprehensive accessibility features:

### ARIA Attributes

- Input has `role="combobox"` with `aria-autocomplete="list"`
- Listbox has `role="listbox"` with proper `aria-label`
- Options have `role="option"` with `aria-selected` state
- Groups are announced with proper hierarchy

### Keyboard Navigation

- **Arrow Down**: Open dropdown / move to next option
- **Arrow Up**: Move to previous option
- **Enter**: Select focused option
- **Escape**: Close dropdown
- **Tab**: Move focus out of component
- **Home**: Jump to first option
- **End**: Jump to last option
- **Type ahead**: Filter options by typing

### Screen Reader Support

```tsx
// Proper labeling for screen readers
<Autocomplete
  label="Select a country"  // Announces: "Select a country, combobox"
  placeholder="Search..."
  options={countries}
/>

// Options announce: "United States, option, 1 of 10"
```

### Focus Management

- Focus automatically moves to input when dropdown opens
- Focus returns to input when selecting an option
- Clear visual focus indicators on all interactive elements

## Best Practices

### ✅ Do

1. **Provide helpful labels and placeholders**: Guide users on what to search

```tsx
// ✅ Good: Clear label and placeholder
<Autocomplete
  label="Shipping Country"
  placeholder="Type to search countries..."
  options={countries}
/>
```

2. **Show loading state during async operations**: Keep users informed

```tsx
// ✅ Good: Loading indicator while fetching
<Autocomplete
  options={options}
  loading={isLoading}
  placeholder={isLoading ? 'Loading...' : 'Search...'}
/>
```

3. **Use grouping for better organization**: Help users scan large lists

```tsx
// ✅ Good: Logical grouping
<Autocomplete
  options={allProducts}
  groupBy={(product) => product.category}
/>
```

4. **Debounce API searches**: Prevent excessive requests

```tsx
// ✅ Good: Debounced search
useEffect(() => {
  const timer = setTimeout(() => fetchResults(query), 300);
  return () => clearTimeout(timer);
}, [query]);
```

### ❌ Don't

1. **Don't use for small option sets**: Use Select instead

```tsx
// ❌ Bad: Too few options for Autocomplete
<Autocomplete options={['Yes', 'No']} />

// ✅ Good: Use Select or RadioGroup
<Select>
  <Option value="yes">Yes</Option>
  <Option value="no">No</Option>
</Select>
```

2. **Don't hide important selection context**: Show enough information

```tsx
// ❌ Bad: Not enough context
<Autocomplete
  options={users.map((u) => ({ value: u.id, label: u.name }))}
/>

// ✅ Good: Include helpful secondary info
<Autocomplete
  options={users.map((u) => ({
    value: u.id,
    label: u.name,
    secondaryText: u.email,
  }))}
/>
```

3. **Don't forget empty states**: Handle no results gracefully

```tsx
// ✅ Good: Handle empty results
<Autocomplete
  options={filteredOptions}
  noOptionsText="No matches found. Try a different search."
/>
```

4. **Don't block interaction during initial load**: Show placeholders

```tsx
// ❌ Bad: Blocking the entire form
{loading ? <Spinner /> : <Autocomplete options={options} />}

// ✅ Good: Allow interaction with loading state
<Autocomplete options={options} loading={loading} />
```

## Performance Considerations

### Built-in Virtualization

Autocomplete automatically virtualizes long lists for performance:

```tsx
// Handles 1000+ options efficiently
<Autocomplete
  options={Array.from({ length: 1000 }, (_, i) => `Option ${i + 1}`)}
/>
```

### Debounce Search Requests

Prevent excessive API calls:

```tsx
function SearchAutocomplete({ fetchOptions }) {
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);

  const debouncedFetch = useMemo(
    () =>
      debounce(async (query: string) => {
        if (!query) {
          setOptions([]);
          return;
        }
        setLoading(true);
        try {
          const results = await fetchOptions(query);
          setOptions(results);
        } finally {
          setLoading(false);
        }
      }, 300),
    [fetchOptions]
  );

  return (
    <Autocomplete
      options={options}
      loading={loading}
      onInputChange={(e) => debouncedFetch(e.target.value)}
    />
  );
}
```

### Memoize Options

Prevent unnecessary re-renders:

```tsx
const options = useMemo(
  () =>
    data.map((item) => ({
      value: item.id,
      label: item.name,
      startDecorator: <StatusChip status={item.status} />,
    })),
  [data]
);

<Autocomplete options={options} />
```

### Lazy Load Options

Fetch options only when needed:

```tsx
function LazyLoadAutocomplete() {
  const [options, setOptions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [initialized, setInitialized] = useState(false);

  const loadOptions = async () => {
    if (initialized) return;

    setLoading(true);
    try {
      const data = await fetchAllOptions();
      setOptions(data);
      setInitialized(true);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Autocomplete
      options={options}
      loading={loading}
      onFocus={loadOptions}
      onOpen={loadOptions}
    />
  );
}
```

### Optimize Option Rendering

For complex option content, memoize components:

```tsx
const MemoizedOption = memo(({ option }) => (
  <Stack direction="row" alignItems="center" gap={1}>
    <Avatar src={option.avatar} size="sm" />
    <Box>
      <Typography level="body-sm">{option.name}</Typography>
      <Typography level="body-xs" color="neutral">
        {option.email}
      </Typography>
    </Box>
  </Stack>
));

const options = useMemo(
  () =>
    users.map((user) => ({
      value: user.id,
      label: user.name,
      startDecorator: <MemoizedOption option={user} />,
    })),
  [users]
);
```

Autocomplete provides a powerful search-and-select experience for large option sets. Use it when users benefit from filtering options by typing, and ensure you provide clear labels, handle loading states gracefully, and optimize performance for large datasets. For simpler use cases with fewer options, prefer Select instead.
