Components
Item
A generic list-row layout primitive with media/content/actions slots. Purely presentational.
Installation
npx kosmesis add itempnpm dlx kosmesis add itemyarn dlx kosmesis add itembunx kosmesis add itemInstall the following dependencies:
npm install class-variance-authoritypnpm add class-variance-authorityyarn add class-variance-authoritybun add class-variance-authorityCopy and paste the following code into your project.
import { cva, type VariantProps } from "class-variance-authority";import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";export const itemVariants = cva( "group/item flex w-full items-center rounded-md border border-transparent text-sm transition-colors", { variants: { variant: { default: "bg-transparent", outline: "border-border", muted: "bg-muted/50", }, size: { default: "gap-4 p-4", sm: "gap-2.5 px-3 py-2", }, }, defaultVariants: { variant: "default", size: "default", }, },);export interface ItemProps extends VariantProps<typeof itemVariants> { as?: "div" | "a" | "button"; href?: string; onClick?: (event: MouseEvent) => void; class?: string; id?: string; children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. */@Component()export class Item extends StatelessComponent<ItemProps> { render() { const { as: Tag = "div", variant, size, href, onClick, class: cls, id, children } = this.props; return ( <Tag id={id} href={Tag === "a" ? href : undefined} onClick={onClick} class={cn(itemVariants({ variant, size }), cls)}> {children} </Tag> ); }}export interface ItemSlotProps { class?: string; children?: Children;}@Component()export class ItemMedia extends StatelessComponent<ItemSlotProps> { render() { const { class: cls, children } = this.props; return ( <div class={cn("flex shrink-0 items-center justify-center [&_svg]:size-5", cls)}>{children}</div> ); }}@Component()export class ItemContent extends StatelessComponent<ItemSlotProps> { render() { const { class: cls, children } = this.props; return <div class={cn("flex flex-1 flex-col gap-0.5", cls)}>{children}</div>; }}@Component()export class ItemTitle extends StatelessComponent<ItemSlotProps> { render() { const { class: cls, children } = this.props; return <div class={cn("text-sm leading-snug font-medium", cls)}>{children}</div>; }}@Component()export class ItemDescription extends StatelessComponent<ItemSlotProps> { render() { const { class: cls, children } = this.props; return <div class={cn("text-sm leading-snug text-muted-foreground", cls)}>{children}</div>; }}@Component()export class ItemActions extends StatelessComponent<ItemSlotProps> { render() { const { class: cls, children } = this.props; return <div class={cn("flex shrink-0 items-center gap-2", cls)}>{children}</div>; }}@Component()export class ItemGroup extends StatelessComponent<ItemSlotProps> { render() { const { class: cls, children } = this.props; return ( <div role="list" class={cn("flex flex-col", cls)}> {children} </div> ); }}@Component()export class ItemSeparator extends StatelessComponent<{ class?: string }> { render() { const { class: cls } = this.props; return <div role="separator" class={cn("my-1 h-px bg-border", cls)} />; }}Install the following dependencies:
npm install @praxisjs/csspnpm add @praxisjs/cssyarn add @praxisjs/cssbun add @praxisjs/cssCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class ItemStyles extends Stylesheet { $root = this.css({ display: "flex", width: "100%", alignItems: "center", borderRadius: `calc(${t.radius} - 2px)`, border: "1px solid transparent", fontSize: "0.875rem", transition: "background-color 120ms ease, border-color 120ms ease", }); $variantDefault = this.css({ backgroundColor: "transparent" }); $variantOutline = this.css({ borderColor: t.border }); $variantMuted = this.css({ backgroundColor: `color-mix(in oklab, ${t.muted} 50%, transparent)` }); $sizeDefault = this.css({ gap: "1rem", padding: "1rem" }); $sizeSm = this.css({ gap: "0.625rem", padding: "0.5rem 0.75rem" }); $media = this.css({ display: "flex", flexShrink: 0, alignItems: "center", justifyContent: "center" }).on("& svg", { width: "1.25rem", height: "1.25rem", }); $content = this.css({ display: "flex", flex: "1 1 0%", flexDirection: "column", gap: "0.125rem" }); $title = this.css({ fontSize: "0.875rem", lineHeight: 1.4, fontWeight: 500 }); $description = this.css({ fontSize: "0.875rem", lineHeight: 1.4, color: t.mutedForeground }); $actions = this.css({ display: "flex", flexShrink: 0, alignItems: "center", gap: "0.5rem" }); $group = this.css({ display: "flex", flexDirection: "column" }); $separator = this.css({ margin: "0.25rem 0", height: "1px", backgroundColor: t.border });}export type ItemVariant = "default" | "outline" | "muted";export type ItemSize = "default" | "sm";export interface ItemProps { variant?: ItemVariant; size?: ItemSize; as?: "div" | "a" | "button"; href?: string; onClick?: (event: MouseEvent) => void; class?: string; id?: string; children?: Children;}/** Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. */@Component()export class Item extends StatelessComponent<ItemProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { as: Tag = "div", variant = "default", size = "default", href, onClick, class: cls, id, children } = this.props; const variants: Record<ItemVariant, string> = { default: this.$s.$variantDefault, outline: this.$s.$variantOutline, muted: this.$s.$variantMuted, }; const sizes: Record<ItemSize, string> = { default: this.$s.$sizeDefault, sm: this.$s.$sizeSm }; return ( <Tag id={id} href={Tag === "a" ? href : undefined} onClick={onClick} class={cx(this.$s.$root, variants[variant], sizes[size], cls)}> {children} </Tag> ); }}export interface ItemSlotProps { class?: string; children?: Children;}@Component()export class ItemMedia extends StatelessComponent<ItemSlotProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$media, cls)}>{children}</div>; }}@Component()export class ItemContent extends StatelessComponent<ItemSlotProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$content, cls)}>{children}</div>; }}@Component()export class ItemTitle extends StatelessComponent<ItemSlotProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$title, cls)}>{children}</div>; }}@Component()export class ItemDescription extends StatelessComponent<ItemSlotProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$description, cls)}>{children}</div>; }}@Component()export class ItemActions extends StatelessComponent<ItemSlotProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls, children } = this.props; return <div class={cx(this.$s.$actions, cls)}>{children}</div>; }}@Component()export class ItemGroup extends StatelessComponent<ItemSlotProps> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls, children } = this.props; return ( <div role="list" class={cx(this.$s.$group, cls)}> {children} </div> ); }}@Component()export class ItemSeparator extends StatelessComponent<{ class?: string }> { @Styled(ItemStyles) $s!: ItemStyles; render() { const { class: cls } = this.props; return <div role="separator" class={cx(this.$s.$separator, cls)} />; }}Examples
Usage
import { Button } from "@/components/ui/button";
import {
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemGroup,
ItemMedia,
ItemSeparator,
ItemTitle,
} from "@/components/ui/item";
<ItemGroup>
<Item variant="outline">
<ItemMedia>🔔</ItemMedia>
<ItemContent>
<ItemTitle>New comment</ItemTitle>
<ItemDescription>Someone replied to your post.</ItemDescription>
</ItemContent>
<ItemActions>
<Button size="sm" variant="ghost">View</Button>
</ItemActions>
</Item>
<ItemSeparator />
<Item variant="outline">
<ItemMedia>⭐</ItemMedia>
<ItemContent>
<ItemTitle>New star</ItemTitle>
<ItemDescription>Your repository got a new star.</ItemDescription>
</ItemContent>
</Item>
</ItemGroup>Props
| Prop | Type | Default |
|---|---|---|
variant | "default" | "outline" | "muted" | "default" |
size | "default" | "sm" | "default" |
as | "div" | "a" | "button" | "div" |
href | string | — only used when as="a" |
onClick | (event: MouseEvent) => void | — only relevant when as="a" or as="button" |