مجموعة إدخال
أضف الرموز والأزرار وتلميحات لوحة المفاتيح إلى حقول الإدخال الخاصة بأشرطة البحث وحقول عناوين URL وأدوات كتابة الرسائل في الدردشة — مع حدود وظلال على الطراز «النيوبروتاليستي».
import { Search } from "lucide-react"
import {تقوم مجموعة «الإدخال» بتجميع حقل إدخال أو منطقة نص مع ملحقاتها — مثل الرموز، والنص المضمن، والأزرار، وتلميحات لوحة المفاتيح — داخل عنصر تحكم واحد محاط بإطار. وهي تركيبة داخلية من عناصر Input وTextarea وButton الخاصة بهذه المكتبة، مصممة وفقًا لأسلوب «النيوبروتاليةي»: حدود سميكة، وظلال حادة، وخط عريض.
استخدمه عندما:
- حقول البحث — أيقونة بحث في المقدمة بالإضافة إلى تلميح
⌘Kأو زر مسح في الخلف. - حقول الإدخال ذات البادئة واللاحقة — نص ثابت من نوع
https://أو@أو.comبحيث يكتب المستخدمون الجزء المتغير فقط. - أدوات إنشاء الدردشات والمطالبات — منطقة نصية مع شريط أدوات
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 {
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المحاذاة#
استخدم الخاصية align على InputGroupAddon لتحديد موضع المُلحَق بالنسبة إلى حقل الإدخال.
لإدارة التركيز بشكل صحيح، يجب دائمًا وضع InputGroupAddon بعد
InputGroupInput أو InputGroupTextarea في DOM. استخدم الخاصية 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 لمجموعات حقول الإدخال — حيث يعرض العنصر المُغلف role="group" بحيث تربط التقنيات المساعدة الإضافات بعنصر التحكم، كما أن الوصول عبر لوحة المفاتيح متوفر بشكل أصلي: يؤدي Tab إلى الوصول إلى حقل الإدخال وأي InputGroupButton وفقًا لترتيب DOM، ولهذا السبب يجب أن تأتي الإضافات بعد حقل الإدخال (يتم وضعها بصريًّا باستخدام align). هناك أمران يجب الانتباه إليهما: نص الملحق مثل https:// هو نص مرئي، وليس تسمية برمجية، لذا لا يزال حقل الإدخال بحاجة إلى <Label> أو aria-label؛ والأزرار التي تحتوي على أيقونة فقط تحتاج إلى aria-label — النقر على ملحق يركز على حقل الإدخال، لكن هذه ميزة مريحة للمؤشر، وليست مسارًا للوحة المفاتيح.
مرجع API#
InputGroup#
المكوّن الرئيسي الذي يغلّف حقول الإدخال والمُلحقات.
| Prop | Type | Default |
|---|---|---|
className | string |
<InputGroup>
<InputGroupInput />
<InputGroupAddon />
</InputGroup>InputGroupAddon#
يعرض الأيقونات أو النصوص أو الأزرار أو محتوى آخر بجانب حقول الإدخال.
للتنقّل بالتركيز بشكل صحيح، يجب وضع مكوّن InputGroupAddon بعد حقل الإدخال.
اضبط الخاصية 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 |
تُمرَّر جميع الخصائص الأخرى إلى مكوّن <Input /> الأساسي.
<InputGroup>
<InputGroupInput placeholder="Enter text..." />
<InputGroupAddon>
<SearchIcon />
</InputGroupAddon>
</InputGroup>InputGroupTextarea#
بديل لـ <Textarea /> عند بناء مجموعات الإدخال. هذا المكوّن تُطبَّق عليه أنماط مجموعة textarea مسبقًا ويستخدم data-slot="input-group-control" الموحّد للتعامل مع حالة التركيز.
| Prop | Type | Default |
|---|---|---|
className | string |
تُمرَّر جميع الخصائص الأخرى إلى مكوّن <Textarea /> الأساسي.
<InputGroup>
<InputGroupTextarea placeholder="Enter message..." />
<InputGroupAddon align="block-end">
<InputGroupButton>Send</InputGroupButton>
</InputGroupAddon>
</InputGroup>