{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "data-table", "type": "registry:ui", "title": "Data Table", "description": "Sortable table built on table primitives (tap header to sort).", "registryDependencies": [ "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/utils.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/text.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/theme.json", "https://raw.githubusercontent.com/truongnat/rnui/main/registry/dist/nativewind/table.json" ], "dependencies": [ "lucide-react-native" ], "files": [ { "path": "registry/shared/ui/data-table.tsx", "type": "registry:ui", "target": "components/ui/data-table.tsx", "content": "import { useMemo, useState } from 'react';\nimport { Pressable, View, type ViewProps } from 'react-native';\nimport { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react-native';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@/components/ui/table';\nimport { Text } from '@/components/ui/text';\nimport { cn, useIconColor } from '@/lib/utils';\n\nexport interface DataTableColumn {\n key: keyof T & string;\n header: string;\n /** Custom cell renderer — defaults to String(row[key]). */\n render?: (row: T) => React.ReactNode;\n /** Enable header tap-to-sort. */\n sortable?: boolean;\n /** Flex weight for the column. */\n flex?: number;\n align?: 'left' | 'right';\n}\n\nexport interface DataTableProps extends ViewProps {\n columns: DataTableColumn[];\n data: T[];\n /** Row key extractor. */\n rowKey: (row: T) => string;\n onRowPress?: (row: T) => void;\n emptyText?: string;\n className?: string;\n}\n\ntype SortDir = 'asc' | 'desc';\n\nexport function DataTable>({\n columns,\n data,\n rowKey,\n onRowPress,\n emptyText = 'No data',\n className,\n ...props\n}: DataTableProps) {\n const [sortKey, setSortKey] = useState(null);\n const [sortDir, setSortDir] = useState('asc');\n const iconColor = useIconColor();\n\n const sorted = useMemo(() => {\n if (!sortKey) return data;\n const dir = sortDir === 'asc' ? 1 : -1;\n return [...data].sort((a, b) => {\n const av = a[sortKey];\n const bv = b[sortKey];\n if (typeof av === 'number' && typeof bv === 'number')\n return (av - bv) * dir;\n return String(av).localeCompare(String(bv)) * dir;\n });\n }, [data, sortKey, sortDir]);\n\n const toggleSort = (key: string) => {\n if (sortKey !== key) {\n setSortKey(key);\n setSortDir('asc');\n } else if (sortDir === 'asc') {\n setSortDir('desc');\n } else {\n setSortKey(null);\n setSortDir('asc');\n }\n };\n\n return (\n \n \n \n \n {columns.map((col) => (\n \n {col.sortable ? (\n toggleSort(col.key)}\n className=\"flex-row items-center gap-1\"\n >\n \n {col.header}\n \n {sortKey === col.key ? (\n sortDir === 'asc' ? (\n \n ) : (\n \n )\n ) : (\n \n )}\n \n ) : (\n \n {col.header}\n \n )}\n \n ))}\n \n \n \n {sorted.length === 0 ? (\n \n \n \n {emptyText}\n \n \n \n ) : (\n sorted.map((row) => (\n onRowPress(row) : undefined}\n >\n {columns.map((col) => (\n \n {col.render ? (\n col.render(row)\n ) : (\n \n {String(row[col.key] ?? '')}\n \n )}\n \n ))}\n \n ))\n )}\n \n
\n
\n );\n}\n" } ] }