{
  "name": "conversation-list",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "conversation-list.tsx",
      "content": "\"use client\";\r\n\r\nimport { css, themeVars as theme } from \"@yugnex/core\";\r\nimport { useMemo, type HTMLAttributes, type ReactNode } from \"react\";\r\n\r\nconst listClass = css({\r\n  display: \"flex\",\r\n  flexDirection: \"column\",\r\n  gap: theme.space[0.5],\r\n  padding: theme.space[2],\r\n  margin: 0,\r\n  listStyle: \"none\",\r\n});\r\n\r\nconst groupLabelClass = css({\r\n  padding: `${theme.space[3]} ${theme.space[2]} ${theme.space[1]}`,\r\n  fontSize: theme.fontSize.xs,\r\n  fontWeight: theme.fontWeight.semibold,\r\n  letterSpacing: theme.letterSpacing.wide,\r\n  textTransform: \"uppercase\",\r\n  color: theme.color.mutedForeground,\r\n});\r\n\r\nconst itemClass = css({\r\n  display: \"flex\",\r\n  alignItems: \"center\",\r\n  gap: theme.space[2],\r\n  width: \"100%\",\r\n  padding: `${theme.space[2]} ${theme.space[2.5]}`,\r\n  borderRadius: theme.radius.sm,\r\n  border: \"none\",\r\n  background: \"transparent\",\r\n  color: theme.color.mutedForeground,\r\n  fontFamily: theme.fontFamily.sans,\r\n  fontSize: theme.fontSize.sm,\r\n  textAlign: \"left\",\r\n  cursor: \"pointer\",\r\n  transitionProperty: \"background-color, color\",\r\n  transitionDuration: theme.duration.fast,\r\n  \"&:hover\": { backgroundColor: theme.color.muted, color: theme.color.foreground },\r\n  \"&:focus-visible\": { outline: `2px solid ${theme.color.ring}`, outlineOffset: \"-2px\" },\r\n  '&[aria-current=\"true\"]': {\r\n    backgroundColor: theme.color.accent,\r\n    color: theme.color.accentForeground,\r\n    fontWeight: theme.fontWeight.medium,\r\n  },\r\n});\r\n\r\nconst titleClass = css({\r\n  flex: 1,\r\n  minWidth: 0,\r\n  overflow: \"hidden\",\r\n  textOverflow: \"ellipsis\",\r\n  whiteSpace: \"nowrap\",\r\n});\r\n\r\nconst trailingClass = css({\r\n  flexShrink: 0,\r\n  display: \"inline-flex\",\r\n  alignItems: \"center\",\r\n  gap: theme.space[1],\r\n  opacity: 0,\r\n  transitionProperty: \"opacity\",\r\n  transitionDuration: theme.duration.fast,\r\n  \"[data-nx-conversation]:hover &, [data-nx-conversation]:focus-within &\": { opacity: 1 },\r\n  '[aria-current=\"true\"] &': { opacity: 1 },\r\n});\r\n\r\nconst emptyClass = css({\r\n  padding: theme.space[6],\r\n  textAlign: \"center\",\r\n  fontSize: theme.fontSize.sm,\r\n  color: theme.color.mutedForeground,\r\n});\r\n\r\nexport interface Conversation {\r\n  id: string;\r\n  title: string;\r\n  /** Drives the Today / Yesterday / Previous 7 days grouping. */\r\n  updatedAt?: Date | number;\r\n  icon?: ReactNode;\r\n  /** Per-row trailing controls, revealed on hover or focus. */\r\n  actions?: ReactNode;\r\n}\r\n\r\nfunction startOfDay(date: Date): number {\r\n  return new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\r\n}\r\n\r\n/** Buckets by calendar day rather than elapsed hours, so \"Yesterday\" means yesterday's date. */\r\nfunction bucketFor(updatedAt: Date | number | undefined, now: Date): string {\r\n  if (updatedAt == null) return \"Earlier\";\r\n  const date = updatedAt instanceof Date ? updatedAt : new Date(updatedAt);\r\n  const days = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);\r\n  if (days <= 0) return \"Today\";\r\n  if (days === 1) return \"Yesterday\";\r\n  if (days <= 7) return \"Previous 7 days\";\r\n  if (days <= 30) return \"Previous 30 days\";\r\n  return \"Earlier\";\r\n}\r\n\r\nconst BUCKET_ORDER = [\"Today\", \"Yesterday\", \"Previous 7 days\", \"Previous 30 days\", \"Earlier\"];\r\n\r\nexport interface ConversationListProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\r\n  conversations: Conversation[];\r\n  activeId?: string;\r\n  onSelect?: (id: string) => void;\r\n  /** Turn off date bucketing and render one flat list. */\r\n  grouped?: boolean;\r\n  emptyMessage?: string;\r\n  /** Reference date for bucketing. Pass a fixed value to keep snapshots stable. */\r\n  now?: Date;\r\n}\r\n\r\n/**\r\n * The conversation history sidebar of an agent app — grouped by recency the way\r\n * every chat product does it, with per-row actions that appear on hover or\r\n * keyboard focus.\r\n */\r\nexport function ConversationList({\r\n  conversations,\r\n  activeId,\r\n  onSelect,\r\n  grouped = true,\r\n  emptyMessage = \"No conversations yet.\",\r\n  now,\r\n  className,\r\n  ...props\r\n}: ConversationListProps) {\r\n  const groups = useMemo(() => {\r\n    if (!grouped) return [[\"\", conversations]] as Array<[string, Conversation[]]>;\r\n    const reference = now ?? new Date();\r\n    const map = new Map<string, Conversation[]>();\r\n    for (const conversation of conversations) {\r\n      const bucket = bucketFor(conversation.updatedAt, reference);\r\n      const list = map.get(bucket);\r\n      if (list) list.push(conversation);\r\n      else map.set(bucket, [conversation]);\r\n    }\r\n    return BUCKET_ORDER.filter((b) => map.has(b)).map((b) => [b, map.get(b) ?? []] as [string, Conversation[]]);\r\n  }, [conversations, grouped, now]);\r\n\r\n  if (conversations.length === 0) {\r\n    return <p className={emptyClass}>{emptyMessage}</p>;\r\n  }\r\n\r\n  return (\r\n    <div className={className} {...props}>\r\n      {groups.map(([label, items]) => (\r\n        <div key={label || \"all\"}>\r\n          {label ? <p className={groupLabelClass}>{label}</p> : null}\r\n          <ul className={listClass}>\r\n            {items.map((conversation) => (\r\n              <li key={conversation.id} data-nx-conversation=\"\" style={{ listStyle: \"none\" }}>\r\n                <button\r\n                  type=\"button\"\r\n                  className={itemClass}\r\n                  aria-current={conversation.id === activeId}\r\n                  onClick={() => onSelect?.(conversation.id)}\r\n                >\r\n                  {conversation.icon}\r\n                  <span className={titleClass}>{conversation.title}</span>\r\n                  {conversation.actions ? <span className={trailingClass}>{conversation.actions}</span> : null}\r\n                </button>\r\n              </li>\r\n            ))}\r\n          </ul>\r\n        </div>\r\n      ))}\r\n    </div>\r\n  );\r\n}\r\n",
      "type": "registry:component"
    }
  ]
}