الانتقال إلى المحتوى

عنصر

صف مرن يحتوي على خانات للوسائط والعنوان والوصف والإجراء، مخصص لموجزات الإشعارات وقوائم الإعدادات وصفوف الملفات — بتصميم على الطراز «النيوبروتاليستي».

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.json
npx shadcn@latest add https://neobrutalism.com/r/base/item.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/item.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/item.json

انسخ والصق الكود التالي في مشروعك.

components/ui/item.tsx
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
    └── ItemFooter

Item 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>
"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

المكوّن الرئيسي لعرض المحتوى مع الوسائط والعنوان والوصف والإجراءات.

PropTypeDefault
variant"default" | "outline" | "muted""default"
size"default" | "sm" | "xs""default"
renderReact.ReactElement

ItemGroup

حاوية تجمع العناصر المرتبطة معًا بتنسيق متّسق.

<ItemGroup>
  <Item />
  <Item />
</ItemGroup>

ItemSeparator

فاصل بين العناصر في مجموعة.

<ItemGroup>
  <Item />
  <ItemSeparator />
  <Item />
</ItemGroup>

ItemMedia

استخدم ItemMedia لعرض محتوى الوسائط مثل الأيقونات أو الصور أو الصور الرمزية.

PropTypeDefault
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>