{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "interactive-logs-table-baseui",
  "type": "registry:block",
  "title": "Interactive Logs Table",
  "description": "Observability logs panel with animated filters, search, and expandable rows (Base UI)",
  "registryDependencies": [
    "button"
  ],
  "dependencies": [
    "framer-motion",
    "react"
  ],
  "files": [
    {
      "path": "@uitripled/react-baseui/src/components/sections/interactive-logs-table-baseui.tsx",
      "content": "\"use client\";\n\nimport { Button } from \"@base-ui/react/button\";\nimport { Input } from \"@base-ui/react/input\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { Check, ChevronDown, Filter, Search } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\n\ntype LogLevel = \"info\" | \"warning\" | \"error\";\n\ninterface Log {\n  id: string;\n  timestamp: string;\n  level: LogLevel;\n  service: string;\n  message: string;\n  duration: string;\n  status: string;\n  tags: string[];\n}\n\ntype Filters = {\n  level: string[];\n  service: string[];\n  status: string[];\n};\n\nconst SAMPLE_LOGS: Log[] = [\n  {\n    id: \"1\",\n    timestamp: \"2024-11-08T14:32:45Z\",\n    level: \"info\",\n    service: \"api-gateway\",\n    message: \"Request processed successfully\",\n    duration: \"245ms\",\n    status: \"200\",\n    tags: [\"api\", \"success\"],\n  },\n  {\n    id: \"2\",\n    timestamp: \"2024-11-08T14:32:42Z\",\n    level: \"warning\",\n    service: \"cache-service\",\n    message: \"Cache miss ratio exceeds threshold\",\n    duration: \"1.2s\",\n    status: \"warning\",\n    tags: [\"cache\", \"performance\"],\n  },\n  {\n    id: \"3\",\n    timestamp: \"2024-11-08T14:32:40Z\",\n    level: \"error\",\n    service: \"database\",\n    message: \"Connection timeout to replica\",\n    duration: \"5.1s\",\n    status: \"503\",\n    tags: [\"db\", \"error\"],\n  },\n  {\n    id: \"4\",\n    timestamp: \"2024-11-08T14:32:38Z\",\n    level: \"info\",\n    service: \"auth-service\",\n    message: \"User session created\",\n    duration: \"156ms\",\n    status: \"201\",\n    tags: [\"auth\", \"session\"],\n  },\n  {\n    id: \"5\",\n    timestamp: \"2024-11-08T14:32:35Z\",\n    level: \"info\",\n    service: \"api-gateway\",\n    message: \"Webhook delivered\",\n    duration: \"432ms\",\n    status: \"200\",\n    tags: [\"webhook\", \"integration\"],\n  },\n  {\n    id: \"6\",\n    timestamp: \"2024-11-08T14:32:32Z\",\n    level: \"error\",\n    service: \"payment-service\",\n    message: \"Payment gateway unavailable\",\n    duration: \"2.3s\",\n    status: \"502\",\n    tags: [\"payment\", \"error\"],\n  },\n  {\n    id: \"7\",\n    timestamp: \"2024-11-08T14:32:30Z\",\n    level: \"info\",\n    service: \"search-service\",\n    message: \"Index updated\",\n    duration: \"876ms\",\n    status: \"200\",\n    tags: [\"search\", \"index\"],\n  },\n  {\n    id: \"8\",\n    timestamp: \"2024-11-08T14:32:28Z\",\n    level: \"warning\",\n    service: \"api-gateway\",\n    message: \"Rate limit approaching\",\n    duration: \"145ms\",\n    status: \"429\",\n    tags: [\"rate-limit\", \"warning\"],\n  },\n];\n\nconst levelStyles: Record<LogLevel, string> = {\n  info: \"bg-blue-500/10 text-blue-600 dark:text-blue-400\",\n  warning: \"bg-yellow-500/10 text-yellow-600 dark:text-yellow-400\",\n  error: \"bg-red-500/10 text-red-600 dark:text-red-400\",\n};\n\nconst statusStyles: Record<string, string> = {\n  \"200\": \"text-green-600 dark:text-green-400\",\n  \"201\": \"text-green-600 dark:text-green-400\",\n  \"429\": \"text-yellow-600 dark:text-yellow-400\",\n  \"502\": \"text-red-600 dark:text-red-400\",\n  \"503\": \"text-red-600 dark:text-red-400\",\n  warning: \"text-yellow-600 dark:text-yellow-400\",\n};\n\nfunction LogRow({\n  log,\n  expanded,\n  onToggle,\n}: {\n  log: Log;\n  expanded: boolean;\n  onToggle: () => void;\n}) {\n  const formattedTime = new Date(log.timestamp).toLocaleTimeString(\"en-US\", {\n    hour: \"2-digit\",\n    minute: \"2-digit\",\n    second: \"2-digit\",\n  });\n\n  return (\n    <>\n      <motion.button\n        onClick={onToggle}\n        className=\"w-full p-4 text-left transition-colors hover:bg-muted/50 active:bg-muted/70\"\n        whileHover={{ backgroundColor: \"rgba(0,0,0,0.02)\" }}\n      >\n        <div className=\"flex items-center gap-4\">\n          <motion.div\n            animate={{ rotate: expanded ? 180 : 0 }}\n            transition={{ duration: 0.2 }}\n            className=\"flex-shrink-0\"\n          >\n            <ChevronDown className=\"h-4 w-4 text-muted-foreground\" />\n          </motion.div>\n\n          <span\n            className={`flex-shrink-0 inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${levelStyles[log.level]}`}\n          >\n            {log.level}\n          </span>\n\n          <time className=\"w-20 flex-shrink-0 font-mono text-xs text-muted-foreground\">\n            {formattedTime}\n          </time>\n\n          <span className=\"min-w-max flex-shrink-0 text-sm font-medium text-foreground\">\n            {log.service}\n          </span>\n\n          <p className=\"flex-1 truncate text-sm text-muted-foreground\">\n            {log.message}\n          </p>\n\n          <span\n            className={`flex-shrink-0 font-mono text-sm font-semibold ${\n              statusStyles[log.status] ?? \"text-muted-foreground\"\n            }`}\n          >\n            {log.status}\n          </span>\n\n          <span className=\"w-16 flex-shrink-0 text-right font-mono text-xs text-muted-foreground\">\n            {log.duration}\n          </span>\n        </div>\n      </motion.button>\n\n      <AnimatePresence initial={false}>\n        {expanded && (\n          <motion.div\n            key=\"details\"\n            initial={{ height: 0, opacity: 0 }}\n            animate={{ height: \"auto\", opacity: 1 }}\n            exit={{ height: 0, opacity: 0 }}\n            transition={{ duration: 0.2 }}\n            className=\"overflow-hidden border-t border-border bg-muted/50\"\n          >\n            <div className=\"space-y-4 p-4\">\n              <div>\n                <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                  Message\n                </p>\n                <p className=\"rounded bg-background p-3 font-mono text-sm text-foreground\">\n                  {log.message}\n                </p>\n              </div>\n\n              <div className=\"grid grid-cols-2 gap-4 text-sm\">\n                <div>\n                  <p className=\"mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                    Duration\n                  </p>\n                  <p className=\"font-mono text-foreground\">{log.duration}</p>\n                </div>\n                <div>\n                  <p className=\"mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                    Timestamp\n                  </p>\n                  <p className=\"font-mono text-xs text-foreground\">\n                    {log.timestamp}\n                  </p>\n                </div>\n              </div>\n\n              <div>\n                <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n                  Tags\n                </p>\n                <div className=\"flex flex-wrap gap-2\">\n                  {log.tags.map((tag) => (\n                    <span\n                      key={tag}\n                      className=\"inline-flex items-center rounded-full border border-border px-2.5 py-0.5 text-xs font-semibold text-foreground\"\n                    >\n                      {tag}\n                    </span>\n                  ))}\n                </div>\n              </div>\n            </div>\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </>\n  );\n}\n\nfunction FilterPanel({\n  filters,\n  onChange,\n  logs,\n}: {\n  filters: Filters;\n  onChange: (filters: Filters) => void;\n  logs: Log[];\n}) {\n  const levels = Array.from(new Set(logs.map((log) => log.level)));\n  const services = Array.from(new Set(logs.map((log) => log.service)));\n  const statuses = Array.from(new Set(logs.map((log) => log.status)));\n\n  const toggleFilter = (category: keyof Filters, value: string) => {\n    const current = filters[category];\n    const updated = current.includes(value)\n      ? current.filter((entry) => entry !== value)\n      : [...current, value];\n\n    onChange({\n      ...filters,\n      [category]: updated,\n    });\n  };\n\n  const clearAll = () => {\n    onChange({\n      level: [],\n      service: [],\n      status: [],\n    });\n  };\n\n  const hasActiveFilters = Object.values(filters).some(\n    (group) => group.length > 0\n  );\n\n  return (\n    <motion.div\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      exit={{ opacity: 0 }}\n      transition={{ delay: 0.05 }}\n      className=\"flex h-full flex-col space-y-6 overflow-y-auto bg-card p-4\"\n    >\n      <div className=\"flex items-center justify-between\">\n        <h3 className=\"text-sm font-semibold text-foreground\">Filters</h3>\n        {hasActiveFilters && (\n          <Button\n            onClick={clearAll}\n            className=\"h-6 px-2 text-xs text-muted-foreground hover:text-foreground\"\n          >\n            Clear\n          </Button>\n        )}\n      </div>\n\n      <div className=\"space-y-3\">\n        <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n          Level\n        </p>\n        <div className=\"space-y-2\">\n          {levels.map((level) => {\n            const selected = filters.level.includes(level);\n\n            return (\n              <motion.button\n                key={level}\n                type=\"button\"\n                whileHover={{ x: 2 }}\n                onClick={() => toggleFilter(\"level\", level)}\n                aria-pressed={selected}\n                className={`flex w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm transition-colors ${\n                  selected\n                    ? \"border-primary bg-primary/10 text-primary\"\n                    : \"border-border text-muted-foreground hover:border-primary/40 hover:bg-muted/40\"\n                }`}\n              >\n                <span className=\"capitalize\">{level}</span>\n                {selected && <Check className=\"h-3.5 w-3.5\" />}\n              </motion.button>\n            );\n          })}\n        </div>\n      </div>\n\n      <div className=\"space-y-3\">\n        <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n          Service\n        </p>\n        <div className=\"space-y-2\">\n          {services.map((service) => {\n            const selected = filters.service.includes(service);\n\n            return (\n              <motion.button\n                key={service}\n                type=\"button\"\n                whileHover={{ x: 2 }}\n                onClick={() => toggleFilter(\"service\", service)}\n                aria-pressed={selected}\n                className={`flex w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm transition-colors ${\n                  selected\n                    ? \"border-primary bg-primary/10 text-primary\"\n                    : \"border-border text-muted-foreground hover:border-primary/40 hover:bg-muted/40\"\n                }`}\n              >\n                <span>{service}</span>\n                {selected && <Check className=\"h-3.5 w-3.5\" />}\n              </motion.button>\n            );\n          })}\n        </div>\n      </div>\n\n      <div className=\"space-y-3\">\n        <p className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n          Status\n        </p>\n        <div className=\"space-y-2\">\n          {statuses.map((status) => {\n            const selected = filters.status.includes(status);\n\n            return (\n              <motion.button\n                key={status}\n                type=\"button\"\n                whileHover={{ x: 2 }}\n                onClick={() => toggleFilter(\"status\", status)}\n                aria-pressed={selected}\n                className={`flex w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm transition-colors ${\n                  selected\n                    ? \"border-primary bg-primary/10 text-primary\"\n                    : \"border-border text-muted-foreground hover:border-primary/40 hover:bg-muted/40\"\n                }`}\n              >\n                <span>{status}</span>\n                {selected && <Check className=\"h-3.5 w-3.5\" />}\n              </motion.button>\n            );\n          })}\n        </div>\n      </div>\n    </motion.div>\n  );\n}\n\nexport function InteractiveLogsTableBaseui() {\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [expandedId, setExpandedId] = useState<string | null>(null);\n  const [showFilters, setShowFilters] = useState(false);\n  const [filters, setFilters] = useState<Filters>({\n    level: [],\n    service: [],\n    status: [],\n  });\n\n  const filteredLogs = useMemo(() => {\n    return SAMPLE_LOGS.filter((log) => {\n      const lowerQuery = searchQuery.toLowerCase();\n\n      const matchSearch =\n        log.message.toLowerCase().includes(lowerQuery) ||\n        log.service.toLowerCase().includes(lowerQuery);\n\n      const matchLevel =\n        filters.level.length === 0 || filters.level.includes(log.level);\n      const matchService =\n        filters.service.length === 0 || filters.service.includes(log.service);\n      const matchStatus =\n        filters.status.length === 0 || filters.status.includes(log.status);\n\n      return matchSearch && matchLevel && matchService && matchStatus;\n    });\n  }, [filters, searchQuery]);\n\n  const activeFilters =\n    filters.level.length + filters.service.length + filters.status.length;\n\n  return (\n    <main className=\"h-screen w-full bg-background\">\n      <div className=\"flex h-full flex-col\">\n        <div className=\"border-b border-border bg-card p-6\">\n          <div className=\"space-y-4\">\n            <div>\n              <h1 className=\"text-2xl font-semibold text-foreground\">Logs</h1>\n              <p className=\"text-sm text-muted-foreground\">\n                {filteredLogs.length} of {SAMPLE_LOGS.length} logs\n              </p>\n            </div>\n\n            <div className=\"flex gap-2\">\n              <div className=\"relative flex-1\">\n                <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n                <Input\n                  placeholder=\"Search logs by message or service...\"\n                  value={searchQuery}\n                  onChange={(event) => setSearchQuery(event.target.value)}\n                  className=\"h-9 w-full rounded-md border border-input bg-background pl-9 pr-3 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring\"\n                />\n              </div>\n              <Button\n                onClick={() => setShowFilters((current) => !current)}\n                className={`relative inline-flex h-9 items-center justify-center rounded-md border px-3 text-sm font-medium transition-colors ${\n                  showFilters\n                    ? \"border-transparent bg-primary text-primary-foreground\"\n                    : \"border-input bg-background hover:bg-accent hover:text-accent-foreground\"\n                }`}\n              >\n                <Filter className=\"h-4 w-4\" />\n                {activeFilters > 0 && (\n                  <span className=\"absolute -right-2 -top-2 flex h-5 w-5 items-center justify-center rounded-full bg-destructive p-0 text-xs text-destructive-foreground\">\n                    {activeFilters}\n                  </span>\n                )}\n              </Button>\n            </div>\n          </div>\n        </div>\n\n        <div className=\"flex flex-1 overflow-hidden\">\n          <AnimatePresence initial={false}>\n            {showFilters && (\n              <motion.div\n                key=\"filters\"\n                initial={{ width: 0, opacity: 0 }}\n                animate={{ width: 280, opacity: 1 }}\n                exit={{ width: 0, opacity: 0 }}\n                transition={{ duration: 0.2 }}\n                className=\"overflow-hidden border-r border-border\"\n              >\n                <FilterPanel\n                  filters={filters}\n                  onChange={setFilters}\n                  logs={SAMPLE_LOGS}\n                />\n              </motion.div>\n            )}\n          </AnimatePresence>\n\n          <div className=\"flex-1 overflow-y-auto\">\n            <div className=\"divide-y divide-border\">\n              <AnimatePresence mode=\"popLayout\">\n                {filteredLogs.length > 0 ? (\n                  filteredLogs.map((log, index) => (\n                    <motion.div\n                      key={log.id}\n                      initial={{ opacity: 0, y: -10 }}\n                      animate={{ opacity: 1, y: 0 }}\n                      exit={{ opacity: 0, y: -10 }}\n                      transition={{\n                        duration: 0.2,\n                        delay: index * 0.02,\n                      }}\n                    >\n                      <LogRow\n                        log={log}\n                        expanded={expandedId === log.id}\n                        onToggle={() =>\n                          setExpandedId((current) =>\n                            current === log.id ? null : log.id\n                          )\n                        }\n                      />\n                    </motion.div>\n                  ))\n                ) : (\n                  <motion.div\n                    key=\"empty-state\"\n                    initial={{ opacity: 0 }}\n                    animate={{ opacity: 1 }}\n                    className=\"p-12 text-center\"\n                  >\n                    <p className=\"text-muted-foreground\">\n                      No logs match your filters.\n                    </p>\n                  </motion.div>\n                )}\n              </AnimatePresence>\n            </div>\n          </div>\n        </div>\n      </div>\n    </main>\n  );\n}\n",
      "type": "registry:block",
      "target": "components/uitripled/interactive-logs-table-baseui.tsx"
    }
  ]
}