{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "table",
  "version": "2.1.0",
  "category": "list",
  "meta": {
    "preview": "https://ui.manifest.build/previews/table.png",
    "version": "2.1.0",
    "changelog": {
      "1.0.0": "Initial release with single and multi-select modes",
      "1.0.1": "Added descriptive alt tags for title images",
      "1.0.2": "Added aria-label to filter removal button for screen reader accessibility",
      "1.0.4": "Added comprehensive JSDoc documentation",
      "1.0.5": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
      "1.0.6": "Fixed defensive property access to handle empty objects and null values",
      "1.0.7": "Inlined OpenAI display mode types to remove external lib dependency for shadcn distribution",
      "2.0.0": "BREAKING: Removed onSelectionChange and onExpand actions. Selection and expand are now internal.",
      "2.0.1": "Removed default content data - component only renders explicitly provided data",
      "2.0.2": "Removed unused checkbox from registry dependencies",
      "2.0.3": "Migrated from OpenAI Apps SDK to MCP Apps protocol for host communication",
      "2.0.4": "Removed stale sortedData from handleRowSelect dependency array",
      "2.0.5": "Fixed table preview not displaying data by passing demo columns and rows",
      "2.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
    }
  },
  "changelog": {
    "1.0.0": "Initial release with single and multi-select modes",
    "1.0.1": "Added descriptive alt tags for title images",
    "1.0.2": "Added aria-label to filter removal button for screen reader accessibility",
    "1.0.4": "Added comprehensive JSDoc documentation",
    "1.0.5": "Updated Props interface with decorative header and sub-parameter JSDoc documentation",
    "1.0.6": "Fixed defensive property access to handle empty objects and null values",
    "1.0.7": "Inlined OpenAI display mode types to remove external lib dependency for shadcn distribution",
    "2.0.0": "BREAKING: Removed onSelectionChange and onExpand actions. Selection and expand are now internal.",
    "2.0.1": "Removed default content data - component only renders explicitly provided data",
    "2.0.2": "Removed unused checkbox from registry dependencies",
    "2.0.3": "Migrated from OpenAI Apps SDK to MCP Apps protocol for host communication",
    "2.0.4": "Removed stale sortedData from handleRowSelect dependency array",
    "2.0.5": "Fixed table preview not displaying data by passing demo columns and rows",
    "2.1.0": "Added demo data defaults - component renders demo content when no data prop is provided"
  },
  "title": "Table",
  "author": "MNFST, Inc",
  "description": "Data table with optional single or multi-select modes for chat interfaces.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "popover",
    "select",
    "input"
  ],
  "files": [
    {
      "path": "registry/list/table.tsx",
      "content": "'use client'\n\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger\n} from '@/components/ui/popover'\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue\n} from '@/components/ui/select'\nimport { cn } from '@/lib/utils'\nimport { demoTableColumns, demoTableRows } from './demo/list'\nimport {\n  ArrowDownAZ,\n  ArrowUpAZ,\n  Check,\n  ChevronDown,\n  ChevronLeft,\n  ChevronRight,\n  ChevronUp,\n  Copy,\n  Download,\n  Maximize2,\n  Minus,\n  RefreshCw,\n  Search,\n  Share2,\n  Trash2,\n  Type\n} from 'lucide-react'\nimport { useCallback, useMemo, useState } from 'react'\n\n// Filter types\ninterface FilterCondition {\n  id: string\n  field: string\n  operator:\n    | 'contains'\n    | 'equals'\n    | 'startsWith'\n    | 'endsWith'\n    | 'isEmpty'\n    | 'isNotEmpty'\n  value: string\n}\n\n/**\n * Configuration for a table column.\n * @interface TableColumn\n * @template T - The row data type\n * @property {string} header - Column header text\n * @property {keyof T | string} accessor - Key to access row data or dot-notation path\n * @property {boolean} [sortable] - Whether the column is sortable\n * @property {string} [width] - CSS width value for the column\n * @property {\"left\" | \"center\" | \"right\"} [align] - Text alignment\n * @property {function} [render] - Custom render function for cell content\n */\nexport interface TableColumn<T = Record<string, unknown>> {\n  header?: string\n  accessor?: keyof T | string\n  sortable?: boolean\n  width?: string\n  align?: 'left' | 'center' | 'right'\n  render?: (value: unknown, row: T, index: number) => React.ReactNode\n}\n\n/**\n * ═══════════════════════════════════════════════════════════════════════════\n * TableProps\n * ═══════════════════════════════════════════════════════════════════════════\n *\n * Props for configuring a data table component with sorting, selection,\n * pagination, and filtering capabilities.\n *\n * @template T - The row data type\n */\nexport interface TableProps<T = Record<string, unknown>> {\n  data?: {\n    /** Column definitions specifying headers, accessors, and rendering. */\n    columns?: TableColumn<T>[]\n    /** Array of row data objects to display. */\n    rows?: T[]\n    /** Table title displayed in the header. */\n    title?: string\n    /** Icon or image URL displayed next to the title. */\n    titleImage?: string\n    /** Timestamp showing when the data was last updated. */\n    lastUpdated?: Date | string\n    /** Total row count for displaying \"+N more\" indicator. */\n    totalRows?: number\n  }\n  actions?: {\n    /** Called when the copy action is triggered with selected rows. */\n    onCopy?: (selectedRows: T[]) => void\n    /** Called when the download action is triggered with selected rows. */\n    onDownload?: (selectedRows: T[]) => void\n    /** Called when the share action is triggered with selected rows. */\n    onShare?: (selectedRows: T[]) => void\n    /** Called when the refresh button is clicked. */\n    onRefresh?: () => void\n  }\n  appearance?: {\n    /**\n     * Row selection mode.\n     * @default \"none\"\n     */\n    selectable?: 'none' | 'single' | 'multi'\n    /**\n     * Message displayed when the table has no data.\n     * @default \"No data available\"\n     */\n    emptyMessage?: string\n    /**\n     * Whether to keep the header fixed when scrolling.\n     * @default false\n     */\n    stickyHeader?: boolean\n    /**\n     * Whether to use compact row height.\n     * @default false\n     */\n    compact?: boolean\n    /** Whether to show action buttons in the header. */\n    showActions?: boolean\n    /**\n     * Whether to show the table header.\n     * @default true\n     */\n    showHeader?: boolean\n    /**\n     * Whether to show the table footer.\n     * @default true\n     */\n    showFooter?: boolean\n    /**\n     * Maximum number of rows to display in inline mode.\n     * @default 5\n     */\n    maxRows?: number\n    /**\n     * Display mode: 'inline' (compact card) or 'fullscreen' (paginated with filters).\n     * @default \"inline\"\n     */\n    displayMode?: 'inline' | 'pip' | 'fullscreen'\n  }\n  control?: {\n    /** Whether to show loading skeleton state. */\n    loading?: boolean\n    /** Controlled array of selected rows. */\n    selectedRows?: T[]\n  }\n}\n\n\nfunction SkeletonRow({\n  columns,\n  compact\n}: {\n  columns: number\n  compact?: boolean\n}) {\n  return (\n    <tr className=\"border-b border-border\">\n      {Array.from({ length: columns }).map((_, i) => (\n        <td key={i} className={cn('px-3', compact ? 'py-2' : 'py-3')}>\n          <div className=\"h-4 bg-muted animate-pulse rounded\" />\n        </td>\n      ))}\n    </tr>\n  )\n}\n\n// TableHeader component (inline mode)\nfunction TableHeader({\n  title,\n  titleImage,\n  onExpand,\n  selectable,\n  hasSelection,\n  onCopy,\n  onDownload,\n  onShare\n}: {\n  title?: string\n  titleImage?: string\n  onExpand?: () => void\n  selectable?: 'none' | 'single' | 'multi'\n  hasSelection?: boolean\n  onCopy?: () => void\n  onDownload?: () => void\n  onShare?: () => void\n}) {\n  if (!title && !onExpand) return null\n\n  return (\n    <div className=\"flex items-center justify-between px-4 py-3 border-b bg-card rounded-t-lg h-14\">\n      <div className=\"flex items-center gap-2\">\n        {titleImage && (\n          <img\n            src={titleImage}\n            alt={title ? `${title} icon` : \"Table icon\"}\n            className=\"h-5 w-5 rounded object-cover\"\n          />\n        )}\n        {title && <span className=\"font-medium\">{title}</span>}\n      </div>\n      <div className=\"flex items-center gap-2\">\n        {/* Action buttons - icons only, disabled when no selection */}\n        {selectable === 'single' && onCopy && (\n          <button\n            onClick={hasSelection ? onCopy : undefined}\n            disabled={!hasSelection}\n            className={cn(\n              'flex h-8 w-8 items-center justify-center rounded-md transition-colors',\n              hasSelection\n                ? 'text-muted-foreground hover:bg-muted hover:text-foreground cursor-pointer'\n                : 'text-muted-foreground/40 cursor-not-allowed'\n            )}\n            aria-label=\"Copy\"\n          >\n            <Copy className=\"h-4 w-4\" />\n          </button>\n        )}\n        {selectable === 'multi' && (\n          <>\n            {onDownload && (\n              <button\n                onClick={hasSelection ? onDownload : undefined}\n                disabled={!hasSelection}\n                className={cn(\n                  'flex h-8 w-8 items-center justify-center rounded-md transition-colors',\n                  hasSelection\n                    ? 'text-muted-foreground hover:bg-muted hover:text-foreground cursor-pointer'\n                    : 'text-muted-foreground/40 cursor-not-allowed'\n                )}\n                aria-label=\"Download\"\n              >\n                <Download className=\"h-4 w-4\" />\n              </button>\n            )}\n            {onShare && (\n              <button\n                onClick={hasSelection ? onShare : undefined}\n                disabled={!hasSelection}\n                className={cn(\n                  'flex h-8 w-8 items-center justify-center rounded-md transition-colors',\n                  hasSelection\n                    ? 'text-muted-foreground hover:bg-muted hover:text-foreground cursor-pointer'\n                    : 'text-muted-foreground/40 cursor-not-allowed'\n                )}\n                aria-label=\"Share\"\n              >\n                <Share2 className=\"h-4 w-4\" />\n              </button>\n            )}\n          </>\n        )}\n        {onExpand && (\n          <button\n            onClick={onExpand}\n            className=\"flex h-8 w-8 items-center justify-center rounded-full border bg-background text-muted-foreground transition-colors hover:bg-muted hover:text-foreground cursor-pointer\"\n            aria-label=\"Expand table\"\n          >\n            <Maximize2 className=\"h-4 w-4\" />\n          </button>\n        )}\n      </div>\n    </div>\n  )\n}\n\n// TableFooter component\nfunction TableFooter({\n  moreCount,\n  lastUpdated,\n  onRefresh\n}: {\n  moreCount?: number\n  lastUpdated?: Date | string\n  onRefresh?: () => void\n}) {\n  const formatTimestamp = (date: Date | string) => {\n    const d = typeof date === 'string' ? new Date(date) : date\n    return d.toLocaleDateString('en-US', {\n      month: 'short',\n      day: 'numeric',\n      year: 'numeric',\n      hour: 'numeric',\n      minute: '2-digit'\n    })\n  }\n\n  const hasLeftContent = (moreCount && moreCount > 0) || lastUpdated\n\n  return (\n    <div className=\"flex items-center justify-between px-4 py-2 border-t bg-muted/50 rounded-b-lg\">\n      <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n        {moreCount && moreCount > 0 && <span>+{moreCount} more</span>}\n        {moreCount && moreCount > 0 && lastUpdated && (\n          <span className=\"text-muted-foreground/50\">·</span>\n        )}\n        {lastUpdated && <span>Data as of {formatTimestamp(lastUpdated)}</span>}\n        {!hasLeftContent && <span>&nbsp;</span>}\n      </div>\n      <button\n        onClick={onRefresh}\n        className={cn(\n          'transition-colors',\n          onRefresh\n            ? 'text-muted-foreground hover:text-foreground cursor-pointer'\n            : 'text-muted-foreground/40 cursor-not-allowed'\n        )}\n        disabled={!onRefresh}\n        aria-label=\"Refresh\"\n      >\n        <RefreshCw className=\"h-4 w-4\" />\n      </button>\n    </div>\n  )\n}\n\n/**\n * A data table component with optional single or multi-select modes for chat interfaces.\n * Supports both inline (compact) and fullscreen (paginated) display modes.\n *\n * Features:\n * - Column sorting (ascending/descending)\n * - Row selection (none, single, multi)\n * - Inline and fullscreen display modes\n * - Pagination in fullscreen mode\n * - Column filtering in fullscreen mode\n * - Custom cell rendering\n * - Loading skeleton state\n * - Copy, download, share actions\n * - Sticky header option\n * - Mobile card layout\n * - MCP Apps display mode support\n *\n * @component\n * @template T - The row data type\n * @example\n * ```tsx\n * <Table\n *   data={{\n *     columns: [\n *       { header: \"Name\", accessor: \"name\", sortable: true },\n *       { header: \"Price\", accessor: \"price\", align: \"right\" }\n *     ],\n *     rows: [\n *       { name: \"Product A\", price: 99 },\n *       { name: \"Product B\", price: 149 }\n *     ],\n *     title: \"Products\",\n *     lastUpdated: new Date()\n *   }}\n *   actions={{\n *     onSelectionChange: (rows) => console.log(\"Selected:\", rows),\n *     onRefresh: () => console.log(\"Refresh\")\n *   }}\n *   appearance={{\n *     selectable: \"multi\",\n *     maxRows: 10,\n *     showHeader: true,\n *     showFooter: true\n *   }}\n * />\n * ```\n */\nexport function Table<T extends Record<string, unknown>>({\n  data: dataProps,\n  actions,\n  appearance,\n  control\n}: TableProps<T>) {\n  const resolvedData: NonNullable<TableProps<T>['data']> = dataProps ?? { columns: demoTableColumns as unknown as TableColumn<T>[], rows: demoTableRows as unknown as T[] }\n  const {\n    columns = [] as unknown as TableColumn<T>[],\n    rows: tableData = [] as unknown as T[],\n    title,\n    titleImage,\n    lastUpdated,\n    totalRows\n  } = resolvedData\n  const {\n    onCopy,\n    onDownload,\n    onShare,\n    onRefresh\n  } = actions ?? {}\n  const {\n    selectable = 'none',\n    emptyMessage = 'No data available',\n    stickyHeader = false,\n    compact = false,\n    showHeader = true,\n    showFooter = true,\n    maxRows = 5,\n    displayMode: propDisplayMode\n  } = appearance ?? {}\n  const { loading = false, selectedRows: controlledSelectedRows } =\n    control ?? {}\n\n  const displayMode = propDisplayMode ?? 'inline'\n\n  const [currentPage, setCurrentPage] = useState(1)\n  const [sortConfig, setSortConfig] = useState<{\n    key: string\n    direction: 'asc' | 'desc'\n  } | null>(null)\n  const [internalSelectedRows, setInternalSelectedRows] = useState<Set<number>>(\n    new Set()\n  )\n\n  // Filter state (fullscreen only)\n  const [filters, setFilters] = useState<FilterCondition[]>([])\n  const [filterOpen, setFilterOpen] = useState(false)\n  const [sortOpen, setSortOpen] = useState(false)\n  const [sortSearch, setSortSearch] = useState('')\n\n  const rowsPerPage = 15\n  const isFullscreen = displayMode === 'fullscreen'\n\n  // Memoize controlled selection to avoid recreating Set on every render\n  const controlledSelectedSet = useMemo(() => {\n    if (!controlledSelectedRows) return null\n    return new Set(controlledSelectedRows.map((row) => tableData.indexOf(row)))\n  }, [controlledSelectedRows, tableData])\n\n  const selectedRowsSet = controlledSelectedSet ?? internalSelectedRows\n\n  // Apply filters to data (fullscreen only)\n  const filteredData = useMemo(() => {\n    if (!isFullscreen || filters.length === 0) return tableData\n\n    return tableData.filter((row) => {\n      return filters.every((filter) => {\n        const value = String(row[filter.field as keyof T] ?? '').toLowerCase()\n        const filterValue = filter.value.toLowerCase()\n\n        switch (filter.operator) {\n          case 'contains':\n            return value.includes(filterValue)\n          case 'equals':\n            return value === filterValue\n          case 'startsWith':\n            return value.startsWith(filterValue)\n          case 'endsWith':\n            return value.endsWith(filterValue)\n          case 'isEmpty':\n            return value === ''\n          case 'isNotEmpty':\n            return value !== ''\n          default:\n            return true\n        }\n      })\n    })\n  }, [tableData, filters, isFullscreen])\n\n  const handleSort = useCallback((accessor: string) => {\n    setSortConfig((current) => {\n      if (current?.key === accessor) {\n        if (current.direction === 'asc') {\n          return { key: accessor, direction: 'desc' }\n        }\n        return null\n      }\n      return { key: accessor, direction: 'asc' }\n    })\n    setSortOpen(false)\n  }, [])\n\n  const sortedData = useMemo(() => {\n    const dataToSort = isFullscreen ? filteredData : tableData\n    if (!sortConfig) return dataToSort\n\n    return [...dataToSort].sort((a, b) => {\n      const aValue = a[sortConfig.key as keyof T]\n      const bValue = b[sortConfig.key as keyof T]\n\n      if (aValue === bValue) return 0\n\n      let comparison = 0\n      if (typeof aValue === 'number' && typeof bValue === 'number') {\n        comparison = aValue - bValue\n      } else {\n        comparison = String(aValue).localeCompare(String(bValue))\n      }\n\n      return sortConfig.direction === 'asc' ? comparison : -comparison\n    })\n  }, [tableData, filteredData, sortConfig, isFullscreen])\n\n  // Pagination (fullscreen) or limit rows (inline)\n  const visibleData = isFullscreen\n    ? sortedData.slice(\n        (currentPage - 1) * rowsPerPage,\n        currentPage * rowsPerPage\n      )\n    : sortedData.slice(0, maxRows)\n\n  const totalPages = Math.ceil(sortedData.length / rowsPerPage)\n  const moreCount = totalRows\n    ? totalRows - maxRows\n    : sortedData.length > maxRows\n    ? sortedData.length - maxRows\n    : 0\n\n  // Filter helpers\n  const addFilter = () => {\n    const firstColumn = columns[0]?.accessor as string\n    setFilters([\n      ...filters,\n      {\n        id: crypto.randomUUID(),\n        field: firstColumn || '',\n        operator: 'contains',\n        value: ''\n      }\n    ])\n  }\n\n  const updateFilter = (id: string, updates: Partial<FilterCondition>) => {\n    setFilters(filters.map((f) => (f.id === id ? { ...f, ...updates } : f)))\n  }\n\n  const removeFilter = (id: string) => {\n    setFilters(filters.filter((f) => f.id !== id))\n  }\n\n  const filteredColumns = columns.filter((col) =>\n    (col.header ?? '').toLowerCase().includes(sortSearch.toLowerCase())\n  )\n\n  const handleRowSelect = useCallback(\n    (index: number) => {\n      if (selectable === 'none') return\n\n      // In fullscreen mode, calculate global index\n      const globalIndex = isFullscreen\n        ? (currentPage - 1) * rowsPerPage + index\n        : index\n\n      const newSelected = new Set(selectedRowsSet)\n\n      if (selectable === 'single') {\n        if (newSelected.has(globalIndex)) {\n          newSelected.clear()\n        } else {\n          newSelected.clear()\n          newSelected.add(globalIndex)\n        }\n      } else {\n        if (newSelected.has(globalIndex)) {\n          newSelected.delete(globalIndex)\n        } else {\n          newSelected.add(globalIndex)\n        }\n      }\n\n      setInternalSelectedRows(newSelected)\n    },\n    [\n      selectable,\n      selectedRowsSet,\n      isFullscreen,\n      currentPage,\n      rowsPerPage\n    ]\n  )\n\n  const handleSelectAll = useCallback(() => {\n    if (selectable !== 'multi') return\n\n    const allSelected = selectedRowsSet.size === visibleData.length\n    const newSelected = allSelected\n      ? new Set<number>()\n      : new Set(visibleData.map((_, i) => i))\n\n    setInternalSelectedRows(newSelected)\n  }, [selectable, selectedRowsSet.size, visibleData])\n\n  const getValue = (row: T, accessor: string): unknown => {\n    const keys = accessor.split('.')\n    let value: unknown = row\n    for (const key of keys) {\n      value = (value as Record<string, unknown>)?.[key]\n    }\n    return value\n  }\n\n  const formatNumber = (value: unknown): string => {\n    if (typeof value === 'number') {\n      return new Intl.NumberFormat('en-US').format(value)\n    }\n    return String(value ?? '')\n  }\n\n  const getSortIcon = (accessor: string) => {\n    if (sortConfig?.key !== accessor) {\n      return <Minus className=\"h-3 w-3 opacity-0 group-hover:opacity-30\" />\n    }\n    return sortConfig?.direction === 'asc' ? (\n      <ChevronUp className=\"h-3 w-3\" />\n    ) : (\n      <ChevronDown className=\"h-3 w-3\" />\n    )\n  }\n\n  const handleExpand = () => {\n    // Display mode changes are handled by the host wrapper (HostAPIProvider)\n  }\n\n  const hasSelection = selectedRowsSet.size > 0\n  const getSelectedRows = () =>\n    sortedData.filter((_, i) => selectedRowsSet.has(i))\n\n  // FULLSCREEN MODE - fills 100% of available space (host controls the container)\n  if (isFullscreen) {\n    return (\n      <div className=\"flex h-full w-full flex-col bg-background\">\n        {/* Fullscreen Header */}\n        <div className=\"flex items-center justify-between px-4 py-3 h-14\">\n          <div className=\"flex items-center gap-2\">\n            {titleImage && (\n              <img\n                src={titleImage}\n                alt={title ? `${title} icon` : \"Table icon\"}\n                className=\"h-5 w-5 rounded object-cover\"\n              />\n            )}\n            {title && <span className=\"font-medium\">{title}</span>}\n          </div>\n\n          {/* Action buttons and Filter/Sort */}\n          <div className=\"flex items-center gap-2\">\n            {selectable === 'single' && onCopy && (\n              <Button\n                variant=\"outline\"\n                size=\"sm\"\n                onClick={() => onCopy(getSelectedRows())}\n                disabled={!hasSelection}\n              >\n                <Copy className=\"mr-1.5 h-3.5 w-3.5\" />\n                Copy\n              </Button>\n            )}\n            {selectable === 'multi' && (\n              <>\n                {onDownload && (\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    onClick={() => onDownload(getSelectedRows())}\n                    disabled={!hasSelection}\n                  >\n                    <Download className=\"mr-1.5 h-3.5 w-3.5\" />\n                    Download\n                  </Button>\n                )}\n                {onShare && (\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    onClick={() => onShare(getSelectedRows())}\n                    disabled={!hasSelection}\n                  >\n                    <Share2 className=\"mr-1.5 h-3.5 w-3.5\" />\n                    Share\n                  </Button>\n                )}\n              </>\n            )}\n\n            {/* Filter Button */}\n            <Popover open={filterOpen} onOpenChange={setFilterOpen}>\n              <PopoverTrigger asChild>\n                <button\n                  className={cn(\n                    'text-sm transition-colors cursor-pointer px-2 py-1 rounded hover:bg-muted',\n                    filters.length > 0\n                      ? 'text-foreground'\n                      : 'text-muted-foreground'\n                  )}\n                >\n                  Filter\n                  {filters.length > 0 && (\n                    <span className=\"ml-1 text-xs bg-muted-foreground/20 px-1.5 py-0.5 rounded\">\n                      {filters.length}\n                    </span>\n                  )}\n                </button>\n              </PopoverTrigger>\n              <PopoverContent align=\"end\" className=\"w-auto min-w-[400px] p-0\">\n                <div className=\"p-3 space-y-3\">\n                  {filters.length === 0 ? (\n                    <p className=\"text-sm text-muted-foreground\">\n                      No filter conditions are applied\n                    </p>\n                  ) : (\n                    <div className=\"space-y-2\">\n                      {filters.map((filter, index) => (\n                        <div\n                          key={filter.id}\n                          className=\"flex items-center gap-2\"\n                        >\n                          <span className=\"text-sm text-muted-foreground w-12\">\n                            {index === 0 ? 'Where' : 'And'}\n                          </span>\n                          <Select\n                            value={filter.field}\n                            onValueChange={(value) =>\n                              updateFilter(filter.id, { field: value })\n                            }\n                          >\n                            <SelectTrigger className=\"w-32\">\n                              <SelectValue />\n                            </SelectTrigger>\n                            <SelectContent>\n                              {columns.map((col) => (\n                                <SelectItem\n                                  key={col.accessor as string}\n                                  value={col.accessor as string}\n                                >\n                                  {col.header}\n                                </SelectItem>\n                              ))}\n                            </SelectContent>\n                          </Select>\n                          <Select\n                            value={filter.operator}\n                            onValueChange={(value) =>\n                              updateFilter(filter.id, {\n                                operator: value as FilterCondition['operator']\n                              })\n                            }\n                          >\n                            <SelectTrigger className=\"w-28\">\n                              <SelectValue />\n                            </SelectTrigger>\n                            <SelectContent>\n                              <SelectItem value=\"contains\">contains</SelectItem>\n                              <SelectItem value=\"equals\">equals</SelectItem>\n                              <SelectItem value=\"startsWith\">\n                                starts with\n                              </SelectItem>\n                              <SelectItem value=\"endsWith\">\n                                ends with\n                              </SelectItem>\n                              <SelectItem value=\"isEmpty\">is empty</SelectItem>\n                              <SelectItem value=\"isNotEmpty\">\n                                is not empty\n                              </SelectItem>\n                            </SelectContent>\n                          </Select>\n                          {filter.operator !== 'isEmpty' &&\n                            filter.operator !== 'isNotEmpty' && (\n                              <Input\n                                placeholder=\"Enter a value\"\n                                value={filter.value}\n                                onChange={(e) =>\n                                  updateFilter(filter.id, {\n                                    value: e.target.value\n                                  })\n                                }\n                                className=\"w-32\"\n                              />\n                            )}\n                          <button\n                            onClick={() => removeFilter(filter.id)}\n                            aria-label=\"Remove filter\"\n                            className=\"p-1 text-muted-foreground hover:text-foreground transition-colors cursor-pointer\"\n                          >\n                            <Trash2 className=\"h-4 w-4\" />\n                          </button>\n                        </div>\n                      ))}\n                    </div>\n                  )}\n                  <div className=\"pt-1\">\n                    <Button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      onClick={addFilter}\n                      className=\"text-primary border-primary hover:bg-primary/10\"\n                    >\n                      + Add condition\n                    </Button>\n                  </div>\n                </div>\n              </PopoverContent>\n            </Popover>\n\n            {/* Sort Button */}\n            <Popover open={sortOpen} onOpenChange={setSortOpen}>\n              <PopoverTrigger asChild>\n                <button\n                  className={cn(\n                    'text-sm transition-colors cursor-pointer px-2 py-1 rounded hover:bg-muted',\n                    sortConfig ? 'text-foreground' : 'text-muted-foreground'\n                  )}\n                >\n                  Sort\n                  {sortConfig && (\n                    <span className=\"ml-1 text-xs bg-muted-foreground/20 px-1.5 py-0.5 rounded\">\n                      1\n                    </span>\n                  )}\n                </button>\n              </PopoverTrigger>\n              <PopoverContent align=\"end\" className=\"w-56 p-0\">\n                <div className=\"p-3 space-y-2\">\n                  <p className=\"text-sm font-medium\">Sort by</p>\n                  <div className=\"relative\">\n                    <Search className=\"absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n                    <Input\n                      placeholder=\"Find a field\"\n                      value={sortSearch}\n                      onChange={(e) => setSortSearch(e.target.value)}\n                      className=\"pl-8\"\n                    />\n                  </div>\n                  <div className=\"space-y-1 max-h-48 overflow-y-auto\">\n                    {filteredColumns.map((col) => (\n                      <button\n                        key={col.accessor as string}\n                        onClick={() => handleSort(col.accessor as string)}\n                        className={cn(\n                          'w-full flex items-center gap-2 px-2 py-1.5 text-sm rounded hover:bg-muted transition-colors cursor-pointer text-left',\n                          sortConfig?.key === col.accessor && 'bg-muted'\n                        )}\n                      >\n                        <Type className=\"h-4 w-4 text-muted-foreground\" />\n                        <span className=\"flex-1\">{col.header}</span>\n                        {sortConfig?.key === col.accessor &&\n                          (sortConfig?.direction === 'asc' ? (\n                            <ArrowUpAZ className=\"h-4 w-4 text-muted-foreground\" />\n                          ) : (\n                            <ArrowDownAZ className=\"h-4 w-4 text-muted-foreground\" />\n                          ))}\n                      </button>\n                    ))}\n                  </div>\n                </div>\n              </PopoverContent>\n            </Popover>\n          </div>\n        </div>\n\n        {/* Table Content */}\n        <div className=\"flex-1 overflow-auto px-4\">\n          <div className=\"w-full\">\n            <div className=\"overflow-x-auto\">\n              <table className=\"w-full text-sm\">\n                <thead className=\"border-b\">\n                  <tr>\n                    {selectable !== 'none' && (\n                      <th\n                        className={cn('w-10 px-3', compact ? 'py-2' : 'py-3')}\n                      />\n                    )}\n                    {columns.map((column, index) => (\n                      <th\n                        key={index}\n                        className={cn(\n                          'px-3 font-medium text-muted-foreground group text-left',\n                          compact ? 'py-2' : 'py-3',\n                          column.align === 'right' && 'text-right',\n                          column.sortable &&\n                            'cursor-pointer select-none hover:text-foreground'\n                        )}\n                        style={{ width: column.width }}\n                        onClick={() =>\n                          column.sortable &&\n                          handleSort((column.accessor ?? '') as string)\n                        }\n                      >\n                        <span\n                          className={cn(\n                            'inline-flex items-center gap-1',\n                            column.align === 'right' && 'justify-end'\n                          )}\n                        >\n                          {column.header ?? ''}\n                          {column.sortable &&\n                            getSortIcon((column.accessor ?? '') as string)}\n                        </span>\n                      </th>\n                    ))}\n                  </tr>\n                </thead>\n                <tbody>\n                  {visibleData.map((row, rowIndex) => {\n                    const globalIndex =\n                      (currentPage - 1) * rowsPerPage + rowIndex\n                    return (\n                      <tr\n                        key={rowIndex}\n                        onClick={() => handleRowSelect(rowIndex)}\n                        className={cn(\n                          'border-b border-border last:border-0 transition-colors',\n                          selectable !== 'none' &&\n                            'cursor-pointer hover:bg-muted/30'\n                        )}\n                      >\n                        {selectable !== 'none' && (\n                          <td className={cn('px-3', compact ? 'py-2' : 'py-3')}>\n                            <div\n                              className={cn(\n                                'flex h-4 w-4 items-center justify-center rounded border transition-colors',\n                                selectedRowsSet.has(globalIndex)\n                                  ? 'bg-foreground border-foreground text-background'\n                                  : 'border-border'\n                              )}\n                            >\n                              {selectedRowsSet.has(globalIndex) && (\n                                <Check className=\"h-3 w-3\" />\n                              )}\n                            </div>\n                          </td>\n                        )}\n                        {columns.map((column, colIndex) => {\n                          const value = getValue(row, (column.accessor ?? '') as string)\n                          const displayValue = column.render\n                            ? column.render(value, row, rowIndex)\n                            : formatNumber(value)\n\n                          return (\n                            <td\n                              key={colIndex}\n                              className={cn(\n                                'px-3',\n                                compact ? 'py-2' : 'py-3',\n                                column.align === 'center' && 'text-center',\n                                column.align === 'right' && 'text-right',\n                                colIndex === 0 && 'font-medium'\n                              )}\n                            >\n                              {displayValue}\n                            </td>\n                          )\n                        })}\n                      </tr>\n                    )\n                  })}\n                </tbody>\n              </table>\n            </div>\n\n            {/* Pagination */}\n            {totalPages > 1 && (\n              <div className=\"mt-4 flex items-center justify-between\">\n                <span className=\"text-sm text-muted-foreground\">\n                  Showing {(currentPage - 1) * rowsPerPage + 1}-\n                  {Math.min(currentPage * rowsPerPage, sortedData.length)} of{' '}\n                  {sortedData.length} rows\n                </span>\n                <div className=\"flex items-center gap-2\">\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}\n                    disabled={currentPage === 1}\n                  >\n                    <ChevronLeft className=\"h-4 w-4\" />\n                  </Button>\n                  <span className=\"text-sm\">\n                    Page {currentPage} of {totalPages}\n                  </span>\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    onClick={() =>\n                      setCurrentPage((p) => Math.min(totalPages, p + 1))\n                    }\n                    disabled={currentPage === totalPages}\n                  >\n                    <ChevronRight className=\"h-4 w-4\" />\n                  </Button>\n                </div>\n              </div>\n            )}\n\n            {/* Footer */}\n            <div className=\"flex items-center justify-between py-3 mt-2\">\n              <div className=\"flex items-center gap-1 text-xs text-muted-foreground\">\n                <span>{sortedData.length} records found</span>\n                {lastUpdated && (\n                  <>\n                    <span className=\"text-muted-foreground/50\">·</span>\n                    <span>\n                      Data as of{' '}\n                      {(typeof lastUpdated === 'string'\n                        ? new Date(lastUpdated)\n                        : lastUpdated\n                      ).toLocaleDateString('en-US', {\n                        month: 'short',\n                        day: 'numeric',\n                        year: 'numeric',\n                        hour: 'numeric',\n                        minute: '2-digit'\n                      })}\n                    </span>\n                  </>\n                )}\n              </div>\n              <button\n                onClick={onRefresh}\n                className={cn(\n                  'transition-colors',\n                  onRefresh\n                    ? 'text-muted-foreground hover:text-foreground cursor-pointer'\n                    : 'text-muted-foreground/40 cursor-not-allowed'\n                )}\n                disabled={!onRefresh}\n                aria-label=\"Refresh\"\n              >\n                <RefreshCw className=\"h-4 w-4\" />\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n    )\n  }\n\n  // INLINE MODE - compact card with limited rows\n  return (\n    <div\n      className=\"w-full rounded-lg border bg-card\"\n      style={{ maxHeight: '458px' }}\n    >\n      {/* Table Header */}\n      {showHeader && (\n        <TableHeader\n          title={title}\n          titleImage={titleImage}\n          onExpand={handleExpand}\n          selectable={selectable}\n          hasSelection={selectedRowsSet.size > 0}\n          onCopy={\n            onCopy\n              ? () =>\n                  onCopy(visibleData.filter((_, i) => selectedRowsSet.has(i)))\n              : undefined\n          }\n          onDownload={\n            onDownload\n              ? () =>\n                  onDownload(\n                    visibleData.filter((_, i) => selectedRowsSet.has(i))\n                  )\n              : undefined\n          }\n          onShare={\n            onShare\n              ? () =>\n                  onShare(visibleData.filter((_, i) => selectedRowsSet.has(i)))\n              : undefined\n          }\n        />\n      )}\n\n      {/* Mobile: Card view */}\n      <div\n        className=\"sm:hidden overflow-y-auto\"\n        style={{\n          maxHeight: `calc(458px - ${showHeader ? '57px' : '0px'} - ${\n            showFooter ? '41px' : '0px'\n          })`\n        }}\n      >\n        <div className=\"p-2 space-y-2\">\n          {loading ? (\n            Array.from({ length: 3 }).map((_, i) => (\n              <div key={i} className=\"rounded-md border bg-card p-3 space-y-2\">\n                {columns.slice(0, 4).map((_, j) => (\n                  <div\n                    key={j}\n                    className=\"h-4 bg-muted animate-pulse rounded w-3/4\"\n                  />\n                ))}\n              </div>\n            ))\n          ) : visibleData.length === 0 ? (\n            <div className=\"rounded-md border bg-card p-6 text-center text-sm text-muted-foreground\">\n              {emptyMessage}\n            </div>\n          ) : (\n            visibleData.map((row, rowIndex) => (\n              <button\n                key={rowIndex}\n                type=\"button\"\n                onClick={() => handleRowSelect(rowIndex)}\n                disabled={selectable === 'none'}\n                className={cn(\n                  'w-full rounded-md border bg-card p-3 text-left transition-all',\n                  selectable !== 'none' &&\n                    'cursor-pointer hover:border-foreground/30',\n                  selectedRowsSet.has(rowIndex) &&\n                    'border-foreground ring-1 ring-foreground'\n                )}\n              >\n                <div className=\"space-y-1.5\">\n                  {columns.map((column, colIndex) => {\n                    const value = getValue(row, (column.accessor ?? '') as string)\n                    const displayValue = column.render\n                      ? column.render(value, row, rowIndex)\n                      : formatNumber(value)\n\n                    return (\n                      <div\n                        key={colIndex}\n                        className=\"flex justify-between items-center\"\n                      >\n                        <span className=\"text-xs text-muted-foreground\">\n                          {column.header ?? ''}\n                        </span>\n                        <span\n                          className={cn(\n                            'text-xs font-medium',\n                            colIndex === 0 && 'font-semibold'\n                          )}\n                        >\n                          {displayValue}\n                        </span>\n                      </div>\n                    )\n                  })}\n                </div>\n              </button>\n            ))\n          )}\n        </div>\n      </div>\n\n      {/* Desktop: Table view */}\n      <div\n        className=\"hidden sm:block overflow-y-auto\"\n        style={{\n          maxHeight: `calc(458px - ${showHeader ? '57px' : '0px'} - ${\n            showFooter ? '41px' : '0px'\n          })`\n        }}\n      >\n        <table className=\"w-full text-sm\" role=\"grid\">\n          <thead\n            className={cn(\n              'border-b bg-muted/50',\n              stickyHeader && 'sticky top-0 z-10'\n            )}\n          >\n            <tr>\n              {selectable === 'multi' && (\n                <th className={cn('w-10 px-3', compact ? 'py-2' : 'py-3')}>\n                  <button\n                    type=\"button\"\n                    onClick={handleSelectAll}\n                    className={cn(\n                      'flex h-4 w-4 items-center justify-center rounded border transition-colors cursor-pointer',\n                      selectedRowsSet.size === visibleData.length &&\n                        visibleData.length > 0\n                        ? 'bg-foreground border-foreground text-background'\n                        : 'border-border hover:border-foreground/50'\n                    )}\n                    aria-label=\"Select all rows\"\n                  >\n                    {selectedRowsSet.size === visibleData.length &&\n                      visibleData.length > 0 && <Check className=\"h-3 w-3\" />}\n                  </button>\n                </th>\n              )}\n              {selectable === 'single' && (\n                <th className={cn('w-10 px-3', compact ? 'py-2' : 'py-3')} />\n              )}\n              {columns.map((column, index) => (\n                <th\n                  key={index}\n                  className={cn(\n                    'px-3 font-medium text-muted-foreground group text-left',\n                    compact ? 'py-2' : 'py-3',\n                    column.align === 'right' && 'text-right',\n                    column.sortable &&\n                      'cursor-pointer select-none hover:text-foreground'\n                  )}\n                  style={{ width: column.width }}\n                  onClick={() =>\n                    column.sortable && handleSort((column.accessor ?? '') as string)\n                  }\n                  role={\n                    column.sortable ? 'columnheader button' : 'columnheader'\n                  }\n                  aria-sort={\n                    sortConfig?.key === column.accessor\n                      ? sortConfig?.direction === 'asc'\n                        ? 'ascending'\n                        : 'descending'\n                      : undefined\n                  }\n                >\n                  <span\n                    className={cn(\n                      'inline-flex items-center gap-1',\n                      column.align === 'right' && 'justify-end'\n                    )}\n                  >\n                    {column.header ?? ''}\n                    {column.sortable && getSortIcon((column.accessor ?? '') as string)}\n                  </span>\n                </th>\n              ))}\n            </tr>\n          </thead>\n          <tbody>\n            {loading ? (\n              Array.from({ length: maxRows }).map((_, i) => (\n                <SkeletonRow\n                  key={i}\n                  columns={columns.length + (selectable !== 'none' ? 1 : 0)}\n                  compact={compact}\n                />\n              ))\n            ) : visibleData.length === 0 ? (\n              <tr>\n                <td\n                  colSpan={columns.length + (selectable !== 'none' ? 1 : 0)}\n                  className=\"px-3 py-8 text-center text-muted-foreground\"\n                >\n                  {emptyMessage}\n                </td>\n              </tr>\n            ) : (\n              visibleData.map((row, rowIndex) => (\n                <tr\n                  key={rowIndex}\n                  onClick={() => handleRowSelect(rowIndex)}\n                  className={cn(\n                    'border-b border-border last:border-0 transition-colors',\n                    selectable !== 'none' && 'cursor-pointer hover:bg-muted/30'\n                  )}\n                  role=\"row\"\n                  aria-selected={selectedRowsSet.has(rowIndex)}\n                >\n                  {selectable !== 'none' && (\n                    <td className={cn('px-3', compact ? 'py-2' : 'py-3')}>\n                      <div\n                        className={cn(\n                          'flex h-4 w-4 items-center justify-center rounded border transition-colors',\n                          selectedRowsSet.has(rowIndex)\n                            ? 'bg-foreground border-foreground text-background'\n                            : 'border-border'\n                        )}\n                      >\n                        {selectedRowsSet.has(rowIndex) && (\n                          <Check className=\"h-3 w-3\" />\n                        )}\n                      </div>\n                    </td>\n                  )}\n                  {columns.map((column, colIndex) => {\n                    const value = getValue(row, (column.accessor ?? '') as string)\n                    const displayValue = column.render\n                      ? column.render(value, row, rowIndex)\n                      : formatNumber(value)\n\n                    return (\n                      <td\n                        key={colIndex}\n                        className={cn(\n                          'px-3',\n                          compact ? 'py-2' : 'py-3',\n                          column.align === 'center' && 'text-center',\n                          column.align === 'right' && 'text-right',\n                          colIndex === 0 && 'font-medium'\n                        )}\n                      >\n                        {displayValue}\n                      </td>\n                    )\n                  })}\n                </tr>\n              ))\n            )}\n          </tbody>\n        </table>\n      </div>\n\n      {/* Table Footer */}\n      {showFooter && (\n        <TableFooter\n          moreCount={moreCount}\n          lastUpdated={lastUpdated}\n          onRefresh={onRefresh}\n        />\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/ui/table.tsx"
    },
    {
      "path": "registry/list/demo/list.ts",
      "content": "// Demo data for List category components\n// This file contains sample data used for component previews and documentation\n\nimport type { Product } from '../types'\n\n// Default products for ProductList\nexport const demoProducts: Product[] = [\n  {\n    name: \"Air Force 1 '07\",\n    description: 'Nike',\n    price: 119,\n    image: 'https://ui.manifest.build/demo/shoe-1.png',\n    rating: 4.9,\n    badge: 'New',\n    inStock: true\n  },\n  {\n    name: 'Air Max 90',\n    description: 'Nike',\n    price: 140,\n    image: 'https://ui.manifest.build/demo/shoe-2.png',\n    rating: 4.8,\n    inStock: true\n  },\n  {\n    name: 'Air Max Plus',\n    description: 'Nike',\n    price: 170,\n    originalPrice: 190,\n    image: 'https://ui.manifest.build/demo/shoe-4.png',\n    rating: 4.7,\n    badge: '-10%',\n    inStock: true\n  },\n  {\n    name: 'Dunk Low',\n    description: 'Nike',\n    price: 115,\n    image: 'https://ui.manifest.build/demo/shoe-3.png',\n    rating: 4.6,\n    inStock: true\n  },\n  {\n    name: 'Jordan 1 Low',\n    description: 'Nike',\n    price: 135,\n    image: 'https://ui.manifest.build/demo/shoe-1.png',\n    rating: 4.8,\n    inStock: true\n  },\n  {\n    name: 'Blazer Mid',\n    description: 'Nike',\n    price: 105,\n    image: 'https://ui.manifest.build/demo/shoe-2.png',\n    rating: 4.5,\n    inStock: true\n  },\n]\n\n// Table columns\nexport const demoTableColumns = [\n  { header: 'Name', accessor: 'name' },\n  { header: 'Email', accessor: 'email' },\n  { header: 'Status', accessor: 'status' },\n]\n\n// Table rows\nexport const demoTableRows = [\n  { name: 'John Doe', email: 'john@example.com', status: 'Active' },\n  { name: 'Jane Smith', email: 'jane@example.com', status: 'Pending' },\n  { name: 'Bob Johnson', email: 'bob@example.com', status: 'Active' },\n]\n\n// Table variant: API Usage (default)\nexport const demoApiUsageColumns = [\n  { header: 'Model', accessor: 'model', sortable: true },\n  { header: 'Total Tokens', accessor: 'totalTokens', sortable: true, align: 'right' as const },\n]\n\nexport const demoApiUsageRows = [\n  { model: 'gpt-5', totalTokens: 2267482 },\n  { model: 'claude-3.5-sonnet', totalTokens: 647528 },\n  { model: 'gemini-pro', totalTokens: 428190 },\n  { model: 'llama-3', totalTokens: 312475 },\n]\n\n// Table variant: Models (single select)\nexport const demoModelsColumns = [\n  { header: 'Model', accessor: 'model', sortable: true },\n  { header: 'Provider', accessor: 'provider', sortable: true },\n  { header: 'Context Window', accessor: 'contextWindow', sortable: true, align: 'right' as const },\n]\n\nexport const demoModelsRows = [\n  { model: 'GPT-5', provider: 'OpenAI', contextWindow: '128K' },\n  { model: 'Claude 3.5 Sonnet', provider: 'Anthropic', contextWindow: '200K' },\n  { model: 'Gemini Pro', provider: 'Google', contextWindow: '1M' },\n  { model: 'Llama 3', provider: 'Meta', contextWindow: '128K' },\n]\n\n// Table variant: Export Data (multi select)\nexport const demoExportColumns = [\n  { header: 'Date', accessor: 'date', sortable: true },\n  { header: 'Event', accessor: 'event', sortable: true },\n  { header: 'Users', accessor: 'users', sortable: true, align: 'right' as const },\n]\n\nexport const demoExportRows = [\n  { date: '2025-01-15', event: 'Page View', users: 1243 },\n  { date: '2025-01-15', event: 'Sign Up', users: 87 },\n  { date: '2025-01-14', event: 'Page View', users: 1105 },\n  { date: '2025-01-14', event: 'Purchase', users: 42 },\n]\n",
      "type": "registry:lib",
      "target": "components/ui/demo/list.ts"
    }
  ],
  "categories": [
    "list"
  ],
  "type": "registry:block"
}