{"name":"item","type":"registry:component","title":"Item","description":"A versatile item component using named slots for flexible content composition. Includes group and separator components for organizing lists.","categories":["ui","list","web-component"],"author":"Lloyd Richards <lloyd.d.richards@gmail.com>","registryDependencies":["@lit/button"],"dependencies":["lucide-static"],"files":[{"path":"registry/ui/item/item.ts","type":"registry:ui","content":"import { cva, type VariantProps } from \"class-variance-authority\";\nimport { adoptStyles, html, LitElement, nothing, unsafeCSS } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { TW, tailwind } from \"@/registry/lib/tailwindMixin\";\nimport { cn } from \"@/registry/lib/utils\";\nimport itemSlottedCss from \"./item.slotted.css?inline\";\n\n/**\n * Item component variants for styling the main container.\n */\nexport const itemVariants = cva(\n  \"flex flex-col gap-4 transition-colors outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n  {\n    variants: {\n      variant: {\n        default: \"bg-background hover:bg-accent/50\",\n        outline: \"border bg-card hover:bg-accent/50\",\n        muted: \"bg-muted/50 hover:bg-muted text-muted-foreground\",\n      },\n      size: {\n        default: \"p-4 rounded-lg\",\n        sm: \"p-3 rounded-md text-sm\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\ntype ItemVariants = VariantProps<typeof itemVariants>;\n\nexport interface ItemProperties {\n  variant?: ItemVariants[\"variant\"];\n  size?: ItemVariants[\"size\"];\n  ariaLabel?: string | null;\n  ariaDescribedby?: string | null;\n  role?: string | null;\n}\n\n/**\n * A versatile item component using named slots for flexible content composition.\n *\n * @slot header - Optional header content above the main content area\n * @slot title - Title content (automatically styled via slotted CSS)\n * @slot description - Description content (automatically styled via slotted CSS)\n * @slot media - Optional icon/avatar/image area (positioned at the start)\n * @slot - Default slot for additional content\n * @slot actions - Optional action buttons/controls (positioned at the end)\n * @slot footer - Optional footer content below the main content area\n *\n * @example Basic usage with named slots\n * ```html\n * <ui-item variant=\"outline\">\n *   <div slot=\"media\" class=\"size-10 rounded-full bg-muted\">CN</div>\n *   <h3 slot=\"title\">Title</h3>\n *   <p slot=\"description\">Description text</p>\n *   <ui-button slot=\"actions\" size=\"sm\">Action</ui-button>\n * </ui-item>\n * ```\n *\n * @example With header and footer\n * ```html\n * <ui-item variant=\"outline\">\n *   <div slot=\"header\" class=\"aspect-square w-full rounded-sm bg-muted\"></div>\n *   <h3 slot=\"title\">Title</h3>\n *   <p slot=\"description\">Description text</p>\n *   <div slot=\"footer\" class=\"text-xs text-muted-foreground\">Footer text</div>\n * </ui-item>\n * ```\n */\nconst TwLitElement = TW(LitElement);\n\nconst itemSlottedStyles = unsafeCSS(itemSlottedCss);\n\n@customElement(\"ui-item\")\nexport class Item extends TwLitElement implements ItemProperties {\n  static styles = itemSlottedStyles;\n\n  @property({ type: String }) variant: ItemVariants[\"variant\"] = \"default\";\n  @property({ type: String }) size: ItemVariants[\"size\"] = \"default\";\n\n  @property({ type: String, attribute: \"aria-label\" }) accessor ariaLabel:\n    | string\n    | null = null;\n  @property({ type: String, attribute: \"aria-describedby\" })\n  accessor ariaDescribedby: string | null = null;\n  @property({ type: String, attribute: \"role\" }) accessor role: string | null =\n    null;\n\n  override connectedCallback() {\n    super.connectedCallback();\n    if (this.shadowRoot) {\n      adoptStyles(this.shadowRoot, [tailwind, itemSlottedStyles]);\n    }\n  }\n\n  override render() {\n    return html`\n      <div\n        data-slot=\"item\"\n        class=${itemVariants({\n          variant: this.variant,\n          size: this.size,\n          class: this.className,\n        })}\n        aria-label=${this.ariaLabel || nothing}\n        aria-describedby=${this.ariaDescribedby || nothing}\n        role=${this.role as \"\"}\n      >\n        <slot name=\"header\"></slot>\n        <div class=\"flex items-center gap-4 w-full min-w-0\">\n          <slot name=\"media\"></slot>\n          <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n            <slot data-slot=\"item-title\" name=\"title\"> </slot>\n            <slot data-slot=\"item-description\" name=\"description\"></slot>\n            <slot></slot>\n          </div>\n          <slot name=\"actions\"></slot>\n        </div>\n        <slot name=\"footer\"></slot>\n      </div>\n    `;\n  }\n}\n\n/**\n * Group container for organizing multiple items with consistent spacing.\n *\n * @example\n * ```html\n * <ui-item-group>\n *   <ui-item>Item 1</ui-item>\n *   <ui-item-separator></ui-item-separator>\n *   <ui-item>Item 2</ui-item>\n * </ui-item-group>\n * ```\n */\n@customElement(\"ui-item-group\")\nexport class ItemGroup extends TW(LitElement) {\n  override render() {\n    return html`\n      <div\n        data-slot=\"item-group\"\n        class=${cn(\"flex flex-col gap-2\", this.className)}\n        role=\"list\"\n      >\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\n/**\n * Visual separator for dividing items within a group.\n *\n * @example\n * ```html\n * <ui-item-group>\n *   <ui-item>Item 1</ui-item>\n *   <ui-item-separator></ui-item-separator>\n *   <ui-item>Item 2</ui-item>\n * </ui-item-group>\n * ```\n */\n@customElement(\"ui-item-separator\")\nexport class ItemSeparator extends TW(LitElement) {\n  override render() {\n    return html`\n      <div\n        data-slot=\"item-separator\"\n        class=${cn(\"bg-border h-px w-full\", this.className)}\n        role=\"separator\"\n      ></div>\n    `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    \"ui-item\": Item;\n    \"ui-item-group\": ItemGroup;\n    \"ui-item-separator\": ItemSeparator;\n  }\n}\n"},{"path":"registry/ui/item/item.stories.ts","type":"registry:ui","content":"import \"./item\";\nimport \"../button/button\";\nimport type { Meta, StoryObj } from \"@storybook/web-components-vite\";\nimport { html } from \"lit\";\nimport { unsafeSVG } from \"lit/directives/unsafe-svg.js\";\nimport { BadgeCheck, ChevronRight, Plus, ShieldAlert } from \"lucide-static\";\n\n/**\n * A versatile item component using named slots for flexible content composition.\n *\n * ## Slots\n * - `header` - Optional header content above main content\n * - `title` - Title content (automatically styled)\n * - `description` - Description content (automatically styled)\n * - `media` - Optional icon/avatar/image area\n * - (default) - Additional content area\n * - `actions` - Optional action buttons/controls\n * - `footer` - Optional footer content below main content\n *\n * ## Related components\n * - `ui-item-group` - Container for grouping items\n * - `ui-item-separator` - Visual divider between items\n */\nconst meta: Meta = {\n  title: \"ui/Item\",\n  component: \"ui-item\",\n  tags: [\"autodocs\"],\n  parameters: {\n    layout: \"centered\",\n  },\n};\n\nexport default meta;\n\ntype Story = StoryObj;\n\nexport const Default: Story = {\n  render: () => html`\n    <ui-item-group>\n      <ui-item variant=\"outline\">\n        <h3 slot=\"title\">Basic Item</h3>\n        <p slot=\"description\">\n          A simple item with title and description using named slots.\n        </p>\n        <ui-button slot=\"actions\" variant=\"outline\" size=\"sm\">Action</ui-button>\n      </ui-item>\n\n      <ui-item variant=\"outline\" size=\"sm\">\n        <div slot=\"media\" class=\"size-5 text-destructive\">\n          ${unsafeSVG(BadgeCheck)}\n        </div>\n        <h3 slot=\"title\">Your profile has been verified.</h3>\n        <div slot=\"actions\" class=\"size-4\">${unsafeSVG(ChevronRight)}</div>\n      </ui-item>\n    </ui-item-group>\n  `,\n};\n\nexport const Outline: Story = {\n  render: () => html`\n    <ui-item variant=\"outline\">\n      <div\n        slot=\"media\"\n        class=\"size-10 rounded-full bg-muted flex items-center justify-center\"\n      >\n        CN\n      </div>\n      <h3 slot=\"title\">Software Update Available</h3>\n      <p slot=\"description\">Version 2.0 is now available for download.</p>\n      <ui-button slot=\"actions\" size=\"sm\" variant=\"outline\">Update</ui-button>\n    </ui-item>\n  `,\n};\n\nexport const Muted: Story = {\n  render: () => html`\n    <ui-item variant=\"muted\">\n      <div\n        slot=\"media\"\n        class=\"size-10 rounded-md bg-muted/50 text-muted-foreground flex items-center justify-center\"\n      >\n        ${unsafeSVG(BadgeCheck)}\n      </div>\n      <h3 slot=\"title\">Account Verified</h3>\n      <p slot=\"description\">Your account has been successfully verified.</p>\n      <ui-button slot=\"actions\" size=\"sm\" variant=\"ghost\">Dismiss</ui-button>\n    </ui-item>\n  `,\n};\n\nexport const Small: Story = {\n  render: () => html`\n    <ui-item-group>\n      <ui-item variant=\"outline\" size=\"sm\">\n        <div\n          slot=\"media\"\n          class=\"size-8 rounded-full bg-muted flex items-center justify-center\"\n        >\n          CN\n        </div>\n        <h3 slot=\"title\">New message from shadcn</h3>\n        <p slot=\"description\">Hey, how are you doing?</p>\n      </ui-item>\n      <ui-item-separator></ui-item-separator>\n      <ui-item variant=\"outline\" size=\"sm\">\n        <div slot=\"media\">\n          <div\n            class=\"size-8 rounded-full bg-muted flex items-center justify-center\"\n          >\n            ML\n          </div>\n        </div>\n        <h3 slot=\"title\">New message from maxleiter</h3>\n        <p slot=\"description\">Check out this new feature!</p>\n      </ui-item>\n    </ui-item-group>\n  `,\n};\n\nexport const WithIcon: Story = {\n  render: () => html`\n    <ui-item variant=\"outline\">\n      <div\n        slot=\"media\"\n        class=\"size-10 rounded-md bg-muted/50 text-muted-foreground\"\n      >\n        ${unsafeSVG(ShieldAlert)}\n      </div>\n      <h3 slot=\"title\">Security Alert</h3>\n      <p slot=\"description\">New login detected from unknown device.</p>\n      <ui-button slot=\"actions\" size=\"sm\" variant=\"outline\">Review</ui-button>\n    </ui-item>\n  `,\n};\n\nexport const WithAvatar: Story = {\n  render: () => html`\n    <ui-item variant=\"outline\">\n      <div\n        slot=\"media\"\n        class=\"size-10 rounded-full bg-muted flex items-center justify-center\"\n      >\n        ER\n      </div>\n\n      <h3 slot=\"title\">Evil Rabbit</h3>\n      <p slot=\"description\">Last seen 5 months ago</p>\n      <ui-button\n        slot=\"actions\"\n        size=\"icon-sm\"\n        variant=\"outline\"\n        class=\"rounded-full\"\n        aria-label=\"Invite\"\n      >\n        ${unsafeSVG(Plus)}\n      </ui-button>\n    </ui-item>\n  `,\n};\n\nexport const WithGroup: Story = {\n  render: () => {\n    const people = [\n      { username: \"shadcn\", email: \"shadcn@vercel.com\" },\n      { username: \"maxleiter\", email: \"maxleiter@vercel.com\" },\n      { username: \"evilrabbit\", email: \"evilrabbit@vercel.com\" },\n    ];\n\n    return html`\n      <ui-item-group>\n        ${people.map(\n          (person, index) => html`\n            <ui-item>\n              <div\n                slot=\"media\"\n                class=\"size-10 rounded-full bg-muted flex items-center justify-center\"\n              >\n                ${person.username.charAt(0).toUpperCase()}\n              </div>\n              <h3 slot=\"title\">${person.username}</h3>\n              <p slot=\"description\">${person.email}</p>\n              <ui-button\n                slot=\"actions\"\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"rounded-full\"\n              >\n                ${unsafeSVG(Plus)}\n              </ui-button>\n            </ui-item>\n            ${\n              index !== people.length - 1\n                ? html`<ui-item-separator></ui-item-separator>`\n                : \"\"\n            }\n          `,\n        )}\n      </ui-item-group>\n    `;\n  },\n};\n\nexport const WithHeader: Story = {\n  render: () => {\n    const models = [\n      {\n        name: \"v0-1.5-sm\",\n        description: \"Everyday tasks and UI generation.\",\n      },\n      {\n        name: \"v0-1.5-lg\",\n        description: \"Advanced thinking or reasoning.\",\n      },\n      {\n        name: \"v0-2.0-mini\",\n        description: \"Open Source model for everyone.\",\n      },\n    ];\n\n    return html`\n      <ui-item-group class=\"grid grid-cols-3 gap-4\">\n        ${models.map(\n          (model) => html`\n            <ui-item variant=\"outline\">\n              <div\n                slot=\"header\"\n                class=\"aspect-square w-full rounded-sm bg-muted\"\n              ></div>\n              <h3 slot=\"title\">${model.name}</h3>\n              <p slot=\"description\">${model.description}</p>\n            </ui-item>\n          `,\n        )}\n      </ui-item-group>\n    `;\n  },\n};\n\nexport const WithFooter: Story = {\n  render: () => html`\n    <ui-item variant=\"outline\">\n      <div\n        slot=\"media\"\n        class=\"size-10 rounded-full bg-muted flex items-center justify-center\"\n      >\n        CN\n      </div>\n      <h3 slot=\"title\">Component Updated</h3>\n      <p slot=\"description\">\n        The item component has been refactored to use named slots.\n      </p>\n      <div slot=\"footer\" class=\"text-xs text-muted-foreground w-full\">\n        Last updated: 2 hours ago\n      </div>\n    </ui-item>\n  `,\n};\n\nexport const CustomClasses: Story = {\n  render: () => html`\n    <ui-item variant=\"outline\">\n      <!-- Custom classes on media slot are merged with defaults -->\n      <div\n        slot=\"media\"\n        class=\"size-12 border-2 border-primary rounded-lg bg-primary/10\"\n      >\n        ${unsafeSVG(BadgeCheck)}\n      </div>\n      <h3 slot=\"title\">Custom Styled Item</h3>\n      <p slot=\"description\">\n        This item demonstrates how custom classes on slotted elements are merged\n        with defaults.\n      </p>\n      <ui-button slot=\"actions\" size=\"sm\">View</ui-button>\n    </ui-item>\n  `,\n};\n\nexport const MixedApproach: Story = {\n  render: () => html`\n    <ui-item-group>\n      <!-- Using slots for structure -->\n      <ui-item variant=\"outline\">\n        <div\n          slot=\"header\"\n          class=\"text-xs font-semibold text-muted-foreground uppercase tracking-wide\"\n        >\n          Notification\n        </div>\n        <div\n          slot=\"media\"\n          class=\"size-10 rounded-md bg-blue-500/10 text-blue-500\"\n        >\n          ${unsafeSVG(BadgeCheck)}\n        </div>\n        <h3 slot=\"title\">Verification Complete</h3>\n        <p slot=\"description\">Your account has been verified successfully.</p>\n        <ui-button slot=\"actions\" size=\"sm\" variant=\"ghost\">Dismiss</ui-button>\n        <div slot=\"footer\" class=\"text-xs text-muted-foreground w-full\">\n          Just now\n        </div>\n      </ui-item>\n\n      <ui-item-separator></ui-item-separator>\n\n      <!-- Another item with different structure -->\n      <ui-item variant=\"outline\">\n        <div\n          slot=\"media\"\n          class=\"size-10 rounded-md bg-orange-500/10 text-orange-500\"\n        >\n          ${unsafeSVG(ShieldAlert)}\n        </div>\n        <h3 slot=\"title\">Security Alert</h3>\n        <p slot=\"description\">New login from unrecognized device.</p>\n        <ui-button slot=\"actions\" size=\"sm\" variant=\"outline\">Review</ui-button>\n      </ui-item>\n    </ui-item-group>\n  `,\n};\n\nexport const LayoutFlexibility: Story = {\n  render: () => html`\n    <ui-item-group>\n      <!-- Full width item -->\n      <ui-item variant=\"outline\" class=\"w-full max-w-2xl\">\n        <div slot=\"media\">\n          <div\n            class=\"size-10 rounded-full bg-muted flex items-center justify-center\"\n          >\n            CN\n          </div>\n        </div>\n        <h3 slot=\"title\">Full Width Item</h3>\n        <p slot=\"description\">\n          This item uses className forwarding to apply max-w-2xl.\n        </p>\n      </ui-item>\n\n      <!-- Fixed width item -->\n      <ui-item variant=\"outline\" class=\"w-96\">\n        <div slot=\"media\">\n          <div\n            class=\"size-10 rounded-full bg-muted flex items-center justify-center\"\n          >\n            ML\n          </div>\n        </div>\n        <h3 slot=\"title\">Fixed Width Item</h3>\n        <p slot=\"description\">This item has a fixed width of w-96.</p>\n      </ui-item>\n    </ui-item-group>\n  `,\n};\n"},{"path":"registry/ui/item/item.slotted.css","type":"registry:ui","content":"@reference \"../../styles/tailwind.global.css\";\n\n@layer base {\n  ::slotted([slot=\"header\"]) {\n    @apply w-full;\n  }\n\n  ::slotted([slot=\"title\"]) {\n    @apply font-medium leading-none m-0;\n  }\n\n  ::slotted([slot=\"description\"]) {\n    @apply text-muted-foreground text-sm m-0;\n  }\n\n  ::slotted([slot=\"media\"]) {\n    @apply flex shrink-0 items-center justify-center;\n  }\n\n  ::slotted([slot=\"actions\"]) {\n    @apply flex shrink-0 items-center gap-2;\n  }\n\n  ::slotted([slot=\"footer\"]) {\n    @apply w-full flex items-center;\n  }\n}\n"}]}