# Input Group

An input or textarea with icons, text or buttons attached inside its border.

```tsx
import * as React from "react"
import { SearchIcon, XIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@zenoxai/ui/components/input-group"
import { Kbd } from "@zenoxai/ui/components/kbd"

function InputGroupDemo() {
  const [query, setQuery] = React.useState("")

  return (
    <InputGroup className="max-w-xs">
      <InputGroupInput
        type="search"
        value={query}
        onChange={(event) => setQuery(event.target.value)}
        placeholder="Search alerts"
        aria-label="Search alerts"
      />
      <InputGroupAddon>
        <SearchIcon />
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        {query ? (
          <InputGroupButton
            size="icon-xs"
            aria-label="Clear search"
            onClick={() => setQuery("")}
          >
            <XIcon />
          </InputGroupButton>
        ) : (
          <Kbd>/</Kbd>
        )}
      </InputGroupAddon>
    </InputGroup>
  )
}
```

## Usage

```tsx
import { SearchIcon } from "lucide-react"

import {
  InputGroup,
  InputGroupAddon,
  InputGroupInput,
} from "@zenoxai/ui/components/input-group"

<InputGroup>
  <InputGroupInput placeholder="Search alerts" aria-label="Search alerts" />
  <InputGroupAddon>
    <SearchIcon />
  </InputGroupAddon>
</InputGroup>
```

`InputGroup` draws one border around a control and its addons, and moves the focus ring and
the invalid ring from the control to the group. Use `InputGroupInput` or `InputGroupTextarea`
inside it, not the plain [Input](/components/input) or [Textarea](/components/textarea),
which would draw a second border.

Put the control first and the addons after it. `align` on `InputGroupAddon` places the addon,
whatever its order in the markup:

| `align`                  | Places the addon                                          |
| ------------------------ | --------------------------------------------------------- |
| `inline-start` (default) | Before the control, on the same line.                     |
| `inline-end`             | After the control, on the same line.                      |
| `block-start`            | Above the control, full width. The group grows in height. |
| `block-end`              | Below the control, full width. The group grows in height. |

A click on an addon outside its buttons focuses the input. Keep addons short: an icon, a unit,
a prefix, a keyboard hint or one or two buttons. For a label and help text, use a
[Field](/components/field) around the group.

The example at the top of the page is the search field of a list: a search icon at the start,
and at the end the `/` shortcut hint, replaced by a "Clear search" button once there is text.
Browsers' own clear buttons on `type="search"` are hidden by the base styles, so this button is
the only one.

## Examples

### Sizes

`size` sets the height of a one-line group: `sm` (28px), `default` (32px) and `lg` (36px), the
heights of [Input](/components/input) and [Button](/components/button). Icons in an addon are
16px, 14px in an `sm` group.

```tsx
import { SearchIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupInput } from "@zenoxai/ui/components/input-group"

function InputGroupSizes() {
  return (
    <div className="flex w-full max-w-xs flex-col gap-3">
      <InputGroup size="sm">
        <InputGroupInput
          placeholder="Filter assets"
          aria-label="Filter assets"
        />
        <InputGroupAddon>
          <SearchIcon />
        </InputGroupAddon>
      </InputGroup>
      <InputGroup>
        <InputGroupInput
          placeholder="Search alerts"
          aria-label="Search alerts"
        />
        <InputGroupAddon>
          <SearchIcon />
        </InputGroupAddon>
      </InputGroup>
      <InputGroup size="lg">
        <InputGroupInput
          placeholder="Search domains"
          aria-label="Search domains"
        />
        <InputGroupAddon>
          <SearchIcon />
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}
```

### Prefix and suffix text

`InputGroupText` shows fixed text, such as a protocol, a domain or a unit, that is part of the
value's format but not typed by the user.

```tsx
import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@zenoxai/ui/components/input-group"

function InputGroupWithText() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput placeholder="acme" aria-label="Workspace subdomain" />
      <InputGroupAddon>
        <InputGroupText>https://</InputGroupText>
      </InputGroupAddon>
      <InputGroupAddon align="inline-end">
        <InputGroupText>.zenox.io</InputGroupText>
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### With a button

`InputGroupButton` is a [Button](/components/button) sized for the group: `xs` (24px, the
default) and `sm` (28px) for buttons with text, `icon-xs` and `icon-sm` for icon-only buttons.
Each size has the text, icon and radius of the Button size of the same name. The 28px sizes
fit the default and `lg` groups; in an `sm` group use the `xs` sizes. It defaults to
`variant="ghost"` and `type="button"`, so it does not submit the form.

```tsx
import { CopyIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@zenoxai/ui/components/input-group"

function InputGroupWithButton() {
  return (
    <InputGroup className="max-w-sm">
      <InputGroupInput
        readOnly
        defaultValue="https://hooks.zenox.io/t/acme/alerts"
        aria-label="Alert webhook URL"
      />
      <InputGroupAddon align="inline-end">
        <InputGroupButton size="icon-xs" aria-label="Copy webhook URL">
          <CopyIcon />
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}
```

### With a textarea

A `block-end` addon under an `InputGroupTextarea` holds a hint and the submit button of a
comment or note box.

```tsx
import { SendIcon } from "lucide-react"
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupTextarea } from "@zenoxai/ui/components/input-group"

function InputGroupWithTextarea() {
  return (
    <InputGroup className="max-w-md">
      <InputGroupTextarea
        placeholder="Add a note for the next analyst"
        aria-label="Investigation note"
        rows={3}
      />
      <InputGroupAddon align="block-end">
        <InputGroupText>Visible to everyone in the tenant</InputGroupText>
        <InputGroupButton variant="default" size="xs" className="ml-auto">
          <SendIcon data-icon="inline-start" />
          Add note
        </InputGroupButton>
      </InputGroupAddon>
    </InputGroup>
  )
}
```

## Accessibility

- The control needs its own name: `aria-label`, or a `FieldLabel` pointing at its `id`. Text in
  an addon, such as `https://`, is not part of that name; repeat it in the label when it
  matters.
- Icon-only `InputGroupButton`s need an `aria-label`. Lucide icons render with
  `aria-hidden="true"`, so an icon addon adds nothing to what screen readers announce.
- The addon's click-to-focus is for pointer users only; keyboard users tab to the input
  directly.
- To mark the group invalid, set `aria-invalid` on the control. The group draws the error
  ring.

## API reference

`import { ... } from "@zenoxai/ui/components/input-group"`

### InputGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| size | `"default" \| "sm" \| "lg"` | `default` | Height of the group on one row: `sm` 28px, `default` 32px, `lg` 36px, the heights of Input and Button. An `sm` group fits `xs` buttons only. |

### InputGroupAddon

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| align | `"inline-end" \| "inline-start" \| "block-start" \| "block-end" \| null` | `inline-start` | Where the addon sits: `inline-start` (default) and `inline-end` before and after the control on its row; `block-start` and `block-end` as full-width rows above and below it, as around a textarea. |

### InputGroupButton

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| focusableWhenDisabled | `boolean` | `false` | Whether the button should be focusable when disabled. |
| nativeButton | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| render | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ButtonState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |
| size | `"xs" \| "sm" \| "icon-xs" \| "icon-sm"` | `xs` |  |
| type | `"button" \| "submit" \| "reset"` | `button` |  |
| variant | `"link" \| "default" \| "destructive" \| "outline" \| "secondary" \| "ghost" \| null` | `ghost` | Visual weight. `default` is the filled primary action, one per view; `outline` and `secondary` are for the other actions; `ghost` has no fill until hover, for toolbars and icon buttons; `destructive` is for delete and other irreversible actions; `link` looks like inline text. |

### InputGroupText

No props of its own.

### InputGroupInput

No props of its own.

### InputGroupTextarea

No props of its own.

