{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "feed-item",
  "title": "Feed Item",
  "description": "Feed Item from the Get IT Done design system.",
  "dependencies": [
    "class-variance-authority@^0.7.0",
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/empty-state.json",
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/recipes.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/feed-item.tsx",
      "content": "'use client';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { BellOff } from 'lucide-react';\nimport * as React from 'react';\n\nimport { focusRing, transitionBase } from '@/lib/recipes';\nimport { cn } from '@/lib/utils';\nimport { EmptyState } from '@/components/ui/empty-state';\nimport { useLabels, usePreset } from '@/components/ui/labels-context';\n\n// Figma: _Feed item base (Size sm|md, Supporting item, New dot) and Activity feed Type=Messages|Versions, Divider=Divider|Connector|Line|False.\n// Timeline and notification lists live in activity-feed.tsx; this is the generic selectable row list (versions, messages, inbox).\nexport type FeedListState = 'ready' | 'loading' | 'empty' | 'error';\n\n// Resets the browser list padding and margin so the rows align with the surrounding content.\nconst feedListVariants = cva('m-0 flex w-full list-none flex-col p-0', {\n  variants: {\n    divider: {\n      none: '',\n      // Hairline between items.\n      line: '[&>li+li]:border-t [&>li+li]:border-solid [&>li+li]:border-border',\n    },\n  },\n  defaultVariants: { divider: 'none' },\n});\n\nexport interface FeedListProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'>, VariantProps<typeof feedListVariants> {\n  // Accessible name of the list, e.g. \"Versions\".\n  'aria-label': string;\n  state?: FeedListState;\n  // Replaces the default empty panel.\n  emptyState?: React.ReactNode;\n  // Replaces the default error panel.\n  errorState?: React.ReactNode;\n  loadingRows?: number;\n  children?: React.ReactNode;\n}\n\nconst FeedList = React.forwardRef<HTMLElement, FeedListProps>(\n  ({ className, divider, state = 'ready', emptyState, errorState, loadingRows = 3, children, ...props }, ref) => {\n    const l = useLabels();\n    if (state === 'loading') {\n      return (\n        <div\n          ref={ref as React.Ref<HTMLDivElement>}\n          role=\"status\"\n          aria-busy=\"true\"\n          aria-label={l.loadingItem(props['aria-label'])}\n          className={cn('flex w-full flex-col gap-4', className)}\n        >\n          {Array.from({ length: loadingRows }, (_, i) => (\n            <div key={i} className=\"flex items-center gap-3\" aria-hidden=\"true\">\n              <span className=\"block h-avatar-sm w-avatar-sm shrink-0 rounded-full bg-overlay-skeleton-default motion-reduce:animate-none\" />\n              <span className=\"flex flex-1 flex-col gap-2\">\n                <span className=\"block h-3 w-1/2 rounded-item bg-overlay-skeleton-subtle motion-reduce:animate-none\" />\n                <span className=\"block h-3 w-3/4 rounded-item bg-overlay-skeleton-default motion-reduce:animate-none\" />\n              </span>\n            </div>\n          ))}\n        </div>\n      );\n    }\n    if (state === 'empty') {\n      return <>{emptyState ?? <EmptyState variant=\"first-use\" title={l.nothingHereYet} />}</>;\n    }\n    if (state === 'error') {\n      return <>{errorState ?? <EmptyState variant=\"error\" title={l.couldNotLoad} description={l.tryAgainInAMoment} />}</>;\n    }\n    return (\n      <ul\n        ref={ref as React.Ref<HTMLUListElement>}\n        className={cn(feedListVariants({ divider }), className)}\n        {...(props as React.HTMLAttributes<HTMLUListElement>)}\n      >\n        {children}\n      </ul>\n    );\n  },\n);\nFeedList.displayName = 'FeedList';\n\nexport interface FeedItemProps extends Omit<React.HTMLAttributes<HTMLLIElement>, 'title' | 'onSelect'> {\n  // Avatar or small status dot.\n  avatar?: React.ReactNode;\n  // Who did what. Rich text is allowed (names, links).\n  title: React.ReactNode;\n  // Relative or absolute time.\n  time?: React.ReactNode;\n  // File row, quoted text, labels, reply button.\n  supporting?: React.ReactNode;\n  size?: 'sm' | 'md';\n  // `compact` is the dense list row: 8px padding and gap, control radius, selected as a plain fill (no ring) and a small count pill. Defaults to `compact` in the zero preset, otherwise `default`.\n  density?: 'default' | 'compact';\n  // Unread marker (notifications).\n  unread?: boolean;\n  // Number of unread entries. Shows a count badge (capped at 99+) instead of the dot.\n  unreadCount?: number;\n  // Keeps the preview on a single line with an ellipsis. The full text stays in the DOM for screen readers.\n  truncateSupporting?: boolean;\n  // Muted conversation: shows a bell-off icon and a screen reader text.\n  muted?: boolean;\n  // Current row (versions).\n  selected?: boolean;\n  // Makes the whole row a button.\n  onSelect?: () => void;\n}\n\nconst FeedItem = React.forwardRef<HTMLLIElement, FeedItemProps>(\n  ({ className, avatar, title, time, supporting, size = 'md', density: densityProp, unread = false, unreadCount, truncateSupporting = false, muted = false, selected = false, onSelect, ...props }, ref) => {\n    const l = useLabels();\n    const zero = usePreset() === 'zero';\n    const density = densityProp ?? (zero ? 'compact' : 'default');\n    const compact = density === 'compact';\n    // Selected rows and button rows sit on the muted fill (selected, hover), where the plain subtle role drops under 4.5:1.\n    const subtle = selected || onSelect ? 'text-text-subtle-on-muted' : 'text-text-subtle';\n    const content = (\n      <>\n        {avatar ? <span className=\"shrink-0\">{avatar}</span> : null}\n        <span className=\"flex min-w-0 flex-1 flex-col gap-1\">\n          <span className=\"flex items-baseline justify-between gap-2\">\n            <span className={cn('min-w-0 text-text-default', size === 'sm' ? 'typography-ui' : 'typography-ui-medium')}>{title}</span>\n            {time || muted ? (\n              <span className={cn('typography-ui flex shrink-0 items-center gap-1', subtle)}>\n                {muted ? (\n                  <>\n                    <BellOff className=\"h-icon-xs w-icon-xs\" aria-hidden=\"true\" />\n                    <span className=\"sr-only\">{l.muted}</span>\n                  </>\n                ) : null}\n                {time}\n              </span>\n            ) : null}\n          </span>\n          {supporting ? (\n            <span\n              className={cn('typography-ui block', subtle, truncateSupporting && 'truncate')}\n              title={truncateSupporting && typeof supporting === 'string' ? supporting : undefined}\n            >\n              {supporting}\n            </span>\n          ) : null}\n        </span>\n        {unreadCount !== undefined && unreadCount > 0 ? (\n          <span\n            role=\"img\"\n            aria-label={l.unreadCount(unreadCount)}\n            className={cn(\n              'flex shrink-0 items-center justify-center rounded-full bg-background-brand-bold-default text-text-on-brand',\n              compact ? 'typography-micro h-4 [min-width:theme(spacing.4)] px-label font-semibold' : 'typography-ui-sm-semibold h-5 [min-width:theme(spacing.5)] px-1',\n            )}\n          >\n            {unreadCount > 99 ? '99+' : unreadCount}\n          </span>\n        ) : unread ? (\n          <span role=\"img\" aria-label={l.unread} className=\"mt-2 h-2 w-2 shrink-0 rounded-full bg-background-brand-bold-default\" />\n        ) : null}\n      </>\n    );\n    // Selected is a tinted fill plus an inset ring: the ring keeps 3:1 contrast on grey sidebar and muted surfaces where the fill alone vanishes. border-focus is used because it holds 3:1 in every theme, which the brand coloured icon-selected does not (yellow, lime, amber).\n    // Hover uses muted-strong because muted equals the grey sidebar surface in light mode (1.0 contrast, invisible); muted-strong keeps >= 1.3 luminance step against sidebar, muted and background in light and dark.\n    const rowClass = cn(\n      'flex w-full items-start text-left',\n      compact ? 'gap-2 rounded-control p-2' : 'gap-3 rounded-item px-3 py-2',\n      selected && (compact ? 'bg-muted' : 'bg-background-selected-default ring-2 ring-inset ring-border-focus'),\n    );\n    return (\n      <li ref={ref} className={cn('list-none', className)} aria-current={selected ? 'true' : undefined} {...props}>\n        {onSelect ? (\n          <button type=\"button\" onClick={onSelect} className={cn(rowClass, 'cursor-pointer hover:bg-muted-strong active:bg-muted-strong', ...transitionBase, ...focusRing)}>\n            {content}\n          </button>\n        ) : (\n          <div className={rowClass}>{content}</div>\n        )}\n      </li>\n    );\n  },\n);\nFeedItem.displayName = 'FeedItem';\n\nexport { FeedItem, FeedList, feedListVariants };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}