عنصر
صف مرن يحتوي على خانات للوسائط والعنوان والوصف والإجراء، مخصص لموجزات الإشعارات وقوائم الإعدادات وصفوف الملفات — بتصميم على الطراز «النيوبروتاليستي».
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"هذا العنصر عبارة عن صف مرن يحتوي على فتحات مسماة — الوسائط، والمحتوى، والإجراءات — لنمط «قائمة العناصر» الذي ينتهي به المطاف كل تطبيق. لا يوجد عنصر أساسي بدون رأس تحته، بل مجرد عناصر مصممة (بالإضافة إلى Base UI's useRender لخاصية render)، تم إنجازها وفقًا لوصفة النمط النيوبروتاليةي: حدود بعرض 2 بكسل — مرئية في النسخة outline — وحلقة تركيز ذات إزاحة ثابتة.
استخدمه عندما:
- تغذية الإشعارات والأنشطة — الصورة الرمزية، والرسالة، والطابع الزمني، وزر إغلاق أو إجراء لكل صف.
- قوائم الإعدادات والتكاملات — أيقونة، واسم، وخط الحالة، مع مفتاح أو زر في الحافة الخلفية.
- متصفحات الملفات ونتائج البحث — صورة مصغرة أو أيقونة بالإضافة إلى البيانات الوصفية، والصف بأكمله يمثل رابطًا عبر
render.
مكوّن Item هو حاوية flex بسيطة يمكنها استيعاب أي نوع محتوى تقريبًا. استخدمه لعرض عنوان ووصف وإجراءات. جمّعه مع مكوّن ItemGroup لإنشاء قائمة من العناصر.
التثبيت#
pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/item.jsonnpx shadcn@latest add https://neobrutalism.com/r/base/item.jsonyarn dlx shadcn@latest add https://neobrutalism.com/r/base/item.jsonbunx --bun shadcn@latest add https://neobrutalism.com/r/base/item.json
انسخ والصق الكود التالي في مشروعك.
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
role="list"
data-slot="item-group"
className={cn(
"group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2",
className
)}
{...props}
/>
)
}
function ItemSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="item-separator"
orientation="horizontal"
className={cn("my-2", className)}
{...props}
/>
)
}
const itemVariants = cva(
"group/item flex w-full flex-wrap items-center rounded border-2 text-sm transition-colors duration-100 outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary [a]:transition-colors [a]:hover:bg-accent",
{
variants: {
variant: {
default: "border-transparent",
outline: "border-border",
muted: "border-transparent bg-muted/50",
},
size: {
default: "gap-2.5 px-3 py-2.5",
sm: "gap-2.5 px-3 py-2.5",
xs: "gap-2 px-2.5 py-2 in-data-[slot=dropdown-menu-content]:p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Item({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "div"
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
)
}
const itemMediaVariants = cva(
"flex shrink-0 items-center justify-center gap-2 group-has-data-[slot=item-description]/item:translate-y-0.5 group-has-data-[slot=item-description]/item:self-start [&_svg]:pointer-events-none",
{
variants: {
variant: {
default: "bg-transparent",
icon: "[&_svg:not([class*='size-'])]:size-4",
image:
"size-10 overflow-hidden rounded-sm group-data-[size=sm]/item:size-8 group-data-[size=xs]/item:size-6 [&_img]:size-full [&_img]:object-cover",
},
},
defaultVariants: {
variant: "default",
},
}
)
function ItemMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
return (
<div
data-slot="item-media"
data-variant={variant}
className={cn(itemMediaVariants({ variant, className }))}
{...props}
/>
)
}
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-content"
className={cn(
"flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0 [&+[data-slot=item-content]]:flex-none",
className
)}
{...props}
/>
)
}
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-title"
className={cn(
"line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-medium underline-offset-4",
className
)}
{...props}
/>
)
}
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="item-description"
className={cn(
"line-clamp-2 text-left text-sm leading-normal font-normal text-muted-foreground group-data-[size=xs]/item:text-xs [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
className
)}
{...props}
/>
)
}
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("flex items-center gap-2", className)}
{...props}
/>
)
}
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-header"
className={cn(
"flex basis-full items-center justify-between gap-2",
className
)}
{...props}
/>
)
}
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-footer"
className={cn(
"flex basis-full items-center justify-between gap-2",
className
)}
{...props}
/>
)
}
export {
Item,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
}
حدِّث مسارات الاستيراد لتطابق إعداد مشروعك.
الاستخدام#
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemMedia,
ItemTitle,
} from "@/components/ui/item"<Item>
<ItemMedia variant="icon">
<Icon />
</ItemMedia>
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>
<ItemActions>
<Button>Action</Button>
</ItemActions>
</Item>التركيب#
استخدم التركيب التالي لبناء Item:
ItemGroup
└── Item
├── ItemHeader
├── ItemMedia
├── ItemContent
│ ├── ItemTitle
│ └── ItemDescription
├── ItemActions
└── ItemFooterItem vs Field#
استخدم Field إذا كنت بحاجة إلى عرض حقل إدخال نموذج مثل مربّع اختيار أو حقل إدخال أو زر اختيار أو select.
إذا كنت بحاجة فقط إلى عرض محتوى مثل عنوان ووصف وإجراءات، فاستخدم Item.
النمط#
استخدم الخاصية variant لتغيير النمط المرئي للعنصر.
import { InboxIcon } from "lucide-react"
import {الحجم#
استخدم الخاصية size لتغيير حجم العنصر. الأحجام المتاحة هي default وsm وxs.
import { InboxIcon } from "lucide-react"
import {أمثلة#
أيقونة#
استخدم ItemMedia مع variant="icon" لعرض أيقونة.
import { ShieldAlertIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Avatar#
يمكنك استخدام ItemMedia مع variant="avatar" لعرض صورة رمزية.
import { Plus } from "lucide-react"
import {صورة#
استخدم ItemMedia مع variant="image" لعرض صورة.
import Image from "next/image"
import {مجموعة#
استخدم ItemGroup لتجميع العناصر المرتبطة معًا.
import * as React from "react"
import { PlusIcon } from "lucide-react"
ترويسة#
استخدم ItemHeader لإضافة ترويسة فوق محتوى العنصر.
import Image from "next/image"
import {رابط#
استخدم الخاصية render لعرض العنصر كرابط. تُطبَّق حالتا التمرير والتركيز على عنصر الارتساء.
import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react"
import {<Item render={<a href="/dashboard" />}>
<ItemMedia variant="icon">
<HomeIcon />
</ItemMedia>
<ItemContent>
<ItemTitle>Dashboard</ItemTitle>
<ItemDescription>Overview of your account and activity.</ItemDescription>
</ItemContent>
</Item>Dropdown#
"use client"
import { ChevronDownIcon } from "lucide-react"RTL#
لتفعيل RTL في Neobrutalism، راجع دليل إعداد RTL.
"use client"
import * as React from "react"إمكانية الوصول#
هذا العنصر تجميلي — عناصر ذات أنماط دون توصيلات ARIA خاصة بها، على الرغم من أن ItemGroup يعرض role="list" (أضف role="listitem" إلى كل Item إذا كان من المفترض الإعلان عن بنية القائمة). بالنسبة للصفوف التفاعلية، لا تضع معالج النقر على عنصر div — قم بعرض <a> أو <button> حقيقي عبر الخاصية render حتى يحصل على التركيز الأصلي وسلوك لوحة المفاتيح؛ حيث إن حلقة التركيز المرئية قد تم تصميمها بالفعل. قم بتمييز وسائط الرموز الزخرفية بـ aria-hidden وامنح وسائط الصور نصًا ذي معنى بـ alt.
مرجع API#
Item#
المكوّن الرئيسي لعرض المحتوى مع الوسائط والعنوان والوصف والإجراءات.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "xs" | "default" |
render | React.ReactElement |
ItemGroup#
حاوية تجمع العناصر المرتبطة معًا بتنسيق متّسق.
<ItemGroup>
<Item />
<Item />
</ItemGroup>ItemSeparator#
فاصل بين العناصر في مجموعة.
<ItemGroup>
<Item />
<ItemSeparator />
<Item />
</ItemGroup>ItemMedia#
استخدم ItemMedia لعرض محتوى الوسائط مثل الأيقونات أو الصور أو الصور الرمزية.
| Prop | Type | Default |
|---|---|---|
variant | "default" | "icon" | "image" | "default" |
<ItemMedia variant="icon">
<Icon />
</ItemMedia><ItemMedia variant="image">
<img src="..." alt="..." />
</ItemMedia>ItemContent#
يغلّف عنوان العنصر ووصفه.
<ItemContent>
<ItemTitle>Title</ItemTitle>
<ItemDescription>Description</ItemDescription>
</ItemContent>ItemTitle#
يعرض عنوان العنصر.
<ItemTitle>Item Title</ItemTitle>ItemDescription#
يعرض وصف العنصر.
<ItemDescription>Item description</ItemDescription>ItemActions#
حاوية لأزرار الإجراءات أو غيرها من العناصر التفاعلية.
<ItemActions>
<Button>Action</Button>
</ItemActions>ItemHeader#
يعرض ترويسة فوق محتوى العنصر.
<Item>
<ItemHeader>Header</ItemHeader>
<ItemContent>...</ItemContent>
</Item>ItemFooter#
يعرض تذييلًا أسفل محتوى العنصر.
<Item>
<ItemContent>...</ItemContent>
<ItemFooter>Footer</ItemFooter>
</Item>