{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "content-calendar-matrix",
  "title": "Content Calendar Matrix",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/content-calendar-matrix/ContentCalendarMatrix.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref, type HTMLAttributes } from 'vue'\nimport {\n  Calendar,\n  CalendarCheck,\n  CalendarDays,\n  CheckCircle2,\n  Clock,\n  FileText,\n  Layers,\n  Mail,\n  MoreHorizontal,\n  Pencil,\n  PenLine,\n  Plus,\n  Search,\n  Share2,\n  Trash2,\n  UserCheck,\n  Video,\n} from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Avatar, AvatarFallback } from '@/components/ui/avatar'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Input } from '@/components/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\n\nexport type ContentChannel = 'all' | 'blog' | 'newsletter' | 'youtube' | 'social'\nexport type ContentStatus = 'published' | 'scheduled' | 'in_review' | 'draft'\n\nexport interface ContentItem {\n  id: string\n  title: string\n  channel: 'blog' | 'newsletter' | 'youtube' | 'social'\n  channelLabel: string\n  publishDate: string\n  publishTime: string\n  author: {\n    name: string\n    role: string\n    avatar: string\n  }\n  status: ContentStatus\n  statusLabel: string\n  keywords: string[]\n  format: string\n}\n\ninterface Props {\n  title?: string\n  subtitle?: string\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  title: 'Editorial Content Calendar',\n  subtitle: 'Schedule, organize, and track cross-channel editorial pipelines and publishing milestones.',\n})\n\nconst selectedMonth = ref('2026-09')\nconst selectedChannel = ref<ContentChannel>('all')\nconst searchQuery = ref('')\n\nconst pacingKpis = [\n  {\n    id: 'planned',\n    title: 'Total Planned Content',\n    value: '24',\n    unit: 'Pieces this month',\n    note: '24 Pieces this month',\n    subtext: '+6 pieces vs previous month',\n    icon: Layers,\n    colorClass: 'text-primary bg-primary/10',\n  },\n  {\n    id: 'scheduled',\n    title: 'Scheduled for Publish',\n    value: '8',\n    unit: 'items ready',\n    note: '8 items ready',\n    subtext: 'Next drop: Sep 08 · 02:00 PM EST',\n    icon: CalendarCheck,\n    colorClass: 'text-info bg-info/10',\n  },\n  {\n    id: 'in-review',\n    title: 'In Review / Drafting',\n    value: '12',\n    unit: 'in progress',\n    note: '12 in progress',\n    subtext: '7 in editorial review · 5 in drafting',\n    icon: PenLine,\n    colorClass: 'text-warning bg-warning/10',\n  },\n  {\n    id: 'published',\n    title: 'Published this Month',\n    value: '4',\n    unit: 'live',\n    note: '4 live',\n    subtext: '100% on-schedule publishing rate',\n    icon: CheckCircle2,\n    colorClass: 'text-success bg-success/10',\n  },\n]\n\nconst channelConfig: Record<\n  'blog' | 'newsletter' | 'youtube' | 'social',\n  { label: string; badgeClass: string; icon: any }\n> = {\n  blog: {\n    label: 'Blog',\n    badgeClass: 'bg-info/10 text-info border-info/20',\n    icon: FileText,\n  },\n  newsletter: {\n    label: 'Newsletter',\n    badgeClass: 'bg-chart-1/10 text-chart-1 border-chart-1/20',\n    icon: Mail,\n  },\n  youtube: {\n    label: 'YouTube',\n    badgeClass: 'bg-destructive/10 text-destructive border-destructive/20',\n    icon: Video,\n  },\n  social: {\n    label: 'Social',\n    badgeClass: 'bg-info/10 text-info border-info/20',\n    icon: Share2,\n  },\n}\n\nconst statusConfig: Record<ContentStatus, { label: string; badgeClass: string; dotClass: string }> = {\n  published: {\n    label: 'Published',\n    badgeClass: 'bg-success/10 text-success border-success/20',\n    dotClass: 'bg-success',\n  },\n  scheduled: {\n    label: 'Scheduled',\n    badgeClass: 'bg-info/10 text-info border-info/20',\n    dotClass: 'bg-info',\n  },\n  in_review: {\n    label: 'In Review',\n    badgeClass: 'bg-warning/10 text-warning border-warning/20',\n    dotClass: 'bg-warning',\n  },\n  draft: {\n    label: 'Draft',\n    badgeClass: 'bg-muted text-muted-foreground border-border',\n    dotClass: 'bg-muted-foreground',\n  },\n}\n\nconst contentItems: ContentItem[] = [\n  {\n    id: 'cnt-01',\n    title: 'Deep Dive: Headless Reka UI Primitives in Vue 3.5',\n    channel: 'blog',\n    channelLabel: 'Blog',\n    publishDate: 'Sep 04',\n    publishTime: '10:00 AM EST',\n    author: {\n      name: 'Sarah Chen',\n      role: 'Lead Developer Advocate',\n      avatar: 'SC',\n    },\n    status: 'published',\n    statusLabel: 'Published',\n    keywords: ['#vue3', '#reka-ui', '#frontend'],\n    format: 'Technical Guide',\n  },\n  {\n    id: 'cnt-02',\n    title: 'Building Zero-Runtime Design Systems with Tailwind CSS v4',\n    channel: 'youtube',\n    channelLabel: 'YouTube',\n    publishDate: 'Sep 08',\n    publishTime: '02:00 PM EST',\n    author: {\n      name: 'Marcus Vance',\n      role: 'Senior UI Engineer',\n      avatar: 'MV',\n    },\n    status: 'scheduled',\n    statusLabel: 'Scheduled',\n    keywords: ['#tailwindcss', '#design-systems', '#webdev'],\n    format: 'Video Tutorial',\n  },\n  {\n    id: 'cnt-03',\n    title: 'Frontend Weekly #142: Subgrid Mastery & CSS Anchor Positioning',\n    channel: 'newsletter',\n    channelLabel: 'Newsletter',\n    publishDate: 'Sep 12',\n    publishTime: '09:00 AM EST',\n    author: {\n      name: 'Elena Rostova',\n      role: 'Technical Editor',\n      avatar: 'ER',\n    },\n    status: 'scheduled',\n    statusLabel: 'Scheduled',\n    keywords: ['#css', '#newsletter', '#subgrid'],\n    format: 'Email Dispatch',\n  },\n  {\n    id: 'cnt-04',\n    title: 'Accessible Command Palettes & Keyboard Shortcuts Architecture',\n    channel: 'blog',\n    channelLabel: 'Blog',\n    publishDate: 'Sep 16',\n    publishTime: '11:30 AM EST',\n    author: {\n      name: 'David Kim',\n      role: 'Accessibility Specialist',\n      avatar: 'DK',\n    },\n    status: 'in_review',\n    statusLabel: 'In Review',\n    keywords: ['#a11y', '#cmdk', '#keyboard-nav'],\n    format: 'Deep Dive',\n  },\n  {\n    id: 'cnt-05',\n    title: 'Product Drop: 12 New Enterprise Blocks for Nuxt 4 & React',\n    channel: 'social',\n    channelLabel: 'Social',\n    publishDate: 'Sep 21',\n    publishTime: '04:00 PM EST',\n    author: {\n      name: 'Aisha Patel',\n      role: 'Product Marketing Lead',\n      avatar: 'AP',\n    },\n    status: 'in_review',\n    statusLabel: 'In Review',\n    keywords: ['#changelog', '#uipkge', '#react', '#vue'],\n    format: 'Social Campaign',\n  },\n  {\n    id: 'cnt-06',\n    title: 'Micro-Interactions & Spring Physics in Modern Web Applications',\n    channel: 'youtube',\n    channelLabel: 'YouTube',\n    publishDate: 'Sep 26',\n    publishTime: '01:15 PM EST',\n    author: {\n      name: 'Lucas Meyer',\n      role: 'Motion Designer',\n      avatar: 'LM',\n    },\n    status: 'draft',\n    statusLabel: 'Draft',\n    keywords: ['#animation', '#spring-physics', '#ui-motion'],\n    format: 'Interactive Workshop',\n  },\n]\n\nconst channelFilters: Array<{ value: ContentChannel; label: string; icon: any }> = [\n  { value: 'all', label: 'All Channels', icon: Layers },\n  { value: 'blog', label: 'Blog', icon: FileText },\n  { value: 'newsletter', label: 'Newsletter', icon: Mail },\n  { value: 'youtube', label: 'YouTube', icon: Video },\n  { value: 'social', label: 'Social', icon: Share2 },\n]\n\nconst filteredItems = computed(() => {\n  const query = searchQuery.value.trim().toLowerCase()\n  return contentItems.filter((item) => {\n    const matchesChannel = selectedChannel.value === 'all' || item.channel === selectedChannel.value\n    const matchesQuery =\n      !query ||\n      item.title.toLowerCase().includes(query) ||\n      item.author.name.toLowerCase().includes(query) ||\n      item.keywords.some((k) => k.toLowerCase().includes(query))\n    return matchesChannel && matchesQuery\n  })\n})\n</script>\n\n<template>\n  <div data-slot=\"content-calendar-matrix\" :class=\"cn('w-full space-y-6', props.class)\">\n    <!-- Header Section -->\n    <div class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n      <div>\n        <div class=\"flex items-center gap-2\">\n          <h2 class=\"text-2xl font-bold tracking-tight\">{{ props.title }}</h2>\n          <Badge variant=\"outline\" class=\"hidden sm:inline-flex\">Editorial Desk</Badge>\n        </div>\n        <p class=\"text-muted-foreground mt-1 text-sm\">{{ props.subtitle }}</p>\n      </div>\n\n      <div class=\"flex flex-wrap items-center gap-3\">\n        <!-- Month Selector -->\n        <Select v-model=\"selectedMonth\">\n          <SelectTrigger class=\"w-[180px] shadow-xs\">\n            <CalendarDays class=\"text-muted-foreground mr-2 size-4\" />\n            <SelectValue placeholder=\"Select month\" />\n          </SelectTrigger>\n          <SelectContent>\n            <SelectItem value=\"2026-08\">August 2026</SelectItem>\n            <SelectItem value=\"2026-09\">September 2026</SelectItem>\n            <SelectItem value=\"2026-10\">October 2026</SelectItem>\n            <SelectItem value=\"2026-11\">November 2026</SelectItem>\n          </SelectContent>\n        </Select>\n\n        <!-- New Content Item Primary Button -->\n        <Button class=\"gap-2 shadow-xs\">\n          <Plus class=\"size-4\" />\n          New Content Item\n        </Button>\n      </div>\n    </div>\n\n    <!-- 4 Editorial Pacing KPI Cards -->\n    <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n      <Card v-for=\"kpi in pacingKpis\" :key=\"kpi.id\" class=\"shadow-xs\">\n        <CardContent class=\"space-y-3 p-5\">\n          <div class=\"flex items-center justify-between\">\n            <p class=\"text-muted-foreground text-sm font-medium\">{{ kpi.title }}</p>\n            <div :class=\"cn('flex size-8 items-center justify-center rounded-md', kpi.colorClass)\">\n              <component :is=\"kpi.icon\" class=\"size-4\" />\n            </div>\n          </div>\n\n          <div>\n            <div class=\"flex items-baseline gap-1.5\">\n              <span class=\"text-2xl font-bold tracking-tight tabular-nums\">{{ kpi.value }}</span>\n              <span class=\"text-muted-foreground text-xs font-medium\">{{ kpi.unit }}</span>\n            </div>\n            <p class=\"text-muted-foreground mt-1 text-xs\">{{ kpi.subtext }}</p>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n\n    <!-- Pipeline & Matrix Controls Bar -->\n    <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n      <!-- Channel Filters -->\n      <div class=\"flex flex-wrap items-center gap-1.5\">\n        <Button\n          v-for=\"filter in channelFilters\"\n          :key=\"filter.value\"\n          :variant=\"selectedChannel === filter.value ? 'default' : 'outline'\"\n          size=\"sm\"\n          class=\"h-8 gap-1.5 text-xs shadow-xs\"\n          @click=\"selectedChannel = filter.value\"\n        >\n          <component :is=\"filter.icon\" class=\"size-3.5\" />\n          <span>{{ filter.label }}</span>\n        </Button>\n      </div>\n\n      <!-- Keyword / Title Search Input -->\n      <div class=\"relative w-full sm:w-64\">\n        <Search class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2\" />\n        <Input\n          v-model=\"searchQuery\"\n          type=\"text\"\n          placeholder=\"Filter by keyword or title...\"\n          class=\"h-8 pl-8 text-xs shadow-xs\"\n        />\n      </div>\n    </div>\n\n    <!-- Content Publishing Pipeline Table -->\n    <div class=\"bg-card overflow-x-auto rounded-lg border shadow-xs\">\n      <Table>\n        <TableHeader>\n          <TableRow>\n            <TableHead class=\"w-[180px]\">Publish Date &amp; Time</TableHead>\n            <TableHead class=\"min-w-[280px]\">Content Title</TableHead>\n            <TableHead class=\"w-[120px]\">Channel</TableHead>\n            <TableHead class=\"min-w-[180px]\">Author</TableHead>\n            <TableHead class=\"w-[130px]\">Status</TableHead>\n            <TableHead class=\"min-w-[200px]\">SEO / Keywords</TableHead>\n            <TableHead class=\"w-12 text-right\">\n              <span class=\"sr-only\">Actions</span>\n            </TableHead>\n          </TableRow>\n        </TableHeader>\n        <TableBody>\n          <TableRow v-for=\"item in filteredItems\" :key=\"item.id\" class=\"hover:bg-muted/50 transition-colors\">\n            <!-- Publish Date & Time -->\n            <TableCell class=\"align-middle\">\n              <div class=\"flex items-center gap-1.5\">\n                <Clock class=\"text-muted-foreground size-3.5 shrink-0\" />\n                <span class=\"text-xs font-medium tabular-nums\">{{ item.publishDate }} · {{ item.publishTime }}</span>\n              </div>\n            </TableCell>\n\n            <!-- Content Title -->\n            <TableCell class=\"align-middle\">\n              <div class=\"space-y-0.5\">\n                <p class=\"text-foreground text-sm leading-snug font-semibold\">{{ item.title }}</p>\n                <p class=\"text-muted-foreground text-xs font-normal\">{{ item.format }}</p>\n              </div>\n            </TableCell>\n\n            <!-- Channel Badge -->\n            <TableCell class=\"align-middle\">\n              <Badge variant=\"outline\" :class=\"cn('gap-1 text-xs font-medium', channelConfig[item.channel].badgeClass)\">\n                <component :is=\"channelConfig[item.channel].icon\" class=\"size-3\" />\n                {{ channelConfig[item.channel].label }}\n              </Badge>\n            </TableCell>\n\n            <!-- Author (Avatar + Name) -->\n            <TableCell class=\"align-middle\">\n              <div class=\"flex items-center gap-2.5\">\n                <Avatar class=\"size-7\">\n                  <AvatarFallback class=\"bg-primary/10 text-primary text-xs font-semibold\">\n                    {{ item.author.avatar }}\n                  </AvatarFallback>\n                </Avatar>\n                <div class=\"min-w-0\">\n                  <p class=\"text-foreground truncate text-xs font-medium\">{{ item.author.name }}</p>\n                  <p class=\"text-muted-foreground truncate text-xs\">{{ item.author.role }}</p>\n                </div>\n              </div>\n            </TableCell>\n\n            <!-- Status Badge -->\n            <TableCell class=\"align-middle\">\n              <Badge variant=\"outline\" :class=\"cn('gap-1.5 text-xs font-medium', statusConfig[item.status].badgeClass)\">\n                <span :class=\"cn('size-1.5 rounded-full', statusConfig[item.status].dotClass)\" />\n                {{ statusConfig[item.status].label }}\n              </Badge>\n            </TableCell>\n\n            <!-- SEO / Target Keywords -->\n            <TableCell class=\"align-middle\">\n              <div class=\"flex flex-wrap gap-1\">\n                <span\n                  v-for=\"keyword in item.keywords\"\n                  :key=\"keyword\"\n                  class=\"bg-muted/60 border-border/50 text-muted-foreground inline-flex items-center rounded-md border px-1.5 py-0.5 font-mono text-xs\"\n                >\n                  {{ keyword }}\n                </span>\n              </div>\n            </TableCell>\n\n            <!-- Actions Dropdown -->\n            <TableCell class=\"text-right align-middle\">\n              <DropdownMenu>\n                <DropdownMenuTrigger as-child>\n                  <Button variant=\"ghost\" size=\"icon-sm\" class=\"text-muted-foreground size-8\">\n                    <MoreHorizontal class=\"size-4\" />\n                    <span class=\"sr-only\">Open actions for {{ item.title }}</span>\n                  </Button>\n                </DropdownMenuTrigger>\n                <DropdownMenuContent align=\"end\" class=\"w-48\">\n                  <DropdownMenuLabel class=\"text-xs\">Manage Content</DropdownMenuLabel>\n                  <DropdownMenuSeparator />\n                  <DropdownMenuItem class=\"gap-2 text-xs\">\n                    <Pencil class=\"size-3.5\" />\n                    <span>Edit item</span>\n                  </DropdownMenuItem>\n                  <DropdownMenuItem class=\"gap-2 text-xs\">\n                    <Calendar class=\"size-3.5\" />\n                    <span>Reschedule</span>\n                  </DropdownMenuItem>\n                  <DropdownMenuItem class=\"gap-2 text-xs\">\n                    <UserCheck class=\"size-3.5\" />\n                    <span>Assign reviewer</span>\n                  </DropdownMenuItem>\n                  <DropdownMenuSeparator />\n                  <DropdownMenuItem class=\"text-destructive focus:text-destructive gap-2 text-xs\">\n                    <Trash2 class=\"size-3.5\" />\n                    <span>Delete item</span>\n                  </DropdownMenuItem>\n                </DropdownMenuContent>\n              </DropdownMenu>\n            </TableCell>\n          </TableRow>\n\n          <TableRow v-if=\"filteredItems.length === 0\">\n            <TableCell colspan=\"7\" class=\"text-muted-foreground h-32 text-center text-sm\">\n              No content items found matching the selected channel filter or search query.\n            </TableCell>\n          </TableRow>\n        </TableBody>\n      </Table>\n    </div>\n\n    <!-- Publishing Matrix Footer -->\n    <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n      <p class=\"text-muted-foreground text-xs\">\n        Showing <span class=\"text-foreground font-semibold tabular-nums\">{{ filteredItems.length }}</span> of\n        <span class=\"text-foreground font-semibold tabular-nums\">{{ contentItems.length }}</span> scheduled content\n        pieces for <span class=\"text-foreground font-medium\">September 2026</span>\n      </p>\n      <div class=\"flex items-center gap-2\">\n        <Button variant=\"outline\" size=\"sm\" class=\"h-8 text-xs shadow-xs\" disabled> Previous </Button>\n        <Button variant=\"outline\" size=\"sm\" class=\"h-8 text-xs shadow-xs\"> Next </Button>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/ContentCalendarMatrix.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/avatar.json",
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/dropdown-menu.json",
    "https://uipkge.dev/r/vue/input.json",
    "https://uipkge.dev/r/vue/select.json",
    "https://uipkge.dev/r/vue/table.json"
  ],
  "description": "Editorial publishing calendar and content matrix with pacing KPI summary cards, channel filter tabs, month picker, publication status pipeline, author avatars, SEO keywords, and contextual action menus.",
  "categories": [
    "media",
    "app",
    "marketing"
  ]
}