feat: add input-group

This commit is contained in:
shadcn
2026-01-19 13:56:42 +04:00
parent 7ecba59894
commit 52a4b1d466
20 changed files with 668 additions and 363 deletions

View File

@@ -1,6 +1,6 @@
---
title: Input Group
description: Display additional information or actions to an input or textarea.
description: Add addons, buttons, and helper content to inputs.
base: base
component: true
---
@@ -10,7 +10,7 @@ import { IconInfoCircle } from "@tabler/icons-react"
<ComponentPreview
styleName="base-nova"
name="input-group-demo"
className="[&_.preview]:p-4"
previewClassName="h-[26rem]"
/>
## Installation
@@ -68,12 +68,59 @@ import {
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton>Search</InputGroupButton>
</InputGroupAddon>
</InputGroup>
```
## Align
Use the `align` prop on `InputGroupAddon` to position the addon relative to the input.
<Callout>
For proper focus management, `InputGroupAddon` should always be placed after
`InputGroupInput` or `InputGroupTextarea` in the DOM. Use the `align` prop to
visually position the addon.
</Callout>
### inline-start
Use `align="inline-start"` to position the addon at the start of the input. This is the default.
<ComponentPreview
styleName="base-nova"
name="input-group-inline-start"
previewClassName="h-48"
/>
### inline-end
Use `align="inline-end"` to position the addon at the end of the input.
<ComponentPreview
styleName="base-nova"
name="input-group-inline-end"
previewClassName="h-48"
/>
### block-start
Use `align="block-start"` to position the addon above the input.
<ComponentPreview
styleName="base-nova"
name="input-group-block-start"
previewClassName="h-96"
/>
### block-end
Use `align="block-end"` to position the addon below the input.
<ComponentPreview
styleName="base-nova"
name="input-group-block-end"
previewClassName="h-[26rem]"
/>
## Examples
### Icon
@@ -81,119 +128,69 @@ import {
<ComponentPreview
styleName="base-nova"
name="input-group-icon"
className="[&_.preview]:p-4"
previewClassName="h-80"
/>
### Text
Display additional text information alongside inputs.
<ComponentPreview
styleName="base-nova"
name="input-group-text"
className="[&_.preview]:p-4"
previewClassName="h-80"
/>
### Button
Add buttons to perform actions within the input group.
<ComponentPreview
styleName="base-nova"
name="input-group-button"
className="[&_.preview]:p-4"
previewClassName="h-72"
/>
### Tooltip
Add tooltips to provide additional context or help.
### Kbd
<ComponentPreview
styleName="base-nova"
name="input-group-tooltip"
className="[&_.preview]:p-4"
/>
### Textarea
Input groups also work with textarea components. Use `block-start` or `block-end` for alignment.
<ComponentPreview
styleName="base-nova"
name="input-group-textarea"
className="[&_.preview]:p-4"
/>
### Spinner
Show loading indicators while processing input.
<ComponentPreview
styleName="base-nova"
name="input-group-spinner"
className="[&_.preview]:p-4"
/>
### Label
Add labels within input groups to improve accessibility.
<ComponentPreview
styleName="base-nova"
name="input-group-label"
className="[&_.preview]:p-4"
name="input-group-kbd"
previewClassName="h-40"
/>
### Dropdown
Pair input groups with dropdown menus for complex interactions.
<ComponentPreview
styleName="base-nova"
name="input-group-dropdown"
className="[&_.preview]:p-4"
previewClassName="h-56"
/>
### Button Group
Wrap input groups with button groups to create prefixes and suffixes.
### Spinner
<ComponentPreview
styleName="base-nova"
name="input-group-button-group"
className="[&_.preview]:p-4"
name="input-group-spinner"
previewClassName="h-80"
/>
### Textarea
<ComponentPreview
styleName="base-nova"
name="input-group-textarea"
previewClassName="h-96"
/>
### Custom Input
Add the `data-slot="input-group-control"` attribute to your custom input for automatic behavior and focus state handling.
Add the `data-slot="input-group-control"` attribute to your custom input for automatic focus state handling.
No style is applied to the custom input. Apply your own styles using the `className` prop.
Here's an example of a custom resizable textarea from a third-party library.
<ComponentPreview
styleName="base-nova"
name="input-group-custom"
className="!mb-4 [&_.preview]:p-4"
previewClassName="h-56"
/>
```tsx showLineNumbers
import { InputGroup, InputGroupAddon } from "@/component/ui/input-group"
import TextareaAutosize from "react-textarea-autosize"
export function InputGroupCustom() {
return (
<InputGroup>
<TextareaAutosize
data-slot="input-group-control"
className="dark:bg-input/30 flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">how</InputGroupAddon>
</InputGroup>
)
}
```
## API Reference
### InputGroup
@@ -297,8 +294,3 @@ All other props are passed through to the underlying `<Textarea />` component.
</InputGroup>
```
## Changelog
### 2025-10-06 `InputGroup`
Add the `min-w-0` class to the `InputGroup` component. See [diff](https://github.com/shadcn-ui/ui/pull/8341/files#diff-0e2ee95d0050ca4c5d82339df86c54e14a6739dc4638fdda0eec8f73aebc2da9).

View File

@@ -1,6 +1,6 @@
---
title: Input Group
description: Display additional information or actions to an input or textarea.
description: Add addons, buttons, and helper content to inputs.
base: radix
component: true
---
@@ -10,7 +10,7 @@ import { IconInfoCircle } from "@tabler/icons-react"
<ComponentPreview
styleName="radix-nova"
name="input-group-demo"
className="[&_.preview]:p-4"
previewClassName="h-[26rem]"
/>
## Installation
@@ -68,12 +68,59 @@ import {
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<InputGroupButton>Search</InputGroupButton>
</InputGroupAddon>
</InputGroup>
```
## Align
Use the `align` prop on `InputGroupAddon` to position the addon relative to the input.
<Callout>
For proper focus management, `InputGroupAddon` should always be placed after
`InputGroupInput` or `InputGroupTextarea` in the DOM. Use the `align` prop to
visually position the addon.
</Callout>
### inline-start
Use `align="inline-start"` to position the addon at the start of the input. This is the default.
<ComponentPreview
styleName="radix-nova"
name="input-group-inline-start"
previewClassName="h-48"
/>
### inline-end
Use `align="inline-end"` to position the addon at the end of the input.
<ComponentPreview
styleName="radix-nova"
name="input-group-inline-end"
previewClassName="h-48"
/>
### block-start
Use `align="block-start"` to position the addon above the input.
<ComponentPreview
styleName="radix-nova"
name="input-group-block-start"
previewClassName="h-96"
/>
### block-end
Use `align="block-end"` to position the addon below the input.
<ComponentPreview
styleName="radix-nova"
name="input-group-block-end"
previewClassName="h-[26rem]"
/>
## Examples
### Icon
@@ -81,119 +128,69 @@ import {
<ComponentPreview
styleName="radix-nova"
name="input-group-icon"
className="[&_.preview]:p-4"
previewClassName="h-80"
/>
### Text
Display additional text information alongside inputs.
<ComponentPreview
styleName="radix-nova"
name="input-group-text"
className="[&_.preview]:p-4"
previewClassName="h-80"
/>
### Button
Add buttons to perform actions within the input group.
<ComponentPreview
styleName="radix-nova"
name="input-group-button"
className="[&_.preview]:p-4"
previewClassName="h-72"
/>
### Tooltip
Add tooltips to provide additional context or help.
### Kbd
<ComponentPreview
styleName="radix-nova"
name="input-group-tooltip"
className="[&_.preview]:p-4"
/>
### Textarea
Input groups also work with textarea components. Use `block-start` or `block-end` for alignment.
<ComponentPreview
styleName="radix-nova"
name="input-group-textarea"
className="[&_.preview]:p-4"
/>
### Spinner
Show loading indicators while processing input.
<ComponentPreview
styleName="radix-nova"
name="input-group-spinner"
className="[&_.preview]:p-4"
/>
### Label
Add labels within input groups to improve accessibility.
<ComponentPreview
styleName="radix-nova"
name="input-group-label"
className="[&_.preview]:p-4"
name="input-group-kbd"
previewClassName="h-40"
/>
### Dropdown
Pair input groups with dropdown menus for complex interactions.
<ComponentPreview
styleName="radix-nova"
name="input-group-dropdown"
className="[&_.preview]:p-4"
previewClassName="h-56"
/>
### Button Group
Wrap input groups with button groups to create prefixes and suffixes.
### Spinner
<ComponentPreview
styleName="radix-nova"
name="input-group-button-group"
className="[&_.preview]:p-4"
name="input-group-spinner"
previewClassName="h-80"
/>
### Textarea
<ComponentPreview
styleName="radix-nova"
name="input-group-textarea"
previewClassName="h-96"
/>
### Custom Input
Add the `data-slot="input-group-control"` attribute to your custom input for automatic behavior and focus state handling.
Add the `data-slot="input-group-control"` attribute to your custom input for automatic focus state handling.
No style is applied to the custom input. Apply your own styles using the `className` prop.
Here's an example of a custom resizable textarea from a third-party library.
<ComponentPreview
styleName="radix-nova"
name="input-group-custom"
className="!mb-4 [&_.preview]:p-4"
previewClassName="h-56"
/>
```tsx showLineNumbers
import { InputGroup, InputGroupAddon } from "@/component/ui/input-group"
import TextareaAutosize from "react-textarea-autosize"
export function InputGroupCustom() {
return (
<InputGroup>
<TextareaAutosize
data-slot="input-group-control"
className="dark:bg-input/30 flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2 text-base transition-[color,box-shadow] outline-none"
placeholder="Autoresize textarea..."
/>
<InputGroupAddon align="block-end">how</InputGroupAddon>
</InputGroup>
)
}
```
## API Reference
### InputGroup

View File

@@ -2553,6 +2553,32 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-group-block-end": {
name: "input-group-block-end",
filePath: "examples/radix/input-group-block-end.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-group-block-end")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-block-end"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-block-start": {
name: "input-group-block-start",
filePath: "examples/radix/input-group-block-start.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-group-block-start")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-block-start"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-button-group": {
name: "input-group-button-group",
filePath: "examples/radix/input-group-button-group.tsx",
@@ -2644,6 +2670,45 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-group-inline-end": {
name: "input-group-inline-end",
filePath: "examples/radix/input-group-inline-end.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-group-inline-end")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-inline-end"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-inline-start": {
name: "input-group-inline-start",
filePath: "examples/radix/input-group-inline-start.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-group-inline-start")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-inline-start"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-kbd": {
name: "input-group-kbd",
filePath: "examples/radix/input-group-kbd.tsx",
component: React.lazy(async () => {
const mod = await import("./radix/input-group-kbd")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-kbd"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-label": {
name: "input-group-label",
filePath: "examples/radix/input-group-label.tsx",
@@ -8862,6 +8927,32 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-group-block-end": {
name: "input-group-block-end",
filePath: "examples/base/input-group-block-end.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-group-block-end")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-block-end"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-block-start": {
name: "input-group-block-start",
filePath: "examples/base/input-group-block-start.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-group-block-start")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-block-start"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-button-group": {
name: "input-group-button-group",
filePath: "examples/base/input-group-button-group.tsx",
@@ -8953,6 +9044,45 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
return { default: mod.default || mod[exportName] }
}),
},
"input-group-inline-end": {
name: "input-group-inline-end",
filePath: "examples/base/input-group-inline-end.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-group-inline-end")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-inline-end"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-inline-start": {
name: "input-group-inline-start",
filePath: "examples/base/input-group-inline-start.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-group-inline-start")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-inline-start"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-kbd": {
name: "input-group-kbd",
filePath: "examples/base/input-group-kbd.tsx",
component: React.lazy(async () => {
const mod = await import("./base/input-group-kbd")
const exportName =
Object.keys(mod).find(
(key) =>
typeof mod[key] === "function" || typeof mod[key] === "object"
) || "input-group-kbd"
return { default: mod.default || mod[exportName] }
}),
},
"input-group-label": {
name: "input-group-label",
filePath: "examples/base/input-group-label.tsx",

View File

@@ -0,0 +1,49 @@
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/examples/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/base/ui/input-group"
export function InputGroupBlockEnd() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-end-input">Input</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="block-end-input" placeholder="Enter amount" />
<InputGroupAddon align="block-end">
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Footer positioned below the input.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="block-end-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-end-textarea"
placeholder="Write a comment..."
/>
<InputGroupAddon align="block-end">
<InputGroupText>0/280</InputGroupText>
<InputGroupButton variant="default" size="sm" className="ml-auto">
Post
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Footer positioned below the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}

View File

@@ -0,0 +1,56 @@
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/examples/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/base/ui/input-group"
import { CopyIcon, FileCodeIcon } from "lucide-react"
export function InputGroupBlockStart() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-start-input">Input</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput
id="block-start-input"
placeholder="Enter your name"
/>
<InputGroupAddon align="block-start">
<InputGroupText>Full Name</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Header positioned above the input.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="block-start-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-start-textarea"
placeholder="console.log('Hello, world!');"
className="font-mono text-sm"
/>
<InputGroupAddon align="block-start">
<FileCodeIcon className="text-muted-foreground" />
<InputGroupText className="font-mono">script.js</InputGroupText>
<InputGroupButton size="icon-xs" className="ml-auto">
<CopyIcon />
<span className="sr-only">Copy</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Header positioned above the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}

View File

@@ -1,102 +1,18 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/examples/base/ui/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/base/ui/input-group"
import { Separator } from "@/examples/base/ui/separator"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/base/ui/tooltip"
import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
import { Search } from "lucide-react"
export default function InputGroupDemo() {
export function InputGroupDemo() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="example.com" className="!pl-1" />
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger
render={
<InputGroupButton className="rounded-full" size="icon-xs" />
}
>
<IconInfoCircle />
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Ask, Search or Chat..." />
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
className="rounded-full"
size="icon-xs"
>
<IconPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger render={<InputGroupButton variant="ghost" />}>
Auto
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:0.95rem]"
>
<DropdownMenuGroup>
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText className="ml-auto">52% used</InputGroupText>
<Separator orientation="vertical" className="!h-4" />
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
disabled
>
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="@shadcn" />
<InputGroupAddon align="inline-end">
<div className="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full">
<IconCheck className="size-3" />
</div>
</InputGroupAddon>
</InputGroup>
</div>
<InputGroup className="max-w-xs">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
)
}

View File

@@ -1,3 +1,5 @@
"use client"
import {
DropdownMenu,
DropdownMenuContent,
@@ -13,7 +15,7 @@ import {
} from "@/examples/base/ui/input-group"
import { ChevronDownIcon, MoreHorizontal } from "lucide-react"
export default function InputGroupDropdown() {
export function InputGroupDropdown() {
return (
<div className="grid w-full max-w-sm gap-4">
<InputGroup>
@@ -31,7 +33,7 @@ export default function InputGroupDropdown() {
>
<MoreHorizontal />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuContent align="end" sideOffset={8} alignOffset={-4}>
<DropdownMenuGroup>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Copy path</DropdownMenuItem>
@@ -41,7 +43,7 @@ export default function InputGroupDropdown() {
</DropdownMenu>
</InputGroupAddon>
</InputGroup>
<InputGroup className="[--radius:1rem]">
<InputGroup>
<InputGroupInput placeholder="Enter search query" />
<InputGroupAddon align="inline-end">
<DropdownMenu>
@@ -52,7 +54,7 @@ export default function InputGroupDropdown() {
>
Search In... <ChevronDownIcon className="size-3" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="[--radius:0.95rem]">
<DropdownMenuContent align="end" sideOffset={8} alignOffset={-4}>
<DropdownMenuGroup>
<DropdownMenuItem>Documentation</DropdownMenuItem>
<DropdownMenuItem>Blog Posts</DropdownMenuItem>

View File

@@ -0,0 +1,26 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/examples/base/ui/input-group"
import { EyeOffIcon } from "lucide-react"
export function InputGroupInlineEnd() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-end-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput
id="inline-end-input"
type="password"
placeholder="Enter password"
/>
<InputGroupAddon align="inline-end">
<EyeOffIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the end.</FieldDescription>
</Field>
)
}

View File

@@ -0,0 +1,22 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/base/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/examples/base/ui/input-group"
import { SearchIcon } from "lucide-react"
export function InputGroupInlineStart() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-start-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-start-input" placeholder="Search..." />
<InputGroupAddon align="inline-start">
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the start.</FieldDescription>
</Field>
)
}

View File

@@ -0,0 +1,21 @@
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/examples/base/ui/input-group"
import { Kbd } from "@/examples/base/ui/kbd"
import { SearchIcon } from "lucide-react"
export function InputGroupKbd() {
return (
<InputGroup className="max-w-sm">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>K</Kbd>
</InputGroupAddon>
</InputGroup>
)
}

View File

@@ -10,27 +10,27 @@ import { LoaderIcon } from "lucide-react"
export default function InputGroupSpinner() {
return (
<div className="grid w-full max-w-sm gap-4">
<InputGroup data-disabled>
<InputGroupInput placeholder="Searching..." disabled />
<InputGroup>
<InputGroupInput placeholder="Searching..." />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Processing..." disabled />
<InputGroup>
<InputGroupInput placeholder="Processing..." />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Saving changes..." disabled />
<InputGroup>
<InputGroupInput placeholder="Saving changes..." />
<InputGroupAddon align="inline-end">
<InputGroupText>Saving...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Refreshing data..." disabled />
<InputGroup>
<InputGroupInput placeholder="Refreshing data..." />
<InputGroupAddon>
<LoaderIcon className="animate-spin" />
</InputGroupAddon>

View File

@@ -1,3 +1,5 @@
"use client"
import {
Field,
FieldDescription,

View File

@@ -1,4 +1,5 @@
import { Button } from "@/examples/base/ui/button"
"use client"
import { ButtonGroup, ButtonGroupText } from "@/examples/base/ui/button-group"
import {
DropdownMenu,
@@ -12,7 +13,6 @@ import {
FieldGroup,
FieldLabel,
} from "@/examples/base/ui/field"
import { Input } from "@/examples/base/ui/input"
import {
InputGroup,
InputGroupAddon,

View File

@@ -0,0 +1,49 @@
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/examples/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/radix/ui/input-group"
export function InputGroupBlockEnd() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-end-input">Input</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput id="block-end-input" placeholder="Enter amount" />
<InputGroupAddon align="block-end">
<InputGroupText>USD</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Footer positioned below the input.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="block-end-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-end-textarea"
placeholder="Write a comment..."
/>
<InputGroupAddon align="block-end">
<InputGroupText>0/280</InputGroupText>
<InputGroupButton variant="default" size="sm" className="ml-auto">
Post
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Footer positioned below the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}

View File

@@ -0,0 +1,56 @@
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
} from "@/examples/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/radix/ui/input-group"
import { CopyIcon, FileCodeIcon } from "lucide-react"
export function InputGroupBlockStart() {
return (
<FieldGroup className="max-w-sm">
<Field>
<FieldLabel htmlFor="block-start-input">Input</FieldLabel>
<InputGroup className="h-auto">
<InputGroupInput
id="block-start-input"
placeholder="Enter your name"
/>
<InputGroupAddon align="block-start">
<InputGroupText>Full Name</InputGroupText>
</InputGroupAddon>
</InputGroup>
<FieldDescription>Header positioned above the input.</FieldDescription>
</Field>
<Field>
<FieldLabel htmlFor="block-start-textarea">Textarea</FieldLabel>
<InputGroup>
<InputGroupTextarea
id="block-start-textarea"
placeholder="console.log('Hello, world!');"
className="font-mono text-sm"
/>
<InputGroupAddon align="block-start">
<FileCodeIcon className="text-muted-foreground" />
<InputGroupText className="font-mono">script.js</InputGroupText>
<InputGroupButton size="icon-xs" className="ml-auto">
<CopyIcon />
<span className="sr-only">Copy</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
Header positioned above the textarea.
</FieldDescription>
</Field>
</FieldGroup>
)
}

View File

@@ -1,100 +1,18 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/examples/radix/ui/dropdown-menu"
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/examples/radix/ui/input-group"
import { Separator } from "@/examples/radix/ui/separator"
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/examples/radix/ui/tooltip"
import { IconCheck, IconInfoCircle, IconPlus } from "@tabler/icons-react"
import { ArrowUpIcon, Search } from "lucide-react"
import { Search } from "lucide-react"
export default function InputGroupDemo() {
export function InputGroupDemo() {
return (
<div className="grid w-full max-w-sm gap-6">
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="example.com" className="!pl-1" />
<InputGroupAddon>
<InputGroupText>https://</InputGroupText>
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Tooltip>
<TooltipTrigger asChild>
<InputGroupButton className="rounded-full" size="icon-xs">
<IconInfoCircle />
</InputGroupButton>
</TooltipTrigger>
<TooltipContent>This is content in a tooltip.</TooltipContent>
</Tooltip>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupTextarea placeholder="Ask, Search or Chat..." />
<InputGroupAddon align="block-end">
<InputGroupButton
variant="outline"
className="rounded-full"
size="icon-xs"
>
<IconPlus />
</InputGroupButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<InputGroupButton variant="ghost">Auto</InputGroupButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="[--radius:0.95rem]"
>
<DropdownMenuGroup>
<DropdownMenuItem>Auto</DropdownMenuItem>
<DropdownMenuItem>Agent</DropdownMenuItem>
<DropdownMenuItem>Manual</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<InputGroupText className="ml-auto">52% used</InputGroupText>
<Separator orientation="vertical" className="!h-4" />
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
disabled
>
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<InputGroup>
<InputGroupInput placeholder="@shadcn" />
<InputGroupAddon align="inline-end">
<div className="bg-primary text-primary-foreground flex size-4 items-center justify-center rounded-full">
<IconCheck className="size-3" />
</div>
</InputGroupAddon>
</InputGroup>
</div>
<InputGroup className="max-w-xs">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Search />
</InputGroupAddon>
<InputGroupAddon align="inline-end">12 results</InputGroupAddon>
</InputGroup>
)
}

View File

@@ -0,0 +1,26 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/examples/radix/ui/input-group"
import { EyeOffIcon } from "lucide-react"
export function InputGroupInlineEnd() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-end-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput
id="inline-end-input"
type="password"
placeholder="Enter password"
/>
<InputGroupAddon align="inline-end">
<EyeOffIcon />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the end.</FieldDescription>
</Field>
)
}

View File

@@ -0,0 +1,22 @@
import { Field, FieldDescription, FieldLabel } from "@/examples/radix/ui/field"
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/examples/radix/ui/input-group"
import { SearchIcon } from "lucide-react"
export function InputGroupInlineStart() {
return (
<Field className="max-w-sm">
<FieldLabel htmlFor="inline-start-input">Input</FieldLabel>
<InputGroup>
<InputGroupInput id="inline-start-input" placeholder="Search..." />
<InputGroupAddon align="inline-start">
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
</InputGroup>
<FieldDescription>Icon positioned at the start.</FieldDescription>
</Field>
)
}

View File

@@ -0,0 +1,21 @@
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/examples/radix/ui/input-group"
import { Kbd } from "@/examples/radix/ui/kbd"
import { SearchIcon } from "lucide-react"
export function InputGroupKbd() {
return (
<InputGroup className="max-w-sm">
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon className="text-muted-foreground" />
</InputGroupAddon>
<InputGroupAddon align="inline-end">
<Kbd>K</Kbd>
</InputGroupAddon>
</InputGroup>
)
}

View File

@@ -10,27 +10,27 @@ import { LoaderIcon } from "lucide-react"
export default function InputGroupSpinner() {
return (
<div className="grid w-full max-w-sm gap-4">
<InputGroup data-disabled>
<InputGroupInput placeholder="Searching..." disabled />
<InputGroup>
<InputGroupInput placeholder="Searching..." />
<InputGroupAddon align="inline-end">
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Processing..." disabled />
<InputGroup>
<InputGroupInput placeholder="Processing..." />
<InputGroupAddon>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Saving changes..." disabled />
<InputGroup>
<InputGroupInput placeholder="Saving changes..." />
<InputGroupAddon align="inline-end">
<InputGroupText>Saving...</InputGroupText>
<Spinner />
</InputGroupAddon>
</InputGroup>
<InputGroup data-disabled>
<InputGroupInput placeholder="Refreshing data..." disabled />
<InputGroup>
<InputGroupInput placeholder="Refreshing data..." />
<InputGroupAddon>
<LoaderIcon className="animate-spin" />
</InputGroupAddon>