From 58b59d76d1e3533b35f8a6d56bd5a42c20410976 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Thu, 29 Oct 2026 13:50:26 +0900 Subject: [PATCH 1/3] refactor(data-table): split implementation by responsibility --- .../core/src/components/data-table/body.tsx | 115 + .../components/data-table/cell-renderers.tsx | 2 +- ...{cell-edit.test.ts => cell-values.test.ts} | 2 +- .../{cell-edit.ts => cell-values.ts} | 0 .../components/data-table/column-footer.tsx | 140 + .../components/data-table/column-layout.ts | 236 ++ .../components/data-table/column-settings.tsx | 12 +- .../components/data-table/context-menu.tsx | 345 +++ .../{data-table-context.tsx => context.tsx} | 8 + .../src/components/data-table/data-table.tsx | 2163 +-------------- ...ate-cache.test.tsx => date-cache.test.tsx} | 0 .../components/data-table/editable-cell.tsx | 1329 --------- .../cell.test.tsx} | 10 +- .../components/data-table/editing/cell.tsx | 58 + .../choice-cell.test.tsx} | 8 +- .../data-table/editing/choice-cell.tsx | 385 +++ .../date-cell.test.tsx} | 6 +- .../data-table/editing/date-cell.tsx | 246 ++ .../drafts.test.tsx} | 8 +- .../errors.test.tsx} | 8 +- .../components/data-table/editing/shared.tsx | 438 +++ .../tooltip.test.tsx} | 8 +- .../data-table/editing/typing-cell.tsx | 261 ++ .../use-navigation.test.ts} | 2 +- .../use-navigation.ts} | 0 .../use-session.test.tsx} | 2 +- .../use-session.ts} | 0 .../components/data-table/expanded-row.tsx | 250 ++ .../data-table/filter-value-utils.ts | 35 - .../data-table/filters/add-panel.tsx | 743 +++++ .../components/data-table/filters/chip.tsx | 297 ++ .../components/data-table/filters/editors.tsx | 789 ++++++ .../filters.test.tsx} | 8 +- .../components/data-table/filters/filters.tsx | 99 + .../data-table/filters/formatting.ts | 125 + .../components/data-table/filters/inputs.tsx | 133 + .../data-table/filters/operators.ts | 197 ++ .../components/data-table/filters/types.ts | 7 + .../values.test.ts} | 2 +- .../components/data-table/filters/values.ts | 195 ++ .../core/src/components/data-table/footer.tsx | 65 + .../core/src/components/data-table/header.tsx | 245 ++ .../core/src/components/data-table/index.ts | 2 +- .../src/components/data-table/pagination.tsx | 2 +- .../core/src/components/data-table/root.tsx | 108 + .../core/src/components/data-table/rows.tsx | 403 +++ .../components/data-table/selection-bar.tsx | 2 +- .../src/components/data-table/status-rows.tsx | 155 ++ .../core/src/components/data-table/table.tsx | 64 + .../src/components/data-table/toolbar.tsx | 2440 +---------------- .../src/components/data-table/use-columns.ts | 149 + .../components/data-table/use-data-table.ts | 378 +-- .../data-table/use-row-expansion.ts | 85 + .../data-table/use-row-selection.ts | 155 ++ .../data-table/use-table-measurements.ts | 113 + 55 files changed, 6694 insertions(+), 6344 deletions(-) create mode 100644 packages/core/src/components/data-table/body.tsx rename packages/core/src/components/data-table/{cell-edit.test.ts => cell-values.test.ts} (99%) rename packages/core/src/components/data-table/{cell-edit.ts => cell-values.ts} (100%) create mode 100644 packages/core/src/components/data-table/column-footer.tsx create mode 100644 packages/core/src/components/data-table/column-layout.ts create mode 100644 packages/core/src/components/data-table/context-menu.tsx rename packages/core/src/components/data-table/{data-table-context.tsx => context.tsx} (94%) rename packages/core/src/components/data-table/{data-table-date-cache.test.tsx => date-cache.test.tsx} (100%) delete mode 100644 packages/core/src/components/data-table/editable-cell.tsx rename packages/core/src/components/data-table/{data-table-editing.test.tsx => editing/cell.test.tsx} (99%) create mode 100644 packages/core/src/components/data-table/editing/cell.tsx rename packages/core/src/components/data-table/{data-table-choice.test.tsx => editing/choice-cell.test.tsx} (98%) create mode 100644 packages/core/src/components/data-table/editing/choice-cell.tsx rename packages/core/src/components/data-table/{data-table-datetime.test.tsx => editing/date-cell.test.tsx} (98%) create mode 100644 packages/core/src/components/data-table/editing/date-cell.tsx rename packages/core/src/components/data-table/{data-table-drafts.test.tsx => editing/drafts.test.tsx} (98%) rename packages/core/src/components/data-table/{data-table-errors.test.tsx => editing/errors.test.tsx} (99%) create mode 100644 packages/core/src/components/data-table/editing/shared.tsx rename packages/core/src/components/data-table/{data-table-editing-tooltip.test.tsx => editing/tooltip.test.tsx} (95%) create mode 100644 packages/core/src/components/data-table/editing/typing-cell.tsx rename packages/core/src/components/data-table/{use-cell-edit-navigation.test.ts => editing/use-navigation.test.ts} (97%) rename packages/core/src/components/data-table/{use-cell-edit-navigation.ts => editing/use-navigation.ts} (100%) rename packages/core/src/components/data-table/{use-cell-edit-session.test.tsx => editing/use-session.test.tsx} (99%) rename packages/core/src/components/data-table/{use-cell-edit-session.ts => editing/use-session.ts} (100%) create mode 100644 packages/core/src/components/data-table/expanded-row.tsx delete mode 100644 packages/core/src/components/data-table/filter-value-utils.ts create mode 100644 packages/core/src/components/data-table/filters/add-panel.tsx create mode 100644 packages/core/src/components/data-table/filters/chip.tsx create mode 100644 packages/core/src/components/data-table/filters/editors.tsx rename packages/core/src/components/data-table/{toolbar.test.tsx => filters/filters.test.tsx} (99%) create mode 100644 packages/core/src/components/data-table/filters/filters.tsx create mode 100644 packages/core/src/components/data-table/filters/formatting.ts create mode 100644 packages/core/src/components/data-table/filters/inputs.tsx create mode 100644 packages/core/src/components/data-table/filters/operators.ts create mode 100644 packages/core/src/components/data-table/filters/types.ts rename packages/core/src/components/data-table/{filter-value-utils.test.ts => filters/values.test.ts} (99%) create mode 100644 packages/core/src/components/data-table/filters/values.ts create mode 100644 packages/core/src/components/data-table/footer.tsx create mode 100644 packages/core/src/components/data-table/header.tsx create mode 100644 packages/core/src/components/data-table/root.tsx create mode 100644 packages/core/src/components/data-table/rows.tsx create mode 100644 packages/core/src/components/data-table/status-rows.tsx create mode 100644 packages/core/src/components/data-table/table.tsx create mode 100644 packages/core/src/components/data-table/use-columns.ts create mode 100644 packages/core/src/components/data-table/use-row-expansion.ts create mode 100644 packages/core/src/components/data-table/use-row-selection.ts create mode 100644 packages/core/src/components/data-table/use-table-measurements.ts diff --git a/packages/core/src/components/data-table/body.tsx b/packages/core/src/components/data-table/body.tsx new file mode 100644 index 00000000..40951bb0 --- /dev/null +++ b/packages/core/src/components/data-table/body.tsx @@ -0,0 +1,115 @@ +import { useContext, useMemo } from "react"; +import { Table } from "@/components/table"; +import { DataTableContext, PinMeasureContext } from "./context"; +import { buildColumnKeys, computePinLayout } from "./column-layout"; +import { DataTableLoaderRows, DataTableStatusRow } from "./status-rows"; +import { DataTableRows } from "./rows"; +import { useDataTableT } from "./i18n"; + +// Fallback row count when no pageSize is configured (static / uncontrolled tables) +const DEFAULT_ROWS = 5; + +/** @internal */ +function DataTableBody({ className }: { className?: string }) { + const ctx = useContext(DataTableContext); + if (!ctx) { + throw new Error(" must be used within "); + } + const { + columns: allColumns, + visibleColumns: columns, + pinnedColumns, + rows, + loading, + error, + onClickRow, + rowActions, + isRowSelected, + toggleRowSelection, + pageSize, + rowExpansion, + isRowExpanded, + toggleRowExpansion, + } = ctx; + const t = useDataTableT(); + const widths = useContext(PinMeasureContext); + const hasRowActions = !!(rowActions && rowActions.length > 0); + const hasSelection = !!toggleRowSelection; + const hasExpand = !!rowExpansion; + const totalColSpan = + (columns?.length ?? 1) + (hasRowActions ? 1 : 0) + (hasSelection ? 1 : 0) + (hasExpand ? 1 : 0); + const rowCount = pageSize > 0 ? pageSize : DEFAULT_ROWS; + const columnKeys = useMemo(() => buildColumnKeys(allColumns), [allColumns]); + const pinLayout = useMemo( + () => + computePinLayout(columns, pinnedColumns, { + hasSelection, + hasExpand, + hasRowActions, + widths, + columnKeys, + }), + [columns, pinnedColumns, hasSelection, hasExpand, hasRowActions, widths, columnKeys], + ); + const tableBodyProps = { + "data-slot": "data-table-body", + className, + }; + if (loading) { + return ( + + + + ); + } + + if (error) { + return ( + + + + {t("errorPrefix")} {error.message} + + + + ); + } + + if (!rows || rows.length === 0) { + return ( + + + {t("noData")} + + + ); + } + + return ( + + + + ); +} +DataTableBody.displayName = "DataTable.Body"; + +export { DataTableBody }; diff --git a/packages/core/src/components/data-table/cell-renderers.tsx b/packages/core/src/components/data-table/cell-renderers.tsx index ade1595e..c6b91dcf 100644 --- a/packages/core/src/components/data-table/cell-renderers.tsx +++ b/packages/core/src/components/data-table/cell-renderers.tsx @@ -3,7 +3,7 @@ import { parseDate } from "@internationalized/date"; import { Link } from "react-router"; import { BadgeList, toValueArray } from "@/components/badge-list"; import type { SelectOption } from "@/types/collection"; -import { optionLabel, resolveBadgeOptions } from "./cell-edit"; +import { optionLabel, resolveBadgeOptions } from "./cell-values"; import { normalizeTemporalFilterValue } from "@/lib/temporal-filter-values"; import type { BadgeCellOptions, diff --git a/packages/core/src/components/data-table/cell-edit.test.ts b/packages/core/src/components/data-table/cell-values.test.ts similarity index 99% rename from packages/core/src/components/data-table/cell-edit.test.ts rename to packages/core/src/components/data-table/cell-values.test.ts index aede0122..e6bf9eaa 100644 --- a/packages/core/src/components/data-table/cell-edit.test.ts +++ b/packages/core/src/components/data-table/cell-values.test.ts @@ -13,7 +13,7 @@ import { toNumberEditText, toNumberValue, toTextValue, -} from "./cell-edit"; +} from "./cell-values"; describe("typed text that isn't a number", () => { it("is reported as not a number, not silently accepted", () => { diff --git a/packages/core/src/components/data-table/cell-edit.ts b/packages/core/src/components/data-table/cell-values.ts similarity index 100% rename from packages/core/src/components/data-table/cell-edit.ts rename to packages/core/src/components/data-table/cell-values.ts diff --git a/packages/core/src/components/data-table/column-footer.tsx b/packages/core/src/components/data-table/column-footer.tsx new file mode 100644 index 00000000..8f61fd2c --- /dev/null +++ b/packages/core/src/components/data-table/column-footer.tsx @@ -0,0 +1,140 @@ +import { useContext, useMemo, type ReactNode } from "react"; +import { cn } from "@/lib/utils"; +import { Table } from "@/components/table"; +import { DataTableContext, PinMeasureContext } from "./context"; +import { + SELECTION_WIDTH, + SELECTION_KEY, + EXPAND_WIDTH, + EXPAND_KEY, + ACTIONS_WIDTH, + ACTIONS_KEY, + buildColumnKeys, + computePinLayout, + pinCellProps, + resolveAlign, +} from "./column-layout"; + +function DataTableColumnFooter() { + const ctx = useContext(DataTableContext); + if (!ctx) { + throw new Error(" must be used within "); + } + const { + columns: allColumns, + visibleColumns: columns, + pinnedColumns, + rows, + loading, + error, + toggleRowSelection, + rowExpansion, + rowActions, + } = ctx; + const widths = useContext(PinMeasureContext); + const hasSelection = !!toggleRowSelection; + const hasExpand = !!rowExpansion; + const hasRowActions = !!rowActions?.length; + const columnKeys = useMemo(() => buildColumnKeys(allColumns), [allColumns]); + const { ordered, keys, placements, selection, expand, actions } = useMemo( + () => + computePinLayout(columns, pinnedColumns, { + hasSelection, + hasExpand, + hasRowActions, + widths, + columnKeys, + }), + [columns, pinnedColumns, hasSelection, hasExpand, hasRowActions, widths, columnKeys], + ); + + if (error || !ordered.some((col) => col.footer !== undefined)) return null; + + return ( + + + {hasSelection && ( + + )} + {hasExpand && ( + + )} + {ordered.map((col) => { + const key = keys.get(col) as string; + const footerContext = { rows }; + const customFooterProps = loading ? undefined : col.footerProps?.(footerContext); + let footerContent: ReactNode = null; + if (!loading) { + footerContent = + typeof col.footer === "function" ? col.footer(footerContext) : col.footer; + } + const { style, className } = pinCellProps( + placements.get(col), + { + style: { + ...customFooterProps?.style, + ...(col.width ? { width: col.width } : {}), + }, + className: cn(resolveAlign(col) === "right" && "astw:text-right"), + }, + "footer", + ); + return ( + + {col.footer !== undefined && + (loading ? ( +