Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/bright-csv-exporter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
"@tailor-platform/app-shell": minor
---

Add `useCsvExporter` and the standalone `CsvExporter` dialog for client-side, cursor-paginated CSV downloads with filename editing, progress, cancellation, UTF-8 BOM, and spreadsheet formula escaping. It is data-client and DataTable independent, so it can be opened from any button or collection UI. Fetchers return `CsvExportPage` values shaped as `{ rows, pageInfo, total? }`, using the same row representation as DataTable.

```tsx
const { open, props } = useCsvExporter({ defaultFilename: "products.csv", columns, fetcher });

<Button onClick={open}>Export CSV</Button>
<CsvExporter {...props} />;
```

Add `useDataTableCsvExporter` and `DataTable.CSVExporter` for table-integrated exports. The dedicated hook takes no column configuration and can be called outside `DataTable.Root`; the component accepts only its branded exporter props and supplies the enclosing table's visible columns in their current order when a download starts. Changing the table layout does not change an export already in progress, and hiding all exportable columns fails before fetching.

```tsx
const { props: tableExporter } = useDataTableCsvExporter({
defaultFilename: "products.csv",
fetcher,
});

<DataTable.Root value={table}>
<DataTable.CSVExporter {...tableExporter} />
</DataTable.Root>;
```
95 changes: 90 additions & 5 deletions docs-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,34 @@
"examples": null
}
},
"csv-exporter": {
"slug": "csv-exporter",
"kind": "code-backed",
"outline": "docs-src/components/csv-exporter.docs.outline.md",
"output": "docs/components/csv-exporter.md",
"examples": null,
"sources": ["packages/core/src/components/csv-exporter/**"],
"claims": [],
"symbols": [
"CsvCursorFetcher",
"CsvExportCell",
"CsvExportColumn",
"CsvExportPage",
"CsvExportPhase",
"CsvExporter",
"CsvExporterProps",
"CsvExporterState",
"UseCsvExporterOptions",
"useCsvExporter"
],
"hashes": {
"typeSurface": "b5d2153372721fdd",
"outline": "1b4bdf78e1a3c576",
"snapshot": null,
"outputMd": "216f1bb6310cc04a",
"examples": null
}
},
"csv-importer": {
"slug": "csv-importer",
"kind": "code-backed",
Expand Down Expand Up @@ -404,6 +432,7 @@
"Column",
"DataTable",
"DataTableAction",
"DataTableCSVExporterProps",
"DataTableContextValue",
"DataTableData",
"DataTableFilterConfig",
Expand Down Expand Up @@ -433,6 +462,7 @@
"TableOrderableFieldName",
"UseCollectionOptions",
"UseCollectionReturn",
"UseDataTableCsvExporterOptions",
"UseDataTableOptions",
"UseDataTableReturn",
"createColumnHelper",
Expand All @@ -442,14 +472,15 @@
"useCollectionVariables",
"useDataTable",
"useDataTableContext",
"useDataTableCsvExporter",
"useURLCollectionVariables",
"withURLCollectionState"
],
"hashes": {
"typeSurface": "d2910e2d95012c6c",
"outline": "5fc3f188a0f9fb8d",
"typeSurface": "5661e39bd9f8e6fc",
"outline": "ced1f8dd3c0835be",
"snapshot": null,
"outputMd": "7a44214b5f1fdc6b",
"outputMd": "e9d635c725336316",
"examples": null
}
},
Expand Down Expand Up @@ -1514,6 +1545,57 @@
"examples": null
}
},
"use-csv-exporter": {
"slug": "use-csv-exporter",
"kind": "code-backed",
"outline": "docs-src/hooks/use-csv-exporter.docs.outline.md",
"output": "docs/api/use-csv-exporter.md",
"examples": null,
"sources": [
"packages/core/src/components/csv-exporter/use-csv-exporter.ts",
"packages/core/src/components/csv-exporter/types.ts"
],
"claims": [],
"symbols": [
"CsvCursorFetcher",
"CsvExportCell",
"CsvExportColumn",
"CsvExportPage",
"CsvExportPhase",
"CsvExporterProps",
"CsvExporterState",
"UseCsvExporterOptions",
"useCsvExporter"
],
"hashes": {
"typeSurface": "0aa0163146f1c3d9",
"outline": "e8496bb30fac3fd1",
"snapshot": null,
"outputMd": "b6fc07903fdca468",
"examples": null
}
},
"use-data-table-csv-exporter": {
"slug": "use-data-table-csv-exporter",
"kind": "code-backed",
"outline": "docs-src/hooks/use-data-table-csv-exporter.docs.outline.md",
"output": "docs/api/use-data-table-csv-exporter.md",
"examples": null,
"sources": ["packages/core/src/components/data-table/use-data-table-csv-exporter.ts"],
"claims": [],
"symbols": [
"DataTableCSVExporterProps",
"UseDataTableCsvExporterOptions",
"useDataTableCsvExporter"
],
"hashes": {
"typeSurface": "ae941a345a689328",
"outline": "c582da24e5f09fad",
"snapshot": null,
"outputMd": "df7e58ddf9cc4cc2",
"examples": null
}
},
"use-override-breadcrumb": {
"slug": "use-override-breadcrumb",
"kind": "prose",
Expand Down Expand Up @@ -1692,8 +1774,9 @@
"packages/core/skills/app-shell-patterns/references/components/checkbox.md": "14f7ba23f441df38",
"packages/core/skills/app-shell-patterns/references/components/combobox.md": "dfb775c7c4307409",
"packages/core/skills/app-shell-patterns/references/components/command-palette.md": "7e5bc675a593791a",
"packages/core/skills/app-shell-patterns/references/components/csv-exporter.md": "216f1bb6310cc04a",
"packages/core/skills/app-shell-patterns/references/components/csv-importer.md": "53d3c795ca21b9dc",
"packages/core/skills/app-shell-patterns/references/components/data-table.md": "7a44214b5f1fdc6b",
"packages/core/skills/app-shell-patterns/references/components/data-table.md": "e9d635c725336316",
"packages/core/skills/app-shell-patterns/references/components/date-picker.md": "bc69f14829004f9d",
"packages/core/skills/app-shell-patterns/references/components/default-header.md": "375e55f4b12fa2be",
"packages/core/skills/app-shell-patterns/references/components/default-sidebar.md": "3422388a0cc78571",
Expand Down Expand Up @@ -1738,6 +1821,8 @@
"packages/core/skills/app-shell-patterns/references/api/use-app-shell-config.md": "1a0a937f3a412c13",
"packages/core/skills/app-shell-patterns/references/api/use-app-shell-data.md": "2d2eaf5aa04d95d3",
"packages/core/skills/app-shell-patterns/references/api/use-auth.md": "a7d907ac79d24944",
"packages/core/skills/app-shell-patterns/references/api/use-csv-exporter.md": "b6fc07903fdca468",
"packages/core/skills/app-shell-patterns/references/api/use-data-table-csv-exporter.md": "df7e58ddf9cc4cc2",
"packages/core/skills/app-shell-patterns/references/api/use-override-breadcrumb.md": "6ec55fc3dfd8c21d",
"packages/core/skills/app-shell-patterns/references/api/use-page-meta.md": "f988bdc0fb180f0c",
"packages/core/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md": "7144ae3dc68aa65b",
Expand All @@ -1757,6 +1842,6 @@
"packages/core/skills/app-shell-patterns/references/patterns/list-dense-scan.md": "916baa3cd2b9cbf1",
"packages/core/skills/app-shell-patterns/references/pages/document-detail.md": "717ef4e7ae835aff",
"packages/core/skills/app-shell-patterns/references/migrations.md": "013c3fef2303b096",
"packages/core/skills/app-shell-patterns/SKILL.md": "526decbfd2db6555"
"packages/core/skills/app-shell-patterns/SKILL.md": "208232be7e1385b3"
}
}
63 changes: 63 additions & 0 deletions docs-src/components/csv-exporter.docs.outline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
kind: code-backed
group: csv-exporter
title: CsvExporter
description: Filename dialog and progress UI for client-side cursor-paginated CSV downloads
sources:
- packages/core/src/components/csv-exporter/**
---

# CsvExporter

`CsvExporter` is the standalone UI for [`useCsvExporter`](../api/use-csv-exporter.md). It provides an editable filename dialog, fetch progress, and cancellation without requiring a DataTable. It does not render a trigger button; use the hook's `open()` action to open it from your own button or action.

## Import

```tsx
import { CsvExporter, useCsvExporter } from "@tailor-platform/app-shell";
```

## Usage

```tsx
const { open, props } = useCsvExporter({
defaultFilename: "products.csv",
columns: [
{ header: "Product code", value: (product) => product.code },
{ header: "Name", value: (product) => product.name },
],
fetcher,
});

<Button onClick={open}>Download products</Button>
<CsvExporter {...props} />;
```

`useCsvExporter` owns the state. Its `open()` function opens the dialog, while `props` contains the controlled `open`, `onOpenChange`, and `exporter` values consumed by this component.

## Props

| Prop | Type | Description |
| -------------- | ------------------------- | ------------------------------------------------------ |
| `open` | `boolean` | Whether the exporter dialog is open. |
| `onOpenChange` | `(open: boolean) => void` | Called when the dialog opens or closes. |
| `exporter` | `CsvExporterState` | Export state and actions returned by `useCsvExporter`. |

The filename input is validated through AppShell `Form` and `Field`. While fetching, the dialog shows fetched rows and a determinate progress bar when the fetcher returns `total`. The user can cancel the request, or dismiss and reopen the dialog while the export continues.

## DataTable

For an export coupled to a table, use [`useDataTableCsvExporter`](../api/use-data-table-csv-exporter.md) with [`DataTable.CSVExporter`](./data-table.md#datatablecsvexporter). The dedicated hook takes no `columns` option and can be called outside `DataTable.Root`:

```tsx
const { props: tableExporter } = useDataTableCsvExporter({
defaultFilename: "products.csv",
fetcher,
});

<DataTable.Root value={table}>
<DataTable.CSVExporter {...tableExporter} />
</DataTable.Root>;
```

This variant includes its own trigger button and always exports the enclosing table's visible columns in their current order at download start. It accepts only the dedicated hook's branded exporter props, not the result of `useCsvExporter`. Use standalone `CsvExporter` for a fixed schema or custom report columns.
33 changes: 33 additions & 0 deletions docs-src/components/data-table.docs.outline.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
DataTable,
useDataTable,
useDataTableContext,
useDataTableCsvExporter,
useCollectionVariables,
createColumnHelper,
type Column,
Expand Down Expand Up @@ -161,6 +162,7 @@ function JournalsPage() {
| `DataTable.Filters` | Add-filter panel + active filter chips, auto-generated from column filter configs. Requires `control` from `useCollectionVariables`. |
| `DataTable.Footer` | Footer container for pagination and other footer content. Optional. Becomes the bulk-action bar while rows are selected — see [Selection actions](#selection-actions). |
| `DataTable.Pagination` | Pre-built pagination controls with optional row count and selection info. Requires `control` from `useCollectionVariables`. Place inside `DataTable.Footer`. |
| `DataTable.CSVExporter` | CSV download button and filename/progress dialog. Pass the dedicated props from [`useDataTableCsvExporter`](../api/use-data-table-csv-exporter.md). |

### `DataTable.Root` Props

Expand Down Expand Up @@ -213,6 +215,37 @@ By default `DataTable.Filters` renders the active filter chips plus the **Add fi
</Toolbar.Root>
```

### `DataTable.CSVExporter` Props

| Prop | Type | Description |
| -------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `open` | `boolean` | Whether the exporter dialog is open. |
| `onOpenChange` | `(open: boolean) => void` | Called when the dialog opens or closes. |
| `exporter` | `DataTableCSVExporterProps<TRow>["exporter"]` | Branded export state from [`useDataTableCsvExporter`](../api/use-data-table-csv-exporter.md). |

`DataTable.CSVExporter` opens a dialog where the user can change the filename before downloading. During the export it shows the fetched row count and a percentage when the fetcher returns `total`; it also provides a cancel action. It accepts only props from `useDataTableCsvExporter`, whose `columns` option is intentionally unavailable. The hook can be called outside `DataTable.Root`; the component reads the enclosing table's visible columns and their order when the download starts. Hiding all exportable columns fails before fetching. Changes to visibility or order after an export starts apply to the next export, not the in-flight CSV. For a fixed schema or custom report columns, use standalone [`CsvExporter`](./csv-exporter.md) with `useCsvExporter` instead.

Compose the toolbar layout normally inside `DataTable.Root` when the exporter should sit immediately before the built-in Columns control:

```tsx
const { props } = useDataTableCsvExporter({
defaultFilename: "products.csv",
fetcher,
});

<Toolbar.Root>
<Toolbar.Row justify="between" aria-label="Table controls">
<Toolbar.Group>
<DataTable.Filters />
</Toolbar.Group>
<Toolbar.Group>
<DataTable.CSVExporter {...props} />
<DataTable.ColumnSettings />
</Toolbar.Group>
</Toolbar.Row>
</Toolbar.Root>;
```

### `DataTable.Pagination` Props

| Prop | Type | Default | Description |
Expand Down
Loading
Loading