{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"ai-data-table","type":"registry:ui","title":"AiDataTable","description":"Compact comparison table for a model's structured answer: real table semantics, tabular numeric columns, check-or-dash booleans, and one tintable column — rendered in the order it arrived, with no sorting","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["ai-agents"],"registryDependencies":["https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/ai-data-table/AiDataTable.tsx","content":"\"use client\";\n\nimport { forwardRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"../../utils\";\nimport \"./ai-data-table.css\";\n\n/** One column, rendered in the order it appears in the array. */\nexport interface AiDataTableColumn {\n\t/** Key looked up on every row. */\n\tkey: string;\n\t/** Header text. */\n\tlabel: string;\n\t/** Horizontal alignment. Defaults to right on a numeric column, left otherwise. */\n\talign?: \"left\" | \"center\" | \"right\";\n\t/** Renders the column with tabular figures, and right-aligns it unless `align` says otherwise. */\n\tnumeric?: boolean;\n}\n\n/** Everything a cell is allowed to hold — the four things a model actually emits. */\nexport type AiDataTableValue = string | number | boolean | null | undefined;\n\n/** One row, keyed by column key. Missing keys read as empty. */\nexport type AiDataTableRow = Record<string, AiDataTableValue>;\n\n/**\n * Props for AiDataTable\n */\nexport interface AiDataTableProps {\n\t/** The columns to render, in order. */\n\tcolumns: AiDataTableColumn[];\n\t/** The rows to render, in the order the model produced them. */\n\trows: AiDataTableRow[];\n\t/** Describes the table for assistive tech. Hidden on screen unless `captionVisible`. */\n\tcaption?: string;\n\t/** Shows `caption` as a muted line above the table. */\n\tcaptionVisible?: boolean;\n\t/** Tighter rows, for a table read at a glance rather than studied. */\n\tdense?: boolean;\n\t/** Key of the column to tint — the recommendation, or whichever criterion decided it. */\n\thighlightColumn?: string;\n\t/** Replaces the default cell rendering. Receives the raw value and its position. */\n\tcell?: (value: unknown, at: { row: number; key: string }) => ReactNode;\n\t/** Additional CSS classes */\n\tclassName?: string;\n}\n\n/** Spoken in place of the glyphs, which say nothing out loud. */\nconst TRUE_LABEL = \"True\";\nconst FALSE_LABEL = \"False\";\nconst EMPTY_LABEL = \"No value\";\n\n/**\n * Written out rather than interpolated: Tailwind reads this file as text, and a\n * class assembled at runtime is a class it never sees.\n */\nconst ALIGN_CLASS = {\n\tleft: \"text-left\",\n\tcenter: \"text-center\",\n\tright: \"text-right\",\n} as const;\n\n/** Numbers lean right by default — that is where the eye compares magnitudes. */\nfunction alignOf(column: AiDataTableColumn): \"left\" | \"center\" | \"right\" {\n\treturn column.align ?? (column.numeric ? \"right\" : \"left\");\n}\n\n/**\n * Renders tabular model output — columns and rows, an optional highlighted\n * column, and accessible glyphs for booleans and empty values.\n *\n * The root element arrives through the ref channel rather than a `ref` prop,\n * per PORTING.md — the Svelte source declares `ref = $bindable(null)`.\n *\n * The wrapper is the only thing allowed to scroll sideways: a model asked for\n * nine criteria will produce nine columns, and the page around it must not go\n * on a horizontal rail because of that. Which is why it is focusable and\n * named — a scroll container that only answers to a pointer is unreachable\n * for anyone driving the page from the keyboard.\n */\nexport const AiDataTable = forwardRef<HTMLDivElement, AiDataTableProps>(\n\t(\n\t\t{ columns, rows, caption, captionVisible = false, dense = false, highlightColumn, cell, className },\n\t\tref\n\t) => {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\"ft-datatable w-full max-w-full overflow-x-auto\", className)}\n\t\t\t\ttabIndex={0}\n\t\t\t\trole=\"region\"\n\t\t\t\taria-label={caption ?? \"Data table\"}\n\t\t\t>\n\t\t\t\t<table\n\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\"ft-datatable-table w-full border-collapse text-sm\",\n\t\t\t\t\t\tdense && \"ft-datatable-dense\"\n\t\t\t\t\t)}\n\t\t\t\t>\n\t\t\t\t\t{caption ? (\n\t\t\t\t\t\t<caption\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\tcaptionVisible\n\t\t\t\t\t\t\t\t\t? \"ft-datatable-caption text-muted-foreground pb-2 text-left text-xs\"\n\t\t\t\t\t\t\t\t\t: \"ft-datatable-caption sr-only\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{caption}\n\t\t\t\t\t\t</caption>\n\t\t\t\t\t) : null}\n\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<tr className=\"border-border border-b\">\n\t\t\t\t\t\t\t{columns.map((column, columnIndex) => (\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tkey={columnIndex}\n\t\t\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\"text-muted-foreground text-xs font-medium whitespace-nowrap\",\n\t\t\t\t\t\t\t\t\t\tALIGN_CLASS[alignOf(column)],\n\t\t\t\t\t\t\t\t\t\tcolumn.numeric && \"tabular-nums\",\n\t\t\t\t\t\t\t\t\t\tcolumn.key === highlightColumn && \"ft-datatable-highlight\"\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{column.label}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</thead>\n\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{rows.map((row, rowIndex) => (\n\t\t\t\t\t\t\t<tr key={rowIndex} className={cn(rowIndex % 2 === 1 && \"ft-datatable-zebra\")}>\n\t\t\t\t\t\t\t\t{columns.map((column, columnIndex) => {\n\t\t\t\t\t\t\t\t\tconst value = row[column.key];\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\t\t\t\tkey={columnIndex}\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\"align-top\",\n\t\t\t\t\t\t\t\t\t\t\t\tALIGN_CLASS[alignOf(column)],\n\t\t\t\t\t\t\t\t\t\t\t\tcolumn.numeric && \"tabular-nums\",\n\t\t\t\t\t\t\t\t\t\t\t\tcolumn.key === highlightColumn && \"ft-datatable-highlight\"\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{cell ? (\n\t\t\t\t\t\t\t\t\t\t\t\tcell(value, { row: rowIndex, key: column.key })\n\t\t\t\t\t\t\t\t\t\t\t) : typeof value === \"boolean\" ? (\n\t\t\t\t\t\t\t\t\t\t\t\tvalue ? (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<svg\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclassName=\"ft-datatable-true inline-block size-3.5 align-[-0.15em]\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tviewBox=\"0 0 24 24\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrokeWidth=\"3\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<path d=\"m5 13 4 4L19 7\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"sr-only\">{TRUE_LABEL}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"ft-datatable-false text-muted-foreground\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t–\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</span>{\" \"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"sr-only\">{FALSE_LABEL}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t) : value === null || value === undefined || value === \"\" ? (\n\t\t\t\t\t\t\t\t\t\t\t\t// An empty string is the third way a model says nothing, alongside\n\t\t\t\t\t\t\t\t\t\t\t\t// `null` and a key it never mentioned, so it reads the same way\n\t\t\t\t\t\t\t\t\t\t\t\t// rather than as a cell the renderer dropped on the floor.\n\t\t\t\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"ft-datatable-empty text-muted-foreground\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t—\n\t\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"sr-only\">{EMPTY_LABEL}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t</div>\n\t\t);\n\t}\n);\n\nAiDataTable.displayName = \"AiDataTable\";\n","type":"registry:ui","target":"@ui/fancy/components/ai-data-table/AiDataTable.tsx"},{"path":"react/src/components/ai-data-table/ai-data-table.css","content":"/*\n * Padding lives here rather than on every cell so a consumer can retune the\n * whole grid with one custom property instead of restyling each element.\n */\n.ft-datatable-table th,\n.ft-datatable-table td {\n\tpadding-block: var(--ft-table-pad-y, 0.5rem);\n\tpadding-inline: var(--ft-table-pad-x, 0.75rem);\n}\n\n.ft-datatable-table.ft-datatable-dense th,\n.ft-datatable-table.ft-datatable-dense td {\n\tpadding-block: var(--ft-table-dense-pad-y, 0.25rem);\n}\n\n/*\n * Zebra sits on the row and the tint on the cell, so the two never fight over\n * one background: a tinted cell simply paints over the stripe underneath it.\n * Both are mixes of `currentColor`, which is how they stay legible on a light\n * page and a dark one without knowing which they are on.\n */\n.ft-datatable-zebra {\n\tbackground: var(--ft-table-zebra, color-mix(in oklab, currentColor 3%, transparent));\n}\n\n.ft-datatable-highlight {\n\tbackground: var(--ft-table-highlight, color-mix(in oklab, currentColor 5%, transparent));\n}\n\n.ft-datatable-true {\n\tcolor: var(\n\t\t--ft-table-true,\n\t\tvar(--ft-status-done, light-dark(oklch(0.5 0.14 145), oklch(0.72 0.15 145)))\n\t);\n}\n","type":"registry:ui","target":"@ui/fancy/components/ai-data-table/ai-data-table.css"},{"path":"react/src/components/ai-data-table/index.ts","content":"\"use client\";\n\nexport { AiDataTable } from \"./AiDataTable\";\nexport type {\n\tAiDataTableProps,\n\tAiDataTableColumn,\n\tAiDataTableRow,\n\tAiDataTableValue,\n} from \"./AiDataTable\";\n","type":"registry:ui","target":"@ui/fancy/components/ai-data-table/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/ai-data-table?framework=react"}}