Skip to content

Commit 435be59

Browse files
authored
Merge pull request #27 from jacksonkasi1/dev
fix: add visual divider for sub-table header
2 parents a36384d + b22aee4 commit 435be59

File tree

14 files changed

+510
-50
lines changed

14 files changed

+510
-50
lines changed

apps/hono-example/src/routes/engine/configs/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { tenants } from './tenants';
22
import { users } from './users';
33
import { products } from './products';
44
import { orders } from './orders';
5+
import { orderItems } from './orderItems';
56

67
export const configs = {
78
tenants,
89
users,
910
products,
1011
orders,
12+
orderItems,
1113
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { defineTable } from "@tablecraft/engine";
2+
import * as s from "@/db/schema";
3+
4+
export const orderItems = defineTable(s.orderItems)
5+
.as("orderItems")
6+
.join(s.products, {
7+
on: "order_items.product_id = products.id",
8+
alias: "product",
9+
columns: ["name", "price"]
10+
});

apps/vite-web-example/src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Orders3Page } from "@/pages/orders3-page";
77
import { UsersPage } from "@/pages/users-page";
88
import { DashboardPage } from "@/pages/dashboard-page";
99
import { OrdersRestPage } from "@/pages/orders-rest-page";
10+
import { OrdersSubRowPage } from "@/pages/orders-subrow-page";
1011
import { EmployeesStaticPage } from "@/pages/employees-static-page";
1112
import { Button } from "@/components/ui/button";
1213
import { cn } from "@/lib/utils";
@@ -27,6 +28,7 @@ function App() {
2728
{ to: "/orders-rest", label: "Orders (REST)", icon: Plug },
2829
{ to: "/employees", label: "Employees (Static)", icon: Database },
2930
{ to: "/users", label: "Users", icon: Users },
31+
{ to: "/orders-subrow", label: "Sub-Rows", icon: Filter },
3032
];
3133

3234
return (
@@ -88,6 +90,7 @@ function App() {
8890
<Route path="/orders-rest" element={<OrdersRestPage />} />
8991
<Route path="/employees" element={<EmployeesStaticPage />} />
9092
<Route path="/users" element={<UsersPage />} />
93+
<Route path="/orders-subrow" element={<OrdersSubRowPage />} />
9194
</Routes>
9295
</main>
9396
</div>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { DataTable } from "@tablecraft/table";
2+
import { createTableCraftAdapter } from "@tablecraft/table";
3+
import { createOrdersAdapter, type OrdersRow } from "../generated";
4+
import { API_BASE_URL } from "../api";
5+
import { useMemo } from "react";
6+
7+
// --- Child Table Component ---
8+
function OrderItemsTable({ orderId }: { orderId: number }) {
9+
// Create an adapter for the orderItems table, passing the orderId as a custom filter.
10+
// We use useMemo so the adapter isn't recreated on every render.
11+
const childAdapter = useMemo(() => {
12+
return createTableCraftAdapter({
13+
baseUrl: API_BASE_URL,
14+
table: "orderItems",
15+
customFilters: {
16+
orderId: orderId, // Automatically translates to a filter constraint
17+
},
18+
});
19+
}, [orderId]);
20+
21+
return (
22+
23+
<DataTable
24+
adapter={childAdapter}
25+
hiddenColumns={["orderId", "id", "tenantId", "deletedAt", "createdAt", "updatedAt"]}
26+
config={{
27+
enableUrlState: false,
28+
enablePagination: false,
29+
enableSearch: false,
30+
enableDateFilter: false,
31+
enableToolbar: false,
32+
enableRowSelection: false,
33+
columnResizingTableId: "order-items-table",
34+
removeOuterBorder: true, // Strips the card wrapper for a seamless nested look!
35+
}}
36+
/>
37+
38+
);
39+
}
40+
41+
// --- Parent Table Component ---
42+
export function OrdersSubRowPage() {
43+
const parentAdapter = useMemo(() => {
44+
return createOrdersAdapter({
45+
baseUrl: API_BASE_URL,
46+
});
47+
}, []);
48+
49+
return (
50+
<div className="p-8 space-y-4 max-w-7xl mx-auto">
51+
<div>
52+
<h1 className="text-3xl font-bold tracking-tight">Orders (Master-Detail)</h1>
53+
<p className="text-muted-foreground mt-1">
54+
Click the arrow next to an order to see its items. The sub-table loads dynamically
55+
and manages its own state without affecting the parent URL!
56+
</p>
57+
</div>
58+
59+
<DataTable<OrdersRow>
60+
adapter={parentAdapter}
61+
renderSubRow={({ row }) => <OrderItemsTable orderId={row.id} />}
62+
config={{
63+
enableSearch: true,
64+
enableColumnResizing: true,
65+
defaultPageSize: 10,
66+
}}
67+
hiddenColumns={["tenantId", "deletedAt", "updatedAt", "userId", "role"]}
68+
/>
69+
</div>
70+
);
71+
}

bun.lock

Lines changed: 84 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/api-reference.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ The `<DataTable>` component accepts the following props:
271271
| `columns` | `ColumnDef<T>[]` | Manual column definitions (optional) |
272272
| `config` | `Partial<TableConfig>` | Table configuration overrides |
273273
| `hiddenColumns` | `string[]` | Columns to hide from UI (data still received) |
274+
| `renderSubRow` | `(props) => ReactNode` | Render a nested component when a row expands |
275+
| `getRowCanExpand`| `(row: T) => boolean` | Control which rows are allowed to be expanded |
274276

275277
### Configuration Options
276278

@@ -286,6 +288,8 @@ The `<DataTable>` component accepts the following props:
286288
enableColumnVisibility: true,
287289
defaultPageSize: 20,
288290
pageSizeOptions: [10, 20, 50, 100],
291+
removeOuterBorder: true,
292+
defaultExpanded: false,
289293
}}
290294
/>
291295
```
@@ -366,7 +370,36 @@ import type { OrdersRow, OrdersColumn } from './generated';
366370
> - If a user has previously reordered columns, their saved order takes precedence.
367371
> - "Reset Column Order" (in the View popover or Settings gear) resets back to `defaultColumnOrder`, not the natural definition order.
368372
369-
### System Column Pinning (`select` & `__actions`)
373+
### Sub-Rows (Master-Detail)
374+
375+
TableCraft supports rendering nested sub-tables or any React component inside an expandable row. When you provide the `renderSubRow` prop, TableCraft automatically injects an `__expand` column with an arrow toggle button.
376+
377+
```tsx
378+
<DataTable
379+
adapter={parentAdapter}
380+
renderSubRow={({ row, table }) => (
381+
<OrderItemsTable orderId={row.id} />
382+
)}
383+
config={{
384+
// Optional: expands all rows by default
385+
defaultExpanded: true,
386+
}}
387+
/>
388+
```
389+
390+
If you want a child table to appear seamlessly without drawing a second "card" border inside the parent, configure the child's `<DataTable>` with `removeOuterBorder: true`.
391+
392+
```tsx
393+
// Inside your OrderItemsTable component
394+
<DataTable
395+
adapter={childAdapter}
396+
config={{
397+
removeOuterBorder: true, // Strips the card wrapper!
398+
}}
399+
/>
400+
```
401+
402+
### System Column Pinning (`select`, `__actions`, & `__expand`)
370403

371404
`select` (row selection checkbox) and `__actions` (actions column) are **system columns** managed entirely by the table. They are pinned automatically on every order change:
372405

docs/features.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,21 @@ All table state stored in URL for shareable links:
178178
* Date range
179179
{% endtab %}
180180

181+
{% tab title="Sub-Rows" %}
182+
Build master-detail interfaces with nested expandable rows.
183+
184+
```tsx
185+
<DataTable
186+
adapter={parentAdapter}
187+
renderSubRow={({ row }) => <OrderItemsTable orderId={row.id} />}
188+
config={{
189+
// Removes the outer card wrapper for a seamless nested look
190+
removeOuterBorder: true,
191+
}}
192+
/>
193+
```
194+
{% endtab %}
195+
181196
{% tab title="Keyboard" %}
182197
Full keyboard accessibility:
183198

packages/table/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tablecraft/table",
3-
"version": "0.2.20",
3+
"version": "0.2.23",
44
"description": "Schema-driven data table for React — built on TanStack Table + Shadcn UI with native TableCraft engine support",
55
"type": "module",
66
"main": "dist/index.js",

packages/table/src/core/table-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { TableConfig } from "../types";
44
* Default table configuration.
55
*/
66
const defaultConfig: TableConfig = {
7+
defaultExpanded: false,
78
enableRowSelection: true,
89
enableKeyboardNavigation: false,
910
enableClickRowSelect: false,
@@ -15,6 +16,7 @@ const defaultConfig: TableConfig = {
1516
enableUrlState: true,
1617
enableColumnResizing: true,
1718
enableToolbar: true,
19+
removeOuterBorder: false,
1820
size: "default",
1921
columnResizingTableId: undefined,
2022
searchPlaceholder: undefined,

0 commit comments

Comments
 (0)