{
  "slug": "task-card",
  "name": "Task Card",
  "description": "Layered task cards with colored priority bands, recessed dashed outlines, stacked avatars and soft shadows.",
  "summary": "A customizable task card with a colored priority band, recessed dashed outline, stacked assignee avatars, status badge, counts and due date, in light and dark themes.",
  "category": "cards",
  "tags": [
    "card",
    "task",
    "kanban",
    "priority",
    "project",
    "avatars",
    "shadow",
    "gsap"
  ],
  "dependencies": [
    "gsap",
    "@gsap/react",
    "@radix-ui/react-avatar",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "avatar"
  ],
  "version": "1.0.0",
  "createdDate": "2026-10-03",
  "updatedDate": "2026-10-03",
  "interactive": true,
  "supportsColor": true,
  "highlights": [
    "Four colored priority bands with custom label and color overrides",
    "Recessed surface, dashed inset outline and adjustable layered shadows",
    "Stacked avatars with image fallbacks and an overflow badge",
    "Status badges, comment/link/file counts and calendar-safe due dates",
    "Optional title action through onOpen",
    "Subtle GSAP hover and focus lift with reduced-motion support"
  ],
  "anatomy": [
    "<article> (Task card with named heading and layered outer shadow)",
    "<div> (Colored priority band behind the recessed body)",
    "<span> (Inset dashed outline)",
    "<ul> (Stacked assignee avatars and overflow badge)",
    "<footer> (Task metadata and semantic due date)"
  ],
  "physics": {
    "engine": "GSAP",
    "description": "A reversible, scoped tween lifts the card on hover or focus without changing its layout.",
    "parameters": [
      {
        "label": "Duration",
        "value": "0.35 seconds"
      },
      {
        "label": "Ease",
        "value": "power3.out"
      },
      {
        "label": "Lift",
        "value": "0–10 pixels"
      }
    ]
  },
  "accessibility": {
    "role": "article",
    "aria": "The heading names each card. Avatar fallbacks expose assignee names, counts have descriptive labels, and due dates use a time element.",
    "reducedMotion": "Hover and focus motion is disabled when prefers-reduced-motion is enabled.",
    "keyboard": [
      {
        "key": "Tab",
        "description": "Focus the title button when onOpen is provided."
      },
      {
        "key": "Enter / Space",
        "description": "Activate the title button (calls onOpen)."
      }
    ]
  },
  "guidelines": {
    "recommended": [
      "Task and project dashboards",
      "Kanban columns and workflow previews",
      "Assigned work and priority summaries"
    ],
    "bestPractices": [
      "Supply an ISO calendar date for dueDate.",
      "Keep descriptions concise; longer descriptions are clamped to two lines.",
      "Provide meaningful names for avatar fallbacks.",
      "Use onOpen to connect the title to task details.",
      "Keep a short priority label and status for compact layouts."
    ]
  },
  "props": [
    {
      "name": "title",
      "type": "string",
      "description": "Task heading.",
      "required": true
    },
    {
      "name": "description",
      "type": "string",
      "description": "Optional description, clamped to two lines."
    },
    {
      "name": "priority",
      "type": "\"moderate\" | \"urgent\" | \"low\" | \"onboarding\"",
      "description": "Built-in priority label and color.",
      "defaultValue": "\"moderate\""
    },
    {
      "name": "priorityLabel",
      "type": "string",
      "description": "Override the priority band text."
    },
    {
      "name": "priorityColor",
      "type": "string",
      "description": "Override the priority band with any CSS color."
    },
    {
      "name": "status",
      "type": "string",
      "description": "Status badge text.",
      "defaultValue": "\"In Progress\""
    },
    {
      "name": "assignees",
      "type": "TaskCardAssignee[]",
      "description": "Names and optional image URLs, with initial fallbacks.",
      "defaultValue": "[]"
    },
    {
      "name": "maxAssignees",
      "type": "number",
      "description": "Maximum visible avatars; remaining people appear in an overflow badge.",
      "defaultValue": "4"
    },
    {
      "name": "comments",
      "type": "number",
      "description": "Nonnegative comment count.",
      "defaultValue": "8"
    },
    {
      "name": "links",
      "type": "number",
      "description": "Nonnegative link count.",
      "defaultValue": "4"
    },
    {
      "name": "files",
      "type": "number",
      "description": "Nonnegative file count.",
      "defaultValue": "12"
    },
    {
      "name": "dueDate",
      "type": "string",
      "description": "Optional ISO calendar date (YYYY-MM-DD), displayed as DD/MM/YYYY without time-zone shifts."
    },
    {
      "name": "theme",
      "type": "\"dark\" | \"light\"",
      "description": "Theme for surfaces, borders and metadata.",
      "defaultValue": "\"dark\""
    },
    {
      "name": "radius",
      "type": "number",
      "description": "Corner radius, clamped to 8–36 pixels.",
      "defaultValue": "20"
    },
    {
      "name": "padding",
      "type": "number",
      "description": "Content and footer inset, clamped to 12–32 pixels.",
      "defaultValue": "18"
    },
    {
      "name": "shadow",
      "type": "number",
      "description": "Layered shadow intensity, from 0 to 1.",
      "defaultValue": "0.7"
    },
    {
      "name": "hoverLift",
      "type": "number",
      "description": "Hover and keyboard-focus lift, from 0 to 10 pixels; 0 disables motion.",
      "defaultValue": "3"
    },
    {
      "name": "avatarSize",
      "type": "number",
      "description": "Avatar diameter, clamped to 20–44 pixels.",
      "defaultValue": "30"
    },
    {
      "name": "bandHeight",
      "type": "number",
      "description": "Minimum priority band height, clamped to 38–60 pixels. Longer labels wrap to two lines.",
      "defaultValue": "38"
    },
    {
      "name": "dashedBorder",
      "type": "boolean",
      "description": "Show the recessed dashed outline.",
      "defaultValue": "true"
    },
    {
      "name": "showAssignees",
      "type": "boolean",
      "description": "Show avatar group.",
      "defaultValue": "true"
    },
    {
      "name": "showStatus",
      "type": "boolean",
      "description": "Show status badge.",
      "defaultValue": "true"
    },
    {
      "name": "showFooter",
      "type": "boolean",
      "description": "Show counts and due date.",
      "defaultValue": "true"
    },
    {
      "name": "surfaceColor",
      "type": "string",
      "description": "Override the recessed card surface color."
    },
    {
      "name": "textColor",
      "type": "string",
      "description": "Override the main text color."
    },
    {
      "name": "className",
      "type": "string",
      "description": "Additional classes for the article."
    },
    {
      "name": "onOpen",
      "type": "() => void",
      "description": "Makes the title a keyboard-accessible open button."
    }
  ],
  "usage": "import { TaskCard } from \"@/components/ui/task-card\";\n\nexport function TaskCardDemo() {\n  return (\n    <TaskCard\n      title=\"Ship onboarding flow\"\n      description=\"Finish the empty states and copy review.\"\n      priority=\"urgent\"\n      status=\"In Progress\"\n      assignees={[{ name: \"Ada Lovelace\" }, { name: \"Alan Turing\" }]}\n      dueDate=\"2026-10-20\"\n    />\n  );\n}",
  "files": [
    {
      "name": "task-card.tsx",
      "path": "src/registry/ui/task-card.tsx",
      "code": "\"use client\";\n\nimport { useId, useRef, type CSSProperties } from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { FolderClosed, Link2, MessagesSquare } from \"lucide-react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport const TASK_CARD_PRIORITIES = {\n  moderate: { label: \"Moderate priority\", color: \"#ff7415\" },\n  urgent: { label: \"Urgent\", color: \"#fa454b\" },\n  low: { label: \"Low priority\", color: \"#36ad71\" },\n  onboarding: { label: \"On boarding\", color: \"#47b3f5\" },\n} as const;\n\nexport type TaskCardPriority = keyof typeof TASK_CARD_PRIORITIES;\n\nexport interface TaskCardAssignee {\n  name: string;\n  src?: string;\n}\n\nexport interface TaskCardProps {\n  title: string;\n  description?: string;\n  priority?: TaskCardPriority;\n  priorityLabel?: string;\n  priorityColor?: string;\n  status?: string;\n  assignees?: TaskCardAssignee[];\n  maxAssignees?: number;\n  comments?: number;\n  links?: number;\n  files?: number;\n  dueDate?: string;\n  theme?: \"dark\" | \"light\";\n  radius?: number;\n  padding?: number;\n  shadow?: number;\n  hoverLift?: number;\n  avatarSize?: number;\n  bandHeight?: number;\n  dashedBorder?: boolean;\n  showAssignees?: boolean;\n  showStatus?: boolean;\n  showFooter?: boolean;\n  surfaceColor?: string;\n  textColor?: string;\n  className?: string;\n  onOpen?: () => void;\n}\n\nconst bounded = (value: number, fallback: number, min: number, max: number) =>\n  Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;\n\nexport function TaskCard({\n  title,\n  description,\n  priority = \"moderate\",\n  priorityLabel,\n  priorityColor,\n  status = \"In Progress\",\n  assignees = [],\n  maxAssignees = 4,\n  comments = 8,\n  links = 4,\n  files = 12,\n  dueDate,\n  theme = \"dark\",\n  radius = 20,\n  padding = 18,\n  shadow = 0.7,\n  hoverLift = 3,\n  avatarSize = 30,\n  bandHeight = 38,\n  dashedBorder = true,\n  showAssignees = true,\n  showStatus = true,\n  showFooter = true,\n  surfaceColor,\n  textColor,\n  className,\n  onOpen,\n}: TaskCardProps) {\n  const rootRef = useRef<HTMLElement>(null);\n  const titleId = useId();\n  const lift = bounded(hoverLift, 3, 0, 10);\n  useGSAP(\n    () => {\n      const root = rootRef.current;\n      if (!root || !lift) return;\n      const media = gsap.matchMedia();\n      media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n        const hover = window.matchMedia(\"(hover: hover)\");\n        const motion = gsap.to(root, {\n          y: -lift,\n          duration: 0.35,\n          ease: \"power3.out\",\n          paused: true,\n        });\n        const enter = () => {\n          if (hover.matches) motion.play();\n        };\n        const focus = () => motion.play();\n        const leave = () => {\n          if (!root.contains(document.activeElement)) motion.reverse();\n        };\n        const blur = () => {\n          if (!root.matches(\":hover\")) motion.reverse();\n        };\n        root.addEventListener(\"pointerenter\", enter);\n        root.addEventListener(\"pointerleave\", leave);\n        root.addEventListener(\"focusin\", focus);\n        root.addEventListener(\"focusout\", blur);\n        return () => {\n          root.removeEventListener(\"pointerenter\", enter);\n          root.removeEventListener(\"pointerleave\", leave);\n          root.removeEventListener(\"focusin\", focus);\n          root.removeEventListener(\"focusout\", blur);\n        };\n      });\n      return () => media.revert();\n    },\n    { scope: rootRef, dependencies: [lift], revertOnUpdate: true },\n  );\n\n  const light = theme === \"light\";\n  const depth = bounded(shadow, 0.7, 0, 1);\n  const curve = bounded(radius, 20, 8, 36);\n  const inset = bounded(padding, 18, 12, 32);\n  const size = bounded(avatarSize, 30, 20, 44);\n  const limit = Math.floor(bounded(maxAssignees, 4, 1, 8));\n  const date = dueDate ? new Date(`${dueDate}T00:00:00Z`) : null;\n  const validDate = date && Number.isFinite(date.getTime());\n  const dateLabel = validDate\n    ? new Intl.DateTimeFormat(\"en-GB\", {\n        day: \"2-digit\",\n        month: \"2-digit\",\n        year: \"numeric\",\n        timeZone: \"UTC\",\n      }).format(date)\n    : undefined;\n  const base = TASK_CARD_PRIORITIES[priority] ?? TASK_CARD_PRIORITIES.moderate;\n  const visibleAssignees = assignees.slice(0, limit);\n  const overflow = assignees.length - visibleAssignees.length;\n\n  return (\n    <article\n      ref={rootRef}\n      aria-labelledby={titleId}\n      data-slot=\"task-card\"\n      data-priority={priority}\n      className={cn(\"relative isolate w-full min-w-0 font-sans\", className)}\n      style={\n        {\n          \"--task-radius\": `${curve}px`,\n          \"--task-inset\": `${inset}px`,\n          \"--task-surface\": surfaceColor ?? (light ? \"#ffffff\" : \"#202020\"),\n          \"--task-text\": textColor ?? (light ? \"#202124\" : \"#f4f4f4\"),\n          \"--task-muted\": light ? \"#74777c\" : \"#96989b\",\n          \"--task-border\": light ? \"#d4d5d8\" : \"#555658\",\n          \"--task-status\": light ? \"#e7e7e8\" : \"#474747\",\n          borderRadius: curve,\n          background: light ? \"#f2f2f3\" : \"#171717\",\n          border: `1px solid ${light ? \"#dddddf\" : \"#292929\"}`,\n          boxShadow: `0 ${12 * depth}px ${28 * depth}px -${8 * depth}px rgb(0 0 0 / ${depth * (light ? 0.16 : 0.4)}), 0 2px 4px rgb(0 0 0 / ${depth * 0.16}), inset 0 1px 0 rgb(255 255 255 / ${depth * 0.06})`,\n          color: \"var(--task-text)\",\n        } as CSSProperties\n      }\n    >\n      <div\n        data-slot=\"task-card-priority\"\n        className=\"flex items-start justify-center overflow-hidden px-3 pt-1.5 pb-3 text-center text-[13px] font-semibold uppercase tracking-[0.045em] text-white\"\n        style={{\n          minHeight: bounded(bandHeight, 38, 38, 60),\n          borderRadius: `${curve - 1}px ${curve - 1}px 0 0`,\n          background: `linear-gradient(105deg, color-mix(in srgb, ${priorityColor ?? base.color}, white 4%), ${priorityColor ?? base.color})`,\n          textShadow: \"0 1px 2px rgb(0 0 0 / 0.15)\",\n        }}\n      >\n        <span className=\"min-w-0 line-clamp-2 wrap-anywhere\">\n          {priorityLabel ?? base.label}\n        </span>\n      </div>\n      <div\n        className=\"relative -mt-3 rounded-(--task-radius) bg-(--task-surface)\"\n        style={{ padding: inset, boxShadow: \"0 -1px 0 rgb(0 0 0 / 0.2)\" }}\n      >\n        {dashedBorder && (\n          <span\n            data-slot=\"task-card-outline\"\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-1 border border-dashed border-(--task-border)\"\n            style={{ borderRadius: Math.max(4, curve - 4) }}\n          />\n        )}\n        <h3\n          id={titleId}\n          className=\"text-[19px] leading-snug font-medium tracking-[-0.04em] wrap-anywhere\"\n        >\n          {onOpen ? (\n            <button\n              type=\"button\"\n              onClick={onOpen}\n              className=\"cursor-pointer rounded-sm text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current\"\n              aria-label={`Open ${title || \"Untitled task\"}`}\n            >\n              {title || \"Untitled task\"}\n            </button>\n          ) : (\n            title || \"Untitled task\"\n          )}\n        </h3>\n        {description && (\n          <p className=\"mt-2.5 line-clamp-2 text-[15px] leading-[1.45] tracking-[-0.025em] text-(--task-muted) wrap-anywhere\">\n            {description}\n          </p>\n        )}\n        {((showAssignees && assignees.length > 0) ||\n          (showStatus && status)) && (\n          <div className=\"mt-4 flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-2\">\n            {showAssignees && assignees.length > 0 && (\n              <ul aria-label=\"Assignees\" className=\"flex -space-x-3\">\n                {visibleAssignees.map((person, index) => (\n                  <li key={`${person.name}-${index}`} title={person.name}>\n                    <Avatar\n                      className=\"border-[1.5px] border-white bg-zinc-300 ring-[1.5px] ring-black\"\n                      style={{ width: size, height: size }}\n                    >\n                      <AvatarImage\n                        src={person.src}\n                        alt={person.name}\n                        className=\"object-cover\"\n                      />\n                      <AvatarFallback\n                        className=\"bg-zinc-300 text-[10px] font-semibold text-zinc-800\"\n                        aria-label={person.name}\n                      >\n                        {person.name\n                          .split(/\\s+/)\n                          .filter(Boolean)\n                          .slice(0, 2)\n                          .map((word) => word[0])\n                          .join(\"\") || \"?\"}\n                      </AvatarFallback>\n                    </Avatar>\n                  </li>\n                ))}\n                {overflow > 0 && (\n                  <li\n                    className=\"relative flex items-center justify-center rounded-full border border-white bg-zinc-700 text-[10px] font-medium text-white ring-[1.5px] ring-black\"\n                    style={{ width: size, height: size }}\n                    aria-label={`${overflow} more assignees: ${assignees\n                      .slice(limit)\n                      .map((person) => person.name)\n                      .join(\", \")}`}\n                  >\n                    +{overflow}\n                  </li>\n                )}\n              </ul>\n            )}\n            {showStatus && status && (\n              <span className=\"ml-auto max-w-full rounded-[5px] bg-(--task-status) px-2.5 py-1.5 text-[13px] leading-snug tracking-[-0.03em] wrap-anywhere\">\n                {status}\n              </span>\n            )}\n          </div>\n        )}\n      </div>\n      {showFooter && (\n        <footer\n          className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-2 py-3 text-[16px] tabular-nums\"\n          style={{ paddingInline: inset }}\n        >\n          <div className=\"flex items-center gap-4\">\n            {[\n              { Icon: MessagesSquare, value: comments, label: \"comments\" },\n              { Icon: Link2, value: links, label: \"links\" },\n              { Icon: FolderClosed, value: files, label: \"files\" },\n            ].map(({ Icon, value, label }) => (\n              <span\n                key={label}\n                className=\"flex items-center gap-1.5\"\n                aria-label={`${Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0} ${label}`}\n              >\n                <Icon\n                  size={17}\n                  strokeWidth={1.6}\n                  className=\"text-(--task-muted)\"\n                  aria-hidden=\"true\"\n                />\n                <span aria-hidden=\"true\">\n                  {Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0}\n                </span>\n              </span>\n            ))}\n          </div>\n          {dateLabel && (\n            <time\n              dateTime={dueDate}\n              className=\"ml-auto text-(--task-muted)\"\n              aria-label={`Due ${dateLabel}`}\n            >\n              {dateLabel}\n            </time>\n          )}\n        </footer>\n      )}\n    </article>\n  );\n}\n\nexport default TaskCard;\n",
      "content": "\"use client\";\n\nimport { useId, useRef, type CSSProperties } from \"react\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { FolderClosed, Link2, MessagesSquare } from \"lucide-react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport const TASK_CARD_PRIORITIES = {\n  moderate: { label: \"Moderate priority\", color: \"#ff7415\" },\n  urgent: { label: \"Urgent\", color: \"#fa454b\" },\n  low: { label: \"Low priority\", color: \"#36ad71\" },\n  onboarding: { label: \"On boarding\", color: \"#47b3f5\" },\n} as const;\n\nexport type TaskCardPriority = keyof typeof TASK_CARD_PRIORITIES;\n\nexport interface TaskCardAssignee {\n  name: string;\n  src?: string;\n}\n\nexport interface TaskCardProps {\n  title: string;\n  description?: string;\n  priority?: TaskCardPriority;\n  priorityLabel?: string;\n  priorityColor?: string;\n  status?: string;\n  assignees?: TaskCardAssignee[];\n  maxAssignees?: number;\n  comments?: number;\n  links?: number;\n  files?: number;\n  dueDate?: string;\n  theme?: \"dark\" | \"light\";\n  radius?: number;\n  padding?: number;\n  shadow?: number;\n  hoverLift?: number;\n  avatarSize?: number;\n  bandHeight?: number;\n  dashedBorder?: boolean;\n  showAssignees?: boolean;\n  showStatus?: boolean;\n  showFooter?: boolean;\n  surfaceColor?: string;\n  textColor?: string;\n  className?: string;\n  onOpen?: () => void;\n}\n\nconst bounded = (value: number, fallback: number, min: number, max: number) =>\n  Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;\n\nexport function TaskCard({\n  title,\n  description,\n  priority = \"moderate\",\n  priorityLabel,\n  priorityColor,\n  status = \"In Progress\",\n  assignees = [],\n  maxAssignees = 4,\n  comments = 8,\n  links = 4,\n  files = 12,\n  dueDate,\n  theme = \"dark\",\n  radius = 20,\n  padding = 18,\n  shadow = 0.7,\n  hoverLift = 3,\n  avatarSize = 30,\n  bandHeight = 38,\n  dashedBorder = true,\n  showAssignees = true,\n  showStatus = true,\n  showFooter = true,\n  surfaceColor,\n  textColor,\n  className,\n  onOpen,\n}: TaskCardProps) {\n  const rootRef = useRef<HTMLElement>(null);\n  const titleId = useId();\n  const lift = bounded(hoverLift, 3, 0, 10);\n  useGSAP(\n    () => {\n      const root = rootRef.current;\n      if (!root || !lift) return;\n      const media = gsap.matchMedia();\n      media.add(\"(prefers-reduced-motion: no-preference)\", () => {\n        const hover = window.matchMedia(\"(hover: hover)\");\n        const motion = gsap.to(root, {\n          y: -lift,\n          duration: 0.35,\n          ease: \"power3.out\",\n          paused: true,\n        });\n        const enter = () => {\n          if (hover.matches) motion.play();\n        };\n        const focus = () => motion.play();\n        const leave = () => {\n          if (!root.contains(document.activeElement)) motion.reverse();\n        };\n        const blur = () => {\n          if (!root.matches(\":hover\")) motion.reverse();\n        };\n        root.addEventListener(\"pointerenter\", enter);\n        root.addEventListener(\"pointerleave\", leave);\n        root.addEventListener(\"focusin\", focus);\n        root.addEventListener(\"focusout\", blur);\n        return () => {\n          root.removeEventListener(\"pointerenter\", enter);\n          root.removeEventListener(\"pointerleave\", leave);\n          root.removeEventListener(\"focusin\", focus);\n          root.removeEventListener(\"focusout\", blur);\n        };\n      });\n      return () => media.revert();\n    },\n    { scope: rootRef, dependencies: [lift], revertOnUpdate: true },\n  );\n\n  const light = theme === \"light\";\n  const depth = bounded(shadow, 0.7, 0, 1);\n  const curve = bounded(radius, 20, 8, 36);\n  const inset = bounded(padding, 18, 12, 32);\n  const size = bounded(avatarSize, 30, 20, 44);\n  const limit = Math.floor(bounded(maxAssignees, 4, 1, 8));\n  const date = dueDate ? new Date(`${dueDate}T00:00:00Z`) : null;\n  const validDate = date && Number.isFinite(date.getTime());\n  const dateLabel = validDate\n    ? new Intl.DateTimeFormat(\"en-GB\", {\n        day: \"2-digit\",\n        month: \"2-digit\",\n        year: \"numeric\",\n        timeZone: \"UTC\",\n      }).format(date)\n    : undefined;\n  const base = TASK_CARD_PRIORITIES[priority] ?? TASK_CARD_PRIORITIES.moderate;\n  const visibleAssignees = assignees.slice(0, limit);\n  const overflow = assignees.length - visibleAssignees.length;\n\n  return (\n    <article\n      ref={rootRef}\n      aria-labelledby={titleId}\n      data-slot=\"task-card\"\n      data-priority={priority}\n      className={cn(\"relative isolate w-full min-w-0 font-sans\", className)}\n      style={\n        {\n          \"--task-radius\": `${curve}px`,\n          \"--task-inset\": `${inset}px`,\n          \"--task-surface\": surfaceColor ?? (light ? \"#ffffff\" : \"#202020\"),\n          \"--task-text\": textColor ?? (light ? \"#202124\" : \"#f4f4f4\"),\n          \"--task-muted\": light ? \"#74777c\" : \"#96989b\",\n          \"--task-border\": light ? \"#d4d5d8\" : \"#555658\",\n          \"--task-status\": light ? \"#e7e7e8\" : \"#474747\",\n          borderRadius: curve,\n          background: light ? \"#f2f2f3\" : \"#171717\",\n          border: `1px solid ${light ? \"#dddddf\" : \"#292929\"}`,\n          boxShadow: `0 ${12 * depth}px ${28 * depth}px -${8 * depth}px rgb(0 0 0 / ${depth * (light ? 0.16 : 0.4)}), 0 2px 4px rgb(0 0 0 / ${depth * 0.16}), inset 0 1px 0 rgb(255 255 255 / ${depth * 0.06})`,\n          color: \"var(--task-text)\",\n        } as CSSProperties\n      }\n    >\n      <div\n        data-slot=\"task-card-priority\"\n        className=\"flex items-start justify-center overflow-hidden px-3 pt-1.5 pb-3 text-center text-[13px] font-semibold uppercase tracking-[0.045em] text-white\"\n        style={{\n          minHeight: bounded(bandHeight, 38, 38, 60),\n          borderRadius: `${curve - 1}px ${curve - 1}px 0 0`,\n          background: `linear-gradient(105deg, color-mix(in srgb, ${priorityColor ?? base.color}, white 4%), ${priorityColor ?? base.color})`,\n          textShadow: \"0 1px 2px rgb(0 0 0 / 0.15)\",\n        }}\n      >\n        <span className=\"min-w-0 line-clamp-2 wrap-anywhere\">\n          {priorityLabel ?? base.label}\n        </span>\n      </div>\n      <div\n        className=\"relative -mt-3 rounded-(--task-radius) bg-(--task-surface)\"\n        style={{ padding: inset, boxShadow: \"0 -1px 0 rgb(0 0 0 / 0.2)\" }}\n      >\n        {dashedBorder && (\n          <span\n            data-slot=\"task-card-outline\"\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-1 border border-dashed border-(--task-border)\"\n            style={{ borderRadius: Math.max(4, curve - 4) }}\n          />\n        )}\n        <h3\n          id={titleId}\n          className=\"text-[19px] leading-snug font-medium tracking-[-0.04em] wrap-anywhere\"\n        >\n          {onOpen ? (\n            <button\n              type=\"button\"\n              onClick={onOpen}\n              className=\"cursor-pointer rounded-sm text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current\"\n              aria-label={`Open ${title || \"Untitled task\"}`}\n            >\n              {title || \"Untitled task\"}\n            </button>\n          ) : (\n            title || \"Untitled task\"\n          )}\n        </h3>\n        {description && (\n          <p className=\"mt-2.5 line-clamp-2 text-[15px] leading-[1.45] tracking-[-0.025em] text-(--task-muted) wrap-anywhere\">\n            {description}\n          </p>\n        )}\n        {((showAssignees && assignees.length > 0) ||\n          (showStatus && status)) && (\n          <div className=\"mt-4 flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-2\">\n            {showAssignees && assignees.length > 0 && (\n              <ul aria-label=\"Assignees\" className=\"flex -space-x-3\">\n                {visibleAssignees.map((person, index) => (\n                  <li key={`${person.name}-${index}`} title={person.name}>\n                    <Avatar\n                      className=\"border-[1.5px] border-white bg-zinc-300 ring-[1.5px] ring-black\"\n                      style={{ width: size, height: size }}\n                    >\n                      <AvatarImage\n                        src={person.src}\n                        alt={person.name}\n                        className=\"object-cover\"\n                      />\n                      <AvatarFallback\n                        className=\"bg-zinc-300 text-[10px] font-semibold text-zinc-800\"\n                        aria-label={person.name}\n                      >\n                        {person.name\n                          .split(/\\s+/)\n                          .filter(Boolean)\n                          .slice(0, 2)\n                          .map((word) => word[0])\n                          .join(\"\") || \"?\"}\n                      </AvatarFallback>\n                    </Avatar>\n                  </li>\n                ))}\n                {overflow > 0 && (\n                  <li\n                    className=\"relative flex items-center justify-center rounded-full border border-white bg-zinc-700 text-[10px] font-medium text-white ring-[1.5px] ring-black\"\n                    style={{ width: size, height: size }}\n                    aria-label={`${overflow} more assignees: ${assignees\n                      .slice(limit)\n                      .map((person) => person.name)\n                      .join(\", \")}`}\n                  >\n                    +{overflow}\n                  </li>\n                )}\n              </ul>\n            )}\n            {showStatus && status && (\n              <span className=\"ml-auto max-w-full rounded-[5px] bg-(--task-status) px-2.5 py-1.5 text-[13px] leading-snug tracking-[-0.03em] wrap-anywhere\">\n                {status}\n              </span>\n            )}\n          </div>\n        )}\n      </div>\n      {showFooter && (\n        <footer\n          className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-2 py-3 text-[16px] tabular-nums\"\n          style={{ paddingInline: inset }}\n        >\n          <div className=\"flex items-center gap-4\">\n            {[\n              { Icon: MessagesSquare, value: comments, label: \"comments\" },\n              { Icon: Link2, value: links, label: \"links\" },\n              { Icon: FolderClosed, value: files, label: \"files\" },\n            ].map(({ Icon, value, label }) => (\n              <span\n                key={label}\n                className=\"flex items-center gap-1.5\"\n                aria-label={`${Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0} ${label}`}\n              >\n                <Icon\n                  size={17}\n                  strokeWidth={1.6}\n                  className=\"text-(--task-muted)\"\n                  aria-hidden=\"true\"\n                />\n                <span aria-hidden=\"true\">\n                  {Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0}\n                </span>\n              </span>\n            ))}\n          </div>\n          {dateLabel && (\n            <time\n              dateTime={dueDate}\n              className=\"ml-auto text-(--task-muted)\"\n              aria-label={`Due ${dateLabel}`}\n            >\n              {dateLabel}\n            </time>\n          )}\n        </footer>\n      )}\n    </article>\n  );\n}\n\nexport default TaskCard;\n",
      "type": "registry:ui",
      "target": "@ui/task-card.tsx"
    },
    {
      "name": "avatar.tsx",
      "path": "src/components/ui/avatar.tsx",
      "code": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Avatar = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full\",\n      className,\n    )}\n    {...props}\n  />\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image\n    ref={ref}\n    className={cn(\"aspect-square h-full w-full\", className)}\n    {...props}\n  />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn(\n      \"flex h-full w-full items-center justify-center rounded-full bg-muted\",\n      className,\n    )}\n    {...props}\n  />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n\nexport default Avatar;\n",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Avatar = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full\",\n      className,\n    )}\n    {...props}\n  />\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image\n    ref={ref}\n    className={cn(\"aspect-square h-full w-full\", className)}\n    {...props}\n  />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn(\n      \"flex h-full w-full items-center justify-center rounded-full bg-muted\",\n      className,\n    )}\n    {...props}\n  />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n\nexport default Avatar;\n",
      "type": "registry:ui",
      "target": "@ui/avatar.tsx"
    }
  ],
  "type": "registry:ui",
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "title": "Task Card"
}
