گروه ورودی
آیکونها، دکمهها و راهنماییهای صفحهکلید را به ورودیهای نوار جستجو، فیلدهای URL و ویرایشگرهای چت متصل کنید — با حاشیهها و سایههای نئوبروتالیست.
import { Search } from "lucide-react"
import {گروه ورودی، input یا textarea را همراه addonهایش — آیکون، متن درونخطی، دکمه، راهنمای صفحهکلید — داخل یک کنترل حاشیهدار میپیچد. ترکیبی داخلی از Input، Textarea و Button این کتابخانه است، با دستور پخت نئوبروتالیستی: حاشیههای ضخیم، سایههای سخت و تایپوگرافی پررنگ.
برای این موارد از آن استفاده کنید:
- فیلد جستجو — آیکون جستجوی پیشرو بههمراه راهنمای
⌘Kیا دکمهٔ clear در انتها. - ورودی با پیشوند و پسوند — متن ثابت
https://،@یا.comتا کاربر فقط بخش متغیر را تایپ کند. - Composer چت و prompt — textarea با نوار ابزار
block-endاز دکمهها و اقدام ارسال.
نصب#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/input-group.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/input-group.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/input-group.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/input-group.json
کد زیر را کپی کرده و در پروژهٔ خود جایگذاری کنید.
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="input-group"
role="group"
className={cn(
"group/input-group relative flex h-8 w-full min-w-0 items-center rounded border-2 bg-input shadow-sm transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:outline-2 has-[[data-slot=input-group-control]:focus-visible]:outline-offset-2 has-[[data-slot=input-group-control]:focus-visible]:outline-primary has-[[data-slot][aria-invalid=true]]:border-destructive has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className
)}
{...props}
/>
)
}
const inputGroupAddonVariants = cva(
"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
{
variants: {
align: {
"inline-start":
"order-first pl-2 has-[>button]:ml-[-0.3rem] has-[>kbd]:ml-[-0.15rem]",
"inline-end":
"order-last pr-2 has-[>button]:mr-[-0.3rem] has-[>kbd]:mr-[-0.15rem]",
"block-start":
"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2",
"block-end":
"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2",
},
},
defaultVariants: {
align: "inline-start",
},
}
)
function InputGroupAddon({
className,
align = "inline-start",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
return (
<div
role="group"
data-slot="input-group-addon"
data-align={align}
className={cn(inputGroupAddonVariants({ align }), className)}
onClick={(e) => {
if ((e.target as HTMLElement).closest("button")) {
return
}
e.currentTarget.parentElement?.querySelector("input")?.focus()
}}
{...props}
/>
)
}
const inputGroupButtonVariants = cva(
"flex items-center gap-2 text-sm shadow-none",
{
variants: {
size: {
xs: "h-6 gap-1 rounded-[calc(var(--radius)-3px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",
sm: "",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-3px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
},
defaultVariants: {
size: "xs",
},
}
)
function InputGroupButton({
className,
type = "button",
variant = "ghost",
size = "xs",
...props
}: Omit<React.ComponentProps<typeof Button>, "size"> &
VariantProps<typeof inputGroupButtonVariants>) {
return (
<Button
type={type}
data-size={size}
variant={variant}
className={cn(inputGroupButtonVariants({ size }), className)}
{...props}
/>
)
}
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
className={cn(
"flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function InputGroupInput({
className,
...props
}: React.ComponentProps<"input">) {
return (
<Input
data-slot="input-group-control"
className={cn(
"flex-1 rounded-none border-0 bg-transparent shadow-none outline-none focus-visible:outline-none disabled:bg-transparent aria-invalid:outline-none dark:bg-transparent dark:disabled:bg-transparent",
className
)}
{...props}
/>
)
}
function InputGroupTextarea({
className,
...props
}: React.ComponentProps<"textarea">) {
return (
<Textarea
data-slot="input-group-control"
className={cn(
"flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none outline-none focus-visible:outline-none disabled:bg-transparent aria-invalid:outline-none dark:bg-transparent dark:disabled:bg-transparent",
className
)}
{...props}
/>
)
}
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
}
مسیرهای import را مطابق با پیکربندی پروژهٔ خود بهروزرسانی کنید.
استفاده#
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
InputGroupText,
InputGroupTextarea,
} from "@/components/ui/input-group"<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>ترکیب#
برای ساخت یک InputGroup از ترکیب زیر استفاده کنید:
InputGroup
├── InputGroupInput or InputGroupTextarea
├── InputGroupAddon
├── InputGroupButton
└── InputGroupTextتراز#
برای موقعیتدهی افزودنی نسبت به ورودی از prop align روی InputGroupAddon استفاده کنید.
برای مدیریت درست فوکوس، InputGroupAddon باید همیشه بعد از InputGroupInput
یا InputGroupTextarea در DOM قرار گیرد. برای موقعیتدهی بصری افزودنی از prop
align استفاده کنید.
inline-start#
برای قرار دادن افزودنی در ابتدای ورودی از align="inline-start" استفاده کنید. این مقدار پیشفرض است.
import { SearchIcon } from "lucide-react"
import {inline-end#
برای قرار دادن افزودنی در انتهای ورودی از align="inline-end" استفاده کنید.
import { EyeOffIcon } from "lucide-react"
import {block-start#
برای قرار دادن افزودنی بالای ورودی از align="block-start" استفاده کنید.
import { CopyIcon, FileCodeIcon } from "lucide-react"
import {block-end#
برای قرار دادن افزودنی پایین ورودی از align="block-end" استفاده کنید.
import {
Field,
FieldDescription,نمونهها#
آیکون#
import {
CheckIcon,
CreditCardIcon,متن#
import {
InputGroup,
InputGroupAddon,دکمه#
"use client"
import * as React from "react"Kbd#
import { SearchIcon } from "lucide-react"
import {منوی کشویی#
"use client"
import { ChevronDownIcon, MoreHorizontal } from "lucide-react"Spinner#
import { LoaderIcon } from "lucide-react"
import {Textarea#
import {
IconBrandJavascript,
IconCopy,ورودی سفارشی#
برای مدیریت خودکار حالت فوکوس، ویژگی data-slot="input-group-control" را به ورودی سفارشی خود اضافه کنید.
در اینجا نمونهای از یک textarea سفارشی و قابل تغییر اندازه از یک کتابخانهٔ شخص ثالث آمده است.
"use client"
import TextareaAutosize from "react-textarea-autosize"RTL#
برای فعالسازی پشتیبانی RTL در Neobrutalism، راهنمای پیکربندی RTL را ببینید.
"use client"
import * as React from "react"دسترسیپذیری#
الگوی ویجت ARIA برای گروه ورودی وجود ندارد — wrapper مقدار role="group" رندر میکند تا فناوری کمکی addonها را با کنترل مرتبط کند، و دسترسی صفحهکلید بومی است: Tab به input و هر InputGroupButton به ترتیب DOM میرسد؛ به همین دلیل addonها بعد از input میآیند (جای بصری با align). دو چیز با شماست: متن addon مثل https:// بصری است نه برچسب برنامهای، پس input همچنان به <Label> یا aria-label نیاز دارد؛ و دکمههای فقطآیکون به aria-label نیاز دارند — کلیک روی addon input را فوکوس میکند، اما آن راحتی اشارهگر است نه مسیر صفحهکلید.
مرجع API#
InputGroup#
کامپوننت اصلی که ورودیها و افزودنیها را در بر میگیرد.
| Prop | Type | Default |
|---|---|---|
className | string |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>InputGroupAddon#
آیکونها، متن، دکمهها یا محتوای دیگر را در کنار ورودیها نمایش میدهد.
برای پیمایش درست فوکوس، کامپوننت InputGroupAddon باید بعد از ورودی قرار گیرد.
برای موقعیتدهی افزودنی prop align را تنظیم کنید.
| Prop | Type | Default |
|---|---|---|
align | "inline-start" | "inline-end" | "block-start" | "block-end" | "inline-start" |
className | string |
<InputGroupAddon align="inline-end">
<SearchIcon />
</InputGroupAddon>برای <InputGroupInput /> از تراز inline-start یا inline-end استفاده کنید. برای <InputGroupTextarea /> از تراز block-start یا block-end استفاده کنید.
کامپوننت InputGroupAddon میتواند چند کامپوننت InputGroupButton و آیکون داشته باشد.
<InputGroupAddon>
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton>Button</InputGroupButton>
</InputGroupAddon>InputGroupButton#
دکمهها را در داخل گروههای ورودی نمایش میدهد.
| Prop | Type | Default |
|---|---|---|
size | "xs" | "icon-xs" | "sm" | "icon-sm" | "xs" |
variant | "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "ghost" |
className | string |
<InputGroupButton>Button</InputGroupButton>
<InputGroupButton size="icon-xs" aria-label="Copy">
<CopyIcon />
</InputGroupButton>InputGroupInput#
جایگزینی برای <Input /> هنگام ساخت گروههای ورودی. این کامپوننت استایلهای گروه ورودی را از پیش اعمالشده دارد و از data-slot="input-group-control" یکپارچه برای مدیریت حالت فوکوس استفاده میکند.
| Prop | Type | Default |
|---|---|---|
className | string |
تمام propهای دیگر به کامپوننت زیرین <Input /> منتقل میشوند.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>InputGroupTextarea#
جایگزینی برای <Textarea /> هنگام ساخت گروههای ورودی. این کامپوننت استایلهای گروه textarea را از پیش اعمالشده دارد و از data-slot="input-group-control" یکپارچه برای مدیریت حالت فوکوس استفاده میکند.
| Prop | Type | Default |
|---|---|---|
className | string |
تمام propهای دیگر به کامپوننت زیرین <Textarea /> منتقل میشوند.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>