Compact card-bound table: muted uppercase header, 48pt rows, pressable rows, and cells that take strings or rich elements.
components/ui/data-table/data-table.tsx@lumioui/core@latest| columns | DataTableColumn[] | |
| rows | Record<string, React.ReactNode>[] | Cell values keyed by column.key. Strings render as text; pass elements (Badge, Chip, price Text) for richer cells. |
| onRowPress? | (row: Record<string, React.ReactNode>, index: number) => void | Makes rows pressable with ink + haptic. |
| rowLabel? | string | Label appended to each row's a11y announcement, e.g. "transaction". |
| className? | string |
Compact card-bound table: muted uppercase header, 48pt rows, pressable rows, and cells that take strings or rich elements.
import { DataTable } from "@/components/ui/data-table/data-table";
export function Example() {
return (
<DataTable />
);
} import * as React from "react";
import { forwardRef } from "react";
import { Text, View, type ViewProps } from "react-native";
import { cn, useHaptic, usePressAnimation, AnimatedPressable } from "@lumioui/core";
export interface DataTableColumn {
/** Key into each row's record. */
key: string;
/** Column header label. */
title: string;
/** Cell alignment — defaults to left. */
align?: "left" | "center" | "right";
/** Width share within the row — defaults to 1 per column. */
flex?: number;
}
export interface DataTableProps extends Omit<ViewProps, "className"> {
columns: DataTableColumn[];
/** Cell values keyed by column.key. Strings render as text; pass elements
* (Badge, Chip, price Text) for richer cells. */
rows: Record<string, React.ReactNode>[];
/** Makes rows pressable with ink + haptic. */
onRowPress?: (row: Record<string, React.ReactNode>, index: number) => void;
/** Label appended to each row's a11y announcement, e.g. "transaction". */
rowLabel?: string;
className?: string;
}
const ALIGN = {
left: "items-start",
center: "items-center",
right: "items-end",
} as const;
function TableRow({
row,
index,
columns,
onPress,
rowLabel,
}: {
row: Record<string, React.ReactNode>;
index: number;
columns: DataTableColumn[];
onPress?: DataTableProps["onRowPress"];
rowLabel?: string;
}) {
const haptics = useHaptic();
const press = usePressAnimation({ pressedScale: 0.99, pressedOpacity: 0.7 });
const cells = columns.map((col) => {
const cell = row[col.key];
return (
<View
key={col.key}
style={{ flex: col.flex ?? 1 }}
className={cn("justify-center", ALIGN[col.align ?? "left"])}
>
{typeof cell === "string" || typeof cell === "number" ? (
<Text
numberOfLines={1}
className={cn(
"text-sm",
col.align === "right" ? "font-medium text-foreground" : "text-foreground"
)}
>
{cell}
</Text>
) : (
cell
)}
</View>
);
});
if (!onPress) {
return <View className="min-h-[44px] flex-row items-center px-4 py-3">{cells}</View>;
}
return (
<AnimatedPressable
onPress={() => {
haptics.selection();
onPress(row, index);
}}
onPressIn={press.onPressIn}
onPressOut={press.onPressOut}
style={press.animatedStyle}
className="min-h-[44px] flex-row items-center px-4 py-3"
accessibilityRole="button"
accessibilityLabel={rowLabel}
>
{cells}
</AnimatedPressable>
);
}
/**
* DataTable — compact card-bound table for finance/productivity screens:
* muted uppercase header, 48pt rows, optional pressable rows. Cells are
* strings by default; drop in Badge/Chip/Text for status or money columns.
*/
export const DataTable = forwardRef<React.ElementRef<typeof View>, DataTableProps>(
function DataTable({ columns, rows, onRowPress, rowLabel, className, ...props }, ref) {
return (
<View
ref={ref}
className={cn("overflow-hidden rounded-2xl border border-border bg-card", className)}
{...props}
>
<View
className="flex-row border-b border-border bg-muted/40 px-4 py-2.5"
accessibilityRole="header"
>
{columns.map((col) => (
<View key={col.key} style={{ flex: col.flex ?? 1 }}>
<Text
numberOfLines={1}
className={cn(
"text-[11px] font-semibold uppercase tracking-wide text-muted-foreground",
col.align === "right" && "text-right",
col.align === "center" && "text-center"
)}
>
{col.title}
</Text>
</View>
))}
</View>
{rows.map((row, i) => (
<React.Fragment key={i}>
{i > 0 ? <View className="mx-4 h-px bg-border/60" /> : null}
<TableRow
row={row}
index={i}
columns={columns}
onPress={onRowPress}
rowLabel={rowLabel}
/>
</React.Fragment>
))}
</View>
);
}
);
DataTable.displayName = "DataTable";