Елемент
Гнучкий блок із слотами для медіа, заголовка, опису та дії, призначений для стрічок сповіщень, списків налаштувань та рядків файлів — у стилі необруталізму.
import { BadgeCheckIcon, ChevronRightIcon } from "lucide-react"
import { Button } from "@/components/ui/button"Item — flex-рядок із іменованими слотами media, content, actions для патерну «список речей», який з’являється в кожному застосунку. Під ним немає headless-примітиву — лише стилізовані елементи (плюс Base UI useRender для пропа render), доведені до рецепту необруталізму: рамка 2px — видима на варіанті outline — і різке кільце фокусу зі зміщенням.
Коли це стане в пригоді:
- Стрічки сповіщень і активності — аватар, повідомлення, час і кнопка dismiss або дії в кожному рядку.
- Списки налаштувань та інтеграцій — іконка, назва, рядок статусу, switch або кнопка з правого краю.
- Файлові браузери та результати пошуку — мініатюра чи іконка плюс метадані; увесь рядок — посилання через
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#
Використовуйте проп variant, щоб змінити візуальний стиль елемента.
import { InboxIcon } from "lucide-react"
import {Size#
Використовуйте проп 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, щоб відрендерити елемент як посилання. Стани hover та focus застосовуються до елемента anchor.
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"Доступність#
Item — презентаційний: стилізовані елементи без власної ARIA-обв’язки, хоча ItemGroup рендерить role="list" (додайте role="listitem" до кожного Item, якщо структуру списку потрібно оголошувати). Для інтерактивних рядків не вішайте обробник кліку на div — рендеріть справжній <a> або <button> через проп render, щоб отримати нативний фокус і клавіатурну поведінку; видиме кільце фокусу вже стилізоване. Декоративні іконки media позначайте 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>