Item
A list row with media, title, description, and actions.
Preview
Loading...
import { Button } from "@/components/ui/Button";
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemSeparator,
ItemTitle,
} from "@/components/ui/Item";
import { FileIcon } from "@radix-ui/react-icons";
export function ItemDemo() {
return (
<ItemGroup className="w-full">
<Item variant="outline">
<ItemMedia variant="icon">
<FileIcon className="icon" />
</ItemMedia>
<ItemContent>
<ItemTitle>Care plan</ItemTitle>
<ItemDescription>Updated this morning</ItemDescription>
</ItemContent>
<ItemActions>
<Button type="button" variant="pill" size="sm">
Open
</Button>
</ItemActions>
</Item>
<ItemSeparator />
<Item variant="muted">
<ItemContent>
<ItemTitle>Lab results</ItemTitle>
<ItemDescription>Waiting on review</ItemDescription>
</ItemContent>
</Item>
</ItemGroup>
);
}Installation
Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces
pnpm dlx shadcn@latest add @aura/itemManual
Install the following dependencies:
pnpm install @radix-ui/react-slot class-variance-authorityCopy and paste the class names utility into your utils/class-names.ts file.
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Copy and paste the separator component into your components/ui/Separator.tsx file.
"use client"
import * as React from "react"
import {Separator as SeparatorPrimitive} from "radix-ui"
import { cn } from "@/utils/class-names"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-gray-a6 shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}
export { Separator }Copy and paste the Item component into your components/ui/Item.tsx file.
"use client";
/**
* @description A row in a list: media, title, description, and actions.
*/
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { Separator } from "@/components/ui/Separator";
import { cn } from "@/utils/class-names";
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
role="list"
data-slot="item-group"
className={cn("flex flex-col", className)}
{...props}
/>
);
}
function ItemSeparator(props: React.ComponentProps<typeof Separator>) {
return (
<Separator data-slot="item-separator" orientation="horizontal" {...props} />
);
}
const itemVariants = cva(
"group/item flex flex-wrap items-center rounded-md border border-transparent text-sm text-gray-12 outline-none transition-colors focus-visible:outline-2 focus-visible:outline-accent-8",
{
variants: {
variant: {
default: "bg-transparent",
outline: "border-gray-6 bg-gray-1",
muted: "bg-gray-3",
},
size: {
default: "gap-1 p-1",
sm: "gap-0.5 px-1 py-0.5",
},
},
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 : "div";
return (
<Comp
role="listitem"
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
);
}
function ItemMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & {
variant?: "default" | "icon" | "image";
}) {
return (
<div
data-slot="item-media"
data-variant={variant}
className={cn(
"flex shrink-0 items-center justify-center",
variant === "icon" &&
"size-2.5 rounded-sm border border-gray-6 bg-gray-3",
variant === "image" && "size-3 overflow-hidden rounded-sm",
className,
)}
{...props}
/>
);
}
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-content"
className={cn("flex min-w-0 flex-1 flex-col gap-0.5", className)}
{...props}
/>
);
}
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-title"
className={cn("flex items-center gap-0.5 font-medium text-gray-12", className)}
{...props}
/>
);
}
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<p
data-slot="item-description"
className={cn("text-sm text-gray-11", className)}
{...props}
/>
);
}
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="item-actions"
className={cn("flex items-center gap-0.5", 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-1",
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-1",
className,
)}
{...props}
/>
);
}
export {
Item,
ItemMedia,
ItemContent,
ItemActions,
ItemGroup,
ItemSeparator,
ItemTitle,
ItemDescription,
ItemHeader,
ItemFooter,
};