{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-table-11",
  "type": "registry:component",
  "title": "DataTable 11",
  "description": "DataTable 11. A component for displaying and managing tabular data.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "button",
    "checkbox",
    "pagination",
    "select",
    "table"
  ],
  "files": [
    {
      "path": "components/watermelon/data-table-11.tsx",
      "type": "registry:component",
      "content": "'use client'\n\nimport { useMemo, useState } from 'react'\n\nimport { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon } from 'lucide-react'\n\nimport { Badge } from '@/components/base-ui/badge'\nimport { Button } from '@/components/base-ui/button'\nimport { Checkbox } from '@/components/base-ui/checkbox'\nimport { Pagination, PaginationContent, PaginationEllipsis, PaginationItem } from '@/components/base-ui/pagination'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/base-ui/select'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/base-ui/table'\nimport { usePagination } from '@/hooks/use-pagination'\nimport { cn } from '@/lib/utils'\n\ntype Availability = 'In Stock' | 'Limited' | 'Out of Stock'\n\ntype ProductItem = {\n  availability: Availability\n  id: string\n  price: number\n  productName: string\n}\n\ntype SortDirection = 'asc' | 'desc'\n\ntype SortableColumn = 'availability' | 'price' | 'productName'\n\ntype SortConfig = {\n  column: SortableColumn\n  direction: SortDirection\n}\n\nconst data: readonly ProductItem[] = [\n  { id: 'PRD-201', productName: 'Atlas Phone X', price: 699, availability: 'In Stock' },\n  { id: 'PRD-202', productName: 'North Headphones', price: 242, availability: 'In Stock' },\n  { id: 'PRD-203', productName: 'Pulse Tablet Air', price: 655, availability: 'Limited' },\n  { id: 'PRD-204', productName: 'Studio Display 24', price: 874, availability: 'In Stock' },\n  { id: 'PRD-205', productName: 'Mono Charging Dock', price: 541, availability: 'Out of Stock' },\n  { id: 'PRD-206', productName: 'Trail Smartwatch', price: 319, availability: 'Limited' },\n  { id: 'PRD-207', productName: 'Luma Keyboard', price: 189, availability: 'In Stock' },\n  { id: 'PRD-208', productName: 'Vector Camera Mini', price: 999, availability: 'Out of Stock' },\n  { id: 'PRD-209', productName: 'Glass Speaker One', price: 420, availability: 'Limited' },\n  { id: 'PRD-210', productName: 'Orbit Mouse', price: 129, availability: 'In Stock' },\n  { id: 'PRD-211', productName: 'Signal Router Max', price: 349, availability: 'Limited' },\n  { id: 'PRD-212', productName: 'Core Laptop Stand', price: 79, availability: 'In Stock' }\n] as const\n\nconst availabilityBadgeClass: Record<Availability, string> = {\n  'In Stock': 'border-none bg-green-600/10 text-green-600 dark:bg-green-400/10 dark:text-green-400',\n  'Out of Stock': 'border-none bg-destructive/10 text-destructive dark:bg-destructive/20',\n  Limited: 'border-none bg-amber-600/10 text-amber-600 dark:bg-amber-400/10 dark:text-amber-400'\n}\n\nconst formatCurrency = (price: number) =>\n  new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD'\n  }).format(price)\n\nconst DataTable11 = () => {\n  const [pageIndex, setPageIndex] = useState(0)\n  const [pageSize, setPageSize] = useState(5)\n  const [selectedIds, setSelectedIds] = useState<string[]>([])\n  const [sortConfig, setSortConfig] = useState<SortConfig>({\n    column: 'productName',\n    direction: 'asc'\n  })\n\n  const sortedData = useMemo(() => {\n    const sorted = [...data]\n\n    sorted.sort((left, right) => {\n      const leftValue = left[sortConfig.column]\n      const rightValue = right[sortConfig.column]\n\n      const comparison =\n        typeof leftValue === 'number' && typeof rightValue === 'number'\n          ? leftValue - rightValue\n          : String(leftValue).localeCompare(String(rightValue))\n\n      return sortConfig.direction === 'asc' ? comparison : -comparison\n    })\n\n    return sorted\n  }, [sortConfig])\n\n  const pageCount = Math.max(1, Math.ceil(sortedData.length / pageSize))\n  const safePageIndex = Math.min(pageIndex, pageCount - 1)\n  const currentPage = safePageIndex + 1\n  const pageStart = safePageIndex * pageSize\n  const pageEnd = pageStart + pageSize\n  const paginatedData = sortedData.slice(pageStart, pageEnd)\n\n  const { pages, showLeftEllipsis, showRightEllipsis } = usePagination({\n    currentPage,\n    totalPages: pageCount,\n    paginationItemsToDisplay: 5\n  })\n\n  const allSelectedOnPage =\n    paginatedData.length > 0 && paginatedData.every((item) => selectedIds.includes(item.id))\n  const someSelectedOnPage =\n    paginatedData.some((item) => selectedIds.includes(item.id)) && !allSelectedOnPage\n\n  const toggleAllOnPage = (checked: boolean) => {\n    if (checked) {\n      setSelectedIds((current) => Array.from(new Set([...current, ...paginatedData.map((item) => item.id)])))\n      return\n    }\n\n    setSelectedIds((current) => current.filter((id) => !paginatedData.some((item) => item.id === id)))\n  }\n\n  const toggleRow = (id: string, checked: boolean) => {\n    setSelectedIds((current) => {\n      if (checked) {\n        return current.includes(id) ? current : [...current, id]\n      }\n\n      return current.filter((item) => item !== id)\n    })\n  }\n\n  const toggleSort = (column: SortableColumn) => {\n    setSortConfig((current) => {\n      if (current.column === column) {\n        return {\n          column,\n          direction: current.direction === 'asc' ? 'desc' : 'asc'\n        }\n      }\n\n      return {\n        column,\n        direction: 'asc'\n      }\n    })\n  }\n\n  const changePageSize = (value: string | null) => {\n    if (!value) {\n      return\n    }\n\n    setPageSize(Number(value))\n    setPageIndex(0)\n  }\n\n  return (\n    <div className='w-60 sm:w-100 md:w-120 lg:w-160 2xl:w-220 mx-auto space-y-4'>\n      <div className='overflow-hidden rounded-xl border border-border/60 bg-background shadow-sm'>\n        <Table className='w-60 sm:w-100 md:w-120 lg:w-160 2xl:w-220 mx-auto'>\n          <TableHeader>\n            <TableRow className='hover:bg-transparent'>\n              <TableHead className='h-12 w-10 bg-muted/20 font-medium'>\n                <Checkbox\n                  checked={allSelectedOnPage}\n                  aria-checked={someSelectedOnPage ? 'mixed' : allSelectedOnPage}\n                  onCheckedChange={(value) => toggleAllOnPage(!!value)}\n                  aria-label='Select all products on this page'\n                  className='after:hidden data-checked:border-sky-600 data-checked:bg-sky-600 data-checked:text-white dark:data-checked:border-sky-500 dark:data-checked:bg-sky-500 dark:data-checked:text-white'\n                />\n              </TableHead>\n\n              {([\n                ['productName', 'Product Name'],\n                ['price', 'Price'],\n                ['availability', 'Availability']\n              ] as const).map(([column, label]) => {\n                const direction = sortConfig.column === column ? sortConfig.direction : undefined\n\n                return (\n                  <TableHead\n                    key={column}\n                    className='h-12 bg-muted/20 text-[13px] font-medium tracking-[0.08em] text-muted-foreground'\n                  >\n                    <button\n                      type='button'\n                      className={cn(\n                        'flex w-full items-center justify-between gap-2 text-left transition-opacity hover:opacity-80',\n                        'font-medium'\n                      )}\n                      onClick={() => toggleSort(column)}\n                    >\n                      {label}\n                      {direction === 'asc' ? (\n                        <ChevronUpIcon className='size-4 opacity-60' aria-hidden='true' />\n                      ) : direction === 'desc' ? (\n                        <ChevronDownIcon className='size-4 opacity-60' aria-hidden='true' />\n                      ) : null}\n                    </button>\n                  </TableHead>\n                )\n              })}\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {paginatedData.length > 0 ? (\n              paginatedData.map((item) => {\n                const isSelected = selectedIds.includes(item.id)\n\n                return (\n                  <TableRow\n                    key={item.id}\n                    data-state={isSelected ? 'selected' : undefined}\n                    className='hover:bg-muted/10 data-[state=selected]:bg-muted/20'\n                  >\n                    <TableCell className='py-3.5'>\n                      <Checkbox\n                        checked={isSelected}\n                        onCheckedChange={(value) => toggleRow(item.id, !!value)}\n                        aria-label={`Select ${item.productName}`}\n                        className='after:hidden data-checked:border-sky-600 data-checked:bg-sky-600 data-checked:text-white dark:data-checked:border-sky-500 dark:data-checked:bg-sky-500 dark:data-checked:text-white'\n                      />\n                    </TableCell>\n                    <TableCell className='py-3.5'>\n                      <div className='font-medium'>{item.productName}</div>\n                    </TableCell>\n                    <TableCell className='py-3.5'>\n                      <div className='font-medium'>{formatCurrency(item.price)}</div>\n                    </TableCell>\n                    <TableCell className='py-3.5'>\n                      <Badge className={availabilityBadgeClass[item.availability]}>{item.availability}</Badge>\n                    </TableCell>\n                  </TableRow>\n                )\n              })\n            ) : (\n              <TableRow>\n                <TableCell colSpan={4} className='h-24 text-center'>\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n        </Table>\n      </div>\n\n      <div className='flex items-center justify-between gap-3 rounded-xl border border-border/60 bg-background px-4 py-3 shadow-sm max-sm:flex-col'>\n        <p className='text-muted-foreground flex-1 text-sm whitespace-nowrap' aria-live='polite'>\n          Page <span className='text-foreground'>{currentPage}</span> of <span className='text-foreground'>{pageCount}</span>\n        </p>\n\n        <div className='grow'>\n          <Pagination>\n            <PaginationContent>\n              <PaginationItem>\n                <Button\n                  size='icon'\n                  variant='outline'\n                  className='disabled:pointer-events-none disabled:opacity-50'\n                  onClick={() => setPageIndex((current) => Math.max(current - 1, 0))}\n                  disabled={safePageIndex === 0}\n                  aria-label='Go to previous page'\n                >\n                  <ChevronLeftIcon aria-hidden='true' />\n                </Button>\n              </PaginationItem>\n\n              {showLeftEllipsis ? (\n                <PaginationItem>\n                  <PaginationEllipsis />\n                </PaginationItem>\n              ) : null}\n\n              {pages.map((page) => {\n                const isActive = page === currentPage\n\n                return (\n                  <PaginationItem key={page}>\n                    <Button\n                      size='icon'\n                      variant={isActive ? 'outline' : 'ghost'}\n                      onClick={() => setPageIndex(page - 1)}\n                      aria-current={isActive ? 'page' : undefined}\n                    >\n                      {page}\n                    </Button>\n                  </PaginationItem>\n                )\n              })}\n\n              {showRightEllipsis ? (\n                <PaginationItem>\n                  <PaginationEllipsis />\n                </PaginationItem>\n              ) : null}\n\n              <PaginationItem>\n                <Button\n                  size='icon'\n                  variant='outline'\n                  className='disabled:pointer-events-none disabled:opacity-50'\n                  onClick={() => setPageIndex((current) => Math.min(current + 1, pageCount - 1))}\n                  disabled={safePageIndex >= pageCount - 1}\n                  aria-label='Go to next page'\n                >\n                  <ChevronRightIcon aria-hidden='true' />\n                </Button>\n              </PaginationItem>\n            </PaginationContent>\n          </Pagination>\n        </div>\n\n        <div className='flex flex-1 justify-end'>\n          <Select value={pageSize.toString()} onValueChange={changePageSize}>\n            <SelectTrigger\n              id='results-per-page'\n              className='h-9 w-fit whitespace-nowrap border-border/60'\n              aria-label='Results per page'\n            >\n              <SelectValue placeholder='Select number of results' />\n            </SelectTrigger>\n            <SelectContent>\n              {[5, 10, 25, 50].map((size) => (\n                <SelectItem key={size} value={size.toString()}>\n                  {size} / page\n                </SelectItem>\n              ))}\n            </SelectContent>\n          </Select>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default DataTable11\n"
    }
  ]
}
