דילוג לתוכן

פריט

שורת תוכן גמישה הכוללת משבצות למדיה, כותרת, תיאור ופעולה עבור עדכוני התראות, רשימות הגדרות ושורות קבצים — בסגנון ניאו-ברוטליסטי.

import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"

import { Button } from "@/components/ui/button"

ה-item הוא שורת flex עם חריצים בשם — media, content, actions — לדפוס "רשימת דברים" שכל אפליקציה מגיעה אליו. אין פרימיטיב headless מתחת, רק אלמנטים מעוצבים (ועוד useRender של Base UI ל-prop ה-render), בגמר המתכון הנאו-ברוטליסטי: מסגרת 2px — גלויה בווריאנט outline — וטבעת פוקוס בהיסט חד.

השתמשו בו כש:

  • פידי התראות ופעילות — avatar, הודעה, חותמת זמן וכפתור סגירה או פעולה לכל שורה.
  • רשימות הגדרות ואינטגרציות — אייקון, שם, שורת סטטוס, עם switch או כפתור בקצה הנגרר.
  • דפדפני קבצים ותוצאות חיפוש — תמונה ממוזערת או אייקון ועוד מטא-דאטה, כל השורה קישור דרך 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.

וריאנט

השתמשו ב-prop בשם variant כדי לשנות את הסגנון החזותי של הפריט.

import { InboxIcon } from "lucide-react"

import {

גודל

השתמשו ב-prop בשם 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 {

קישור

השתמשו ב-prop בשם 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"

נגישות

ה-item הוא מצגתי — אלמנטים מעוצבים בלי חיווט ARIA משלהם, אם כי ItemGroup מרנדר role="list" (הוסיפו role="listitem" לכל Item אם מבנה הרשימה צריך להיות מוכרז). לשורות אינטראקטיביות, אל תשימו click handler על ה-div — רנדרו <a> או <button> אמיתי דרך prop ה-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>