@multiplatform.one/table 6.4.2 → 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/ChildTable/index.native.js +1 -1
- package/dist/cjs/components/ChildTable/index.native.js.map +1 -1
- package/dist/cjs/components/ColumnHeaderFilter/index.cjs +1 -0
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js +2 -1
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableCardControls.native.js +1 -1
- package/dist/cjs/components/DataTable/DataTableCardControls.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableContext.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableMobileCards.cjs +9 -3
- package/dist/cjs/components/DataTable/DataTableMobileCards.native.js +12 -3
- package/dist/cjs/components/DataTable/DataTableMobileCards.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableRoot.cjs +133 -13
- package/dist/cjs/components/DataTable/DataTableRoot.native.js +166 -20
- package/dist/cjs/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableStateContext.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableVirtualized.cjs +3 -1
- package/dist/cjs/components/DataTable/DataTableVirtualized.native.js +3 -1
- package/dist/cjs/components/DataTable/DataTableVirtualized.native.js.map +1 -1
- package/dist/cjs/components/DataTable/index.native.js +50 -13
- package/dist/cjs/components/DataTable/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableBulkActions/index.cjs +16 -1
- package/dist/cjs/components/DataTableBulkActions/index.native.js +20 -1
- package/dist/cjs/components/DataTableBulkActions/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableCell/CellModalEditor.cjs +4 -8
- package/dist/cjs/components/DataTableCell/CellOptionsEditor.native.js.map +1 -1
- package/dist/cjs/components/DataTableCell/index.cjs +1 -33
- package/dist/cjs/components/DataTableCell/index.native.js +1 -34
- package/dist/cjs/components/DataTableCell/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableColumnPinning/index.cjs +2 -2
- package/dist/cjs/components/DataTableColumnPinning/index.native.js +2 -2
- package/dist/cjs/components/DataTableColumnPinning/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableDateFilter/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableExport/index.cjs +1 -0
- package/dist/cjs/components/DataTableExport/index.native.js +1 -0
- package/dist/cjs/components/DataTableExport/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilterButton/index.cjs +1 -1
- package/dist/cjs/components/DataTableFilterButton/index.native.js +5 -2
- package/dist/cjs/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilterChips/index.cjs +1 -1
- package/dist/cjs/components/DataTableFilterChips/index.native.js +1 -1
- package/dist/cjs/components/DataTableFilterChips/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilters/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableGrouping/index.cjs +17 -1
- package/dist/cjs/components/DataTableGrouping/index.native.js +17 -1
- package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/cjs/components/DataTablePagination/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableSearch/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableToolbar/index.cjs +1 -0
- package/dist/cjs/components/DataTableToolbar/index.native.js +2 -1
- package/dist/cjs/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableViewOptions/index.native.js.map +1 -1
- package/dist/cjs/components/FilterManager.cjs +18 -14
- package/dist/cjs/components/FilterManager.native.js +18 -14
- package/dist/cjs/components/FilterManager.native.js.map +1 -1
- package/dist/cjs/components/FilterOperatorValueEditor/index.native.js.map +1 -1
- package/dist/cjs/components/KnobDialog.native.js.map +1 -1
- package/dist/cjs/components/SavedFilterDropdown.native.js.map +1 -1
- package/dist/cjs/components/SimpleTable/index.cjs +1 -1
- package/dist/cjs/components/SimpleTable/index.native.js +1 -1
- package/dist/cjs/components/SimpleTable/index.native.js.map +1 -1
- package/dist/cjs/components/TableInput/index.native.js +4 -1
- package/dist/cjs/components/TableInput/index.native.js.map +1 -1
- package/dist/cjs/components/TableInput/shared.native.js.map +1 -1
- package/dist/cjs/hooks/useDataTable.cjs +17 -1
- package/dist/cjs/hooks/useDataTable.native.js +19 -1
- package/dist/cjs/hooks/useDataTable.native.js.map +1 -1
- package/dist/cjs/hooks/useDataTableKeyboard.native.js.map +1 -1
- package/dist/cjs/hooks/useDragScroll.native.js.map +1 -1
- package/dist/cjs/hooks/useInfiniteRows.native.js.map +1 -1
- package/dist/cjs/hooks/useSavedFilters.native.js.map +1 -1
- package/dist/cjs/hooks/useTableColumnPinning.native.js.map +1 -1
- package/dist/cjs/hooks/useTableFiltering.native.js.map +1 -1
- package/dist/cjs/hooks/useTableGrouping.native.js.map +1 -1
- package/dist/cjs/hooks/useTableKeyboard.native.js.map +1 -1
- package/dist/cjs/hooks/useTablePagination.native.js.map +1 -1
- package/dist/cjs/hooks/useTableSearch.native.js.map +1 -1
- package/dist/cjs/hooks/useTableSelection.native.js.map +1 -1
- package/dist/cjs/hooks/useTableSorting.native.js.map +1 -1
- package/dist/cjs/hooks/useTableUrlSync.native.js.map +1 -1
- package/dist/cjs/hooks/useTableViews.native.js.map +1 -1
- package/dist/cjs/shared/t.native.js.map +1 -1
- package/dist/cjs/shims/primitives.native.js.map +1 -1
- package/dist/cjs/shims/tableCellContext.native.js.map +1 -1
- package/dist/cjs/types.native.js.map +1 -1
- package/dist/cjs/utils/columnAlign.native.js.map +1 -1
- package/dist/cjs/utils/filterFields.native.js.map +1 -1
- package/dist/cjs/utils/filterUtils.native.js.map +1 -1
- package/dist/cjs/utils/paginationDefaults.native.js.map +1 -1
- package/dist/cjs/utils/rowPress.cjs +44 -0
- package/dist/cjs/utils/rowPress.native.js +56 -0
- package/dist/cjs/utils/rowPress.native.js.map +1 -0
- package/dist/cjs/utils/urlStateUtils.native.js.map +1 -1
- package/dist/cjs/utils/viewData.native.js.map +1 -1
- package/dist/cjs/utils/withInterp.native.js.map +1 -1
- package/dist/esm/components/ColumnHeaderFilter/index.mjs +1 -0
- package/dist/esm/components/ColumnHeaderFilter/index.mjs.map +1 -1
- package/dist/esm/components/ColumnHeaderFilter/index.native.js +1 -0
- package/dist/esm/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableMobileCards.mjs +9 -3
- package/dist/esm/components/DataTable/DataTableMobileCards.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableMobileCards.native.js +12 -3
- package/dist/esm/components/DataTable/DataTableMobileCards.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableRoot.mjs +133 -13
- package/dist/esm/components/DataTable/DataTableRoot.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableRoot.native.js +159 -13
- package/dist/esm/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.mjs +3 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.native.js +3 -1
- package/dist/esm/components/DataTable/DataTableVirtualized.native.js.map +1 -1
- package/dist/esm/components/DataTable/index.native.js +40 -3
- package/dist/esm/components/DataTable/index.native.js.map +1 -1
- package/dist/esm/components/DataTableBulkActions/index.mjs +16 -1
- package/dist/esm/components/DataTableBulkActions/index.mjs.map +1 -1
- package/dist/esm/components/DataTableBulkActions/index.native.js +20 -1
- package/dist/esm/components/DataTableBulkActions/index.native.js.map +1 -1
- package/dist/esm/components/DataTableCell/CellModalEditor.mjs +4 -8
- package/dist/esm/components/DataTableCell/CellModalEditor.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.mjs +1 -33
- package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.native.js +1 -34
- package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
- package/dist/esm/components/DataTableColumnPinning/index.mjs +2 -2
- package/dist/esm/components/DataTableColumnPinning/index.mjs.map +1 -1
- package/dist/esm/components/DataTableColumnPinning/index.native.js +2 -2
- package/dist/esm/components/DataTableColumnPinning/index.native.js.map +1 -1
- package/dist/esm/components/DataTableExport/index.mjs +1 -0
- package/dist/esm/components/DataTableExport/index.mjs.map +1 -1
- package/dist/esm/components/DataTableExport/index.native.js +1 -0
- package/dist/esm/components/DataTableExport/index.native.js.map +1 -1
- package/dist/esm/components/DataTableFilterButton/index.mjs +1 -1
- package/dist/esm/components/DataTableFilterButton/index.mjs.map +1 -1
- package/dist/esm/components/DataTableFilterButton/index.native.js +4 -1
- package/dist/esm/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/esm/components/DataTableFilterChips/index.mjs +1 -1
- package/dist/esm/components/DataTableFilterChips/index.mjs.map +1 -1
- package/dist/esm/components/DataTableFilterChips/index.native.js +1 -1
- package/dist/esm/components/DataTableGrouping/index.mjs +18 -3
- package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/esm/components/DataTableGrouping/index.native.js +18 -3
- package/dist/esm/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/esm/components/DataTableToolbar/index.mjs +1 -0
- package/dist/esm/components/DataTableToolbar/index.mjs.map +1 -1
- package/dist/esm/components/DataTableToolbar/index.native.js +1 -0
- package/dist/esm/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/esm/components/FilterManager.mjs +18 -14
- package/dist/esm/components/FilterManager.mjs.map +1 -1
- package/dist/esm/components/FilterManager.native.js +18 -14
- package/dist/esm/components/FilterManager.native.js.map +1 -1
- package/dist/esm/components/SimpleTable/index.mjs +1 -1
- package/dist/esm/components/SimpleTable/index.mjs.map +1 -1
- package/dist/esm/components/SimpleTable/index.native.js +1 -1
- package/dist/esm/components/TableInput/index.native.js +3 -0
- package/dist/esm/components/TableInput/index.native.js.map +1 -1
- package/dist/esm/hooks/useDataTable.mjs +17 -1
- package/dist/esm/hooks/useDataTable.mjs.map +1 -1
- package/dist/esm/hooks/useDataTable.native.js +19 -1
- package/dist/esm/hooks/useDataTable.native.js.map +1 -1
- package/dist/esm/hooks/useTableGrouping.mjs.map +1 -1
- package/dist/esm/utils/rowPress.mjs +19 -0
- package/dist/esm/utils/rowPress.mjs.map +1 -0
- package/dist/esm/utils/rowPress.native.js +28 -0
- package/dist/esm/utils/rowPress.native.js.map +1 -0
- package/dist/jsx/components/ColumnHeaderFilter/index.mjs +1 -0
- package/dist/jsx/components/ColumnHeaderFilter/index.mjs.map +1 -1
- package/dist/jsx/components/ColumnHeaderFilter/index.native.js +1 -0
- package/dist/jsx/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableMobileCards.mjs +9 -3
- package/dist/jsx/components/DataTable/DataTableMobileCards.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableMobileCards.native.js +12 -3
- package/dist/jsx/components/DataTable/DataTableMobileCards.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableRoot.mjs +133 -13
- package/dist/jsx/components/DataTable/DataTableRoot.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableRoot.native.js +159 -13
- package/dist/jsx/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.mjs +3 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.native.js +3 -1
- package/dist/jsx/components/DataTable/DataTableVirtualized.native.js.map +1 -1
- package/dist/jsx/components/DataTable/index.native.js +40 -3
- package/dist/jsx/components/DataTable/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableBulkActions/index.mjs +16 -1
- package/dist/jsx/components/DataTableBulkActions/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableBulkActions/index.native.js +20 -1
- package/dist/jsx/components/DataTableBulkActions/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableCell/CellModalEditor.mjs +4 -8
- package/dist/jsx/components/DataTableCell/CellModalEditor.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.mjs +1 -33
- package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.native.js +1 -34
- package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableColumnPinning/index.mjs +2 -2
- package/dist/jsx/components/DataTableColumnPinning/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableColumnPinning/index.native.js +2 -2
- package/dist/jsx/components/DataTableColumnPinning/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableExport/index.mjs +1 -0
- package/dist/jsx/components/DataTableExport/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableExport/index.native.js +1 -0
- package/dist/jsx/components/DataTableExport/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.mjs +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.native.js +4 -1
- package/dist/jsx/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableFilterChips/index.mjs +1 -1
- package/dist/jsx/components/DataTableFilterChips/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableFilterChips/index.native.js +1 -1
- package/dist/jsx/components/DataTableGrouping/index.mjs +18 -3
- package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableGrouping/index.native.js +17 -1
- package/dist/jsx/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableToolbar/index.mjs +1 -0
- package/dist/jsx/components/DataTableToolbar/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableToolbar/index.native.js +1 -0
- package/dist/jsx/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/jsx/components/FilterManager.mjs +18 -14
- package/dist/jsx/components/FilterManager.mjs.map +1 -1
- package/dist/jsx/components/FilterManager.native.js +18 -14
- package/dist/jsx/components/FilterManager.native.js.map +1 -1
- package/dist/jsx/components/SimpleTable/index.mjs +1 -1
- package/dist/jsx/components/SimpleTable/index.mjs.map +1 -1
- package/dist/jsx/components/SimpleTable/index.native.js +1 -1
- package/dist/jsx/components/SimpleTable/index.native.js.map +1 -1
- package/dist/jsx/components/TableInput/index.native.js +3 -0
- package/dist/jsx/components/TableInput/index.native.js.map +1 -1
- package/dist/jsx/hooks/useDataTable.mjs +17 -1
- package/dist/jsx/hooks/useDataTable.mjs.map +1 -1
- package/dist/jsx/hooks/useDataTable.native.js +19 -1
- package/dist/jsx/hooks/useDataTable.native.js.map +1 -1
- package/dist/jsx/hooks/useTableGrouping.mjs.map +1 -1
- package/dist/jsx/utils/rowPress.mjs +19 -0
- package/dist/jsx/utils/rowPress.mjs.map +1 -0
- package/dist/jsx/utils/rowPress.native.js +56 -0
- package/dist/jsx/utils/rowPress.native.js.map +1 -0
- package/package.json +12 -12
- package/src/components/ColumnHeaderFilter/index.tsx +1 -0
- package/src/components/DataTable/DataTable.spec.tsx +4 -1
- package/src/components/DataTable/DataTable.stories.tsx +190 -5
- package/src/components/DataTable/DataTableMobileCards.tsx +30 -4
- package/src/components/DataTable/DataTableRoot.tsx +195 -8
- package/src/components/DataTable/DataTableVirtualized.tsx +8 -0
- package/src/components/DataTable/cardLayout.spec.tsx +4 -1
- package/src/components/DataTable/customCellAlign.spec.tsx +124 -0
- package/src/components/DataTable/disabledContract.spec.tsx +4 -3
- package/src/components/DataTable/index.native.tsx +47 -1
- package/src/components/DataTable/rowPress.spec.tsx +229 -0
- package/src/components/DataTable/selectionSemantics.spec.tsx +156 -0
- package/src/components/DataTableBulkActions/index.tsx +16 -2
- package/src/components/DataTableBulkActions/selectionCountI18n.spec.tsx +94 -0
- package/src/components/DataTableCell/CellModalEditor.tsx +3 -1
- package/src/components/DataTableCell/DataTableCell.stories.tsx +1 -5
- package/src/components/DataTableCell/cellDraftPolicy.spec.tsx +10 -2
- package/src/components/DataTableCell/dataTableCell.spec.tsx +10 -2
- package/src/components/DataTableCell/index.tsx +5 -36
- package/src/components/DataTableColumnPinning/DataTableColumnPinning.stories.tsx +49 -17
- package/src/components/DataTableDateFilter/DataTableDateFilter.stories.tsx +4 -3
- package/src/components/DataTableExport/DataTableExport.stories.tsx +3 -2
- package/src/components/DataTableExport/index.tsx +1 -0
- package/src/components/DataTableFilterButton/index.tsx +4 -1
- package/src/components/DataTableGrouping/DataTableGrouping.spec.tsx +59 -2
- package/src/components/DataTableGrouping/DataTableGrouping.stories.tsx +17 -5
- package/src/components/DataTableGrouping/index.tsx +1 -0
- package/src/components/DataTablePagination/DataTablePagination.stories.tsx +34 -20
- package/src/components/DataTableToolbar/index.tsx +1 -0
- package/src/components/DataTableViewOptions/DataTableViewOptions.stories.tsx +2 -1
- package/src/components/FilterManager.tsx +21 -13
- package/src/components/TableInput/index.native.tsx +3 -0
- package/src/components/__scratch__/scratch.spec.tsx +46 -0
- package/src/hooks/useDataTable.ts +27 -1
- package/src/types.ts +25 -0
- package/src/utils/rowPress.ts +58 -0
- package/types/components/ColumnHeaderFilter/index.d.ts.map +1 -1
- package/types/components/DataTable/DataTableMobileCards.d.ts +13 -1
- package/types/components/DataTable/DataTableMobileCards.d.ts.map +1 -1
- package/types/components/DataTable/DataTableRoot.d.ts +1 -1
- package/types/components/DataTable/DataTableRoot.d.ts.map +1 -1
- package/types/components/DataTable/DataTableVirtualized.d.ts +7 -1
- package/types/components/DataTable/DataTableVirtualized.d.ts.map +1 -1
- package/types/components/DataTable/index.native.d.ts +1 -1
- package/types/components/DataTable/index.native.d.ts.map +1 -1
- package/types/components/DataTableBulkActions/index.d.ts.map +1 -1
- package/types/components/DataTableCell/CellModalEditor.d.ts.map +1 -1
- package/types/components/DataTableCell/index.d.ts.map +1 -1
- package/types/components/DataTableColumnPinning/index.d.ts.map +1 -1
- package/types/components/DataTableFilterButton/index.d.ts.map +1 -1
- package/types/components/DataTableGrouping/index.d.ts +8 -0
- package/types/components/DataTableGrouping/index.d.ts.map +1 -1
- package/types/components/DataTableToolbar/index.d.ts.map +1 -1
- package/types/components/FilterManager.d.ts +7 -1
- package/types/components/FilterManager.d.ts.map +1 -1
- package/types/components/TableInput/index.native.d.ts.map +1 -1
- package/types/hooks/useDataTable.d.ts.map +1 -1
- package/types/types.d.ts +23 -0
- package/types/types.d.ts.map +1 -1
- package/types/utils/rowPress.d.ts +20 -0
- package/types/utils/rowPress.d.ts.map +1 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// D-15: the selection-count label is i18n-safe — each variant is ONE
|
|
2
|
+
// whole-sentence t() key ("{{n}} selected", "{{n}} selected (+{{hidden}}
|
|
3
|
+
// filtered out)"), never concatenated English fragments, so locales own the
|
|
4
|
+
// word order. This spec runs with an initialized i18next instance whose
|
|
5
|
+
// translations deliberately differ from the keys (reordered words), proving
|
|
6
|
+
// the label routes through t() — the sibling DataTableBulkActions.spec.tsx
|
|
7
|
+
// covers the untranslated fallback (withInterp fills the raw key).
|
|
8
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
9
|
+
import { cleanup, screen } from "@testing-library/react";
|
|
10
|
+
import i18next from "i18next";
|
|
11
|
+
import { initReactI18next } from "react-i18next";
|
|
12
|
+
import React from "react";
|
|
13
|
+
import {
|
|
14
|
+
getCoreRowModel,
|
|
15
|
+
getFilteredRowModel,
|
|
16
|
+
getPaginationRowModel,
|
|
17
|
+
useReactTable,
|
|
18
|
+
} from "@tanstack/react-table";
|
|
19
|
+
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
|
20
|
+
import { DataTableStateContext } from "../DataTable/DataTableStateContext";
|
|
21
|
+
import { type BulkAction, DataTableBulkActions } from "./index";
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
// Same natural-language keys registered in packages/i18n/en/common.json,
|
|
25
|
+
// mapped to a pseudo-locale that reorders the sentence.
|
|
26
|
+
await i18next.use(initReactI18next).init({
|
|
27
|
+
lng: "en",
|
|
28
|
+
interpolation: { escapeValue: false },
|
|
29
|
+
resources: {
|
|
30
|
+
en: {
|
|
31
|
+
translation: {
|
|
32
|
+
"{{n}} selected": "selected: {{n}}",
|
|
33
|
+
"{{n}} selected (+{{hidden}} filtered out)":
|
|
34
|
+
"selected: {{n}} (hidden by filter: {{hidden}})",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(cleanup);
|
|
42
|
+
|
|
43
|
+
type Row = { id: string; name: string; status: string };
|
|
44
|
+
|
|
45
|
+
const rows: Row[] = [
|
|
46
|
+
{ id: "a", name: "Alpha", status: "ready" },
|
|
47
|
+
{ id: "b", name: "Bravo", status: "blocked" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const actions: BulkAction<Row>[] = [{ id: "x", label: "X", onExecute: vi.fn() }];
|
|
51
|
+
|
|
52
|
+
function Harness({ columnFilters = [] }: { columnFilters?: Array<{ id: string; value: any }> }) {
|
|
53
|
+
const [rowSelection, setRowSelection] = React.useState<Record<string, boolean>>({
|
|
54
|
+
a: true,
|
|
55
|
+
b: true,
|
|
56
|
+
});
|
|
57
|
+
const table = useReactTable({
|
|
58
|
+
data: rows,
|
|
59
|
+
columns: [
|
|
60
|
+
{ id: "name", accessorKey: "name", header: "Name" },
|
|
61
|
+
{ id: "status", accessorKey: "status", header: "Status" },
|
|
62
|
+
] as any,
|
|
63
|
+
state: { rowSelection, columnFilters, pagination: { pageIndex: 0, pageSize: 10 } },
|
|
64
|
+
onRowSelectionChange: setRowSelection,
|
|
65
|
+
getCoreRowModel: getCoreRowModel(),
|
|
66
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
67
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
68
|
+
getRowId: (row) => row.id,
|
|
69
|
+
enableRowSelection: true,
|
|
70
|
+
});
|
|
71
|
+
const ctxValue = React.useMemo(
|
|
72
|
+
() => ({ table, state: {} as any, actions: {} as any, computed: {} as any }) as any,
|
|
73
|
+
[table, rowSelection, columnFilters],
|
|
74
|
+
);
|
|
75
|
+
return (
|
|
76
|
+
<DataTableStateContext.Provider value={ctxValue}>
|
|
77
|
+
<DataTableBulkActions actions={actions} />
|
|
78
|
+
</DataTableStateContext.Provider>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("selection count label i18n (D-15)", () => {
|
|
83
|
+
it("base count renders through the {{n}} selected key", () => {
|
|
84
|
+
renderWithProviders(<Harness />);
|
|
85
|
+
expect(screen.getByText("selected: 2")).toBeInTheDocument();
|
|
86
|
+
expect(screen.queryByText(/\d selected/)).not.toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("filtered-out annotation renders through its own whole-sentence key", () => {
|
|
90
|
+
renderWithProviders(<Harness columnFilters={[{ id: "status", value: "ready" }]} />);
|
|
91
|
+
expect(screen.getByText("selected: 2 (hidden by filter: 1)")).toBeInTheDocument();
|
|
92
|
+
expect(screen.queryByText(/filtered out/)).not.toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* footer stays within the two-action dialog budget (LC-31).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { DialogOverlay } from "@multiplatform.one/components";
|
|
15
16
|
import { FieldDisplay, type FieldDisplayType } from "@multiplatform.one/forms";
|
|
16
17
|
import { TableCellContext } from "@multiplatform.one/table-primitives";
|
|
17
18
|
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
@@ -118,11 +119,12 @@ export function CellModalEditor({
|
|
|
118
119
|
</View>
|
|
119
120
|
<Dialog open={open} onOpenChange={handleOpenChange} modal>
|
|
120
121
|
<Dialog.Portal>
|
|
121
|
-
<
|
|
122
|
+
<DialogOverlay opacity={0.5} />
|
|
122
123
|
<Dialog.Content
|
|
123
124
|
borderWidth={1}
|
|
124
125
|
borderColor="$borderColor"
|
|
125
126
|
elevation={knobProps.elevation}
|
|
127
|
+
transition={knobProps.transition}
|
|
126
128
|
enterStyle={{ y: -10, opacity: 0, scale: 0.96 }}
|
|
127
129
|
exitStyle={{ y: 10, opacity: 0, scale: 0.96 }}
|
|
128
130
|
maxWidth={720}
|
|
@@ -16,11 +16,7 @@ import { fn } from "storybook/test";
|
|
|
16
16
|
import { Table } from "@multiplatform.one/table-primitives";
|
|
17
17
|
import React from "react";
|
|
18
18
|
import { Text, View, XStack, YStack } from "tamagui";
|
|
19
|
-
import type {
|
|
20
|
-
DataTableCellRenderContext,
|
|
21
|
-
DataTableField,
|
|
22
|
-
DataTableFieldProps,
|
|
23
|
-
} from "../../types";
|
|
19
|
+
import type { DataTableCellRenderContext, DataTableField, DataTableFieldProps } from "../../types";
|
|
24
20
|
import { DataTableCell } from "./index";
|
|
25
21
|
|
|
26
22
|
// Custom renderers receive the cell's honest state as the 4th argument and
|
|
@@ -188,7 +188,11 @@ function MockMultiDatePicker({ value, onChange }: any) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
// Partial mock: real module first (module-scope exports like `zIndex` keep
|
|
192
|
+
// resolving for transitive imports), field components faked on top — the
|
|
193
|
+
// spec asserts draft/commit policy per field, not the fields themselves.
|
|
194
|
+
vi.mock("@multiplatform.one/forms", async (importOriginal) => ({
|
|
195
|
+
...(await importOriginal<typeof import("@multiplatform.one/forms")>()),
|
|
192
196
|
Checkbox: createMock("Checkbox"),
|
|
193
197
|
Switch: createMock("Switch"),
|
|
194
198
|
Select: MockSelect,
|
|
@@ -231,7 +235,11 @@ vi.mock("@multiplatform.one/rich-text", () => ({
|
|
|
231
235
|
RichTextEditor: createMock("RichTextEditor"),
|
|
232
236
|
}));
|
|
233
237
|
|
|
234
|
-
|
|
238
|
+
// Partial mock (importOriginal): only the knobs hook is stubbed — real
|
|
239
|
+
// module-scope exports (MIN_PRESS_TARGET, press-target helpers, formatters)
|
|
240
|
+
// keep resolving for every component this spec transitively imports.
|
|
241
|
+
vi.mock("@multiplatform.one/theme", async (importOriginal) => ({
|
|
242
|
+
...(await importOriginal<typeof import("@multiplatform.one/theme")>()),
|
|
235
243
|
useResolvedKnobs: () => ({
|
|
236
244
|
knobProps: {
|
|
237
245
|
sizeToken: "$3",
|
|
@@ -30,7 +30,11 @@ function createMock(name: string) {
|
|
|
30
30
|
return Comp;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// Partial mock: real module first (module-scope exports like `zIndex` keep
|
|
34
|
+
// resolving for transitive imports), field components faked on top — the
|
|
35
|
+
// spec asserts ROUTING to fields, not the fields themselves.
|
|
36
|
+
vi.mock("@multiplatform.one/forms", async (importOriginal) => ({
|
|
37
|
+
...(await importOriginal<typeof import("@multiplatform.one/forms")>()),
|
|
34
38
|
Checkbox: createMock("Checkbox"),
|
|
35
39
|
Switch: createMock("Switch"),
|
|
36
40
|
Select: createMock("Select"),
|
|
@@ -94,7 +98,11 @@ vi.mock("@multiplatform.one/rich-text", () => ({
|
|
|
94
98
|
RichTextEditor: createMock("RichTextEditor"),
|
|
95
99
|
}));
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
// Partial mock (importOriginal): only the knobs hook is stubbed — real
|
|
102
|
+
// module-scope exports (MIN_PRESS_TARGET, press-target helpers, formatters)
|
|
103
|
+
// keep resolving for every component this spec transitively imports.
|
|
104
|
+
vi.mock("@multiplatform.one/theme", async (importOriginal) => ({
|
|
105
|
+
...(await importOriginal<typeof import("@multiplatform.one/theme")>()),
|
|
98
106
|
useResolvedKnobs: () => ({
|
|
99
107
|
knobProps: {
|
|
100
108
|
sizeToken: "$3",
|
|
@@ -175,19 +175,16 @@ export function DataTableCell({
|
|
|
175
175
|
// large tables performant and gives heavy fields a purpose-built compact
|
|
176
176
|
// representation. Edit-mode (below) still mounts the full field component.
|
|
177
177
|
if (readOnly) {
|
|
178
|
-
//
|
|
179
|
-
// ISO-
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
fieldProps.currency != null
|
|
183
|
-
? { ...fieldProps, currency: normalizeCurrencyCode(fieldProps.currency) }
|
|
184
|
-
: fieldProps;
|
|
178
|
+
// Currency symbols ride through untouched: the money register in
|
|
179
|
+
// `@multiplatform.one/theme` owns symbol→ISO-code resolution (LC-12 one
|
|
180
|
+
// channel per data class) and prefixes a symbol it has no code for, so a
|
|
181
|
+
// cell never has to sanitize a value on the way to the formatter.
|
|
185
182
|
return (
|
|
186
183
|
<CellWrapper justify={cellJustify}>
|
|
187
184
|
<FieldDisplay
|
|
188
185
|
field={field}
|
|
189
186
|
value={value}
|
|
190
|
-
fieldProps={
|
|
187
|
+
fieldProps={fieldProps}
|
|
191
188
|
linkedValue={_linkedValue}
|
|
192
189
|
/>
|
|
193
190
|
</CellWrapper>
|
|
@@ -1182,34 +1179,6 @@ function DraftFieldEditor({
|
|
|
1182
1179
|
);
|
|
1183
1180
|
}
|
|
1184
1181
|
|
|
1185
|
-
const CURRENCY_SYMBOL_TO_CODE: Record<string, string> = {
|
|
1186
|
-
$: "USD",
|
|
1187
|
-
"€": "EUR",
|
|
1188
|
-
"£": "GBP",
|
|
1189
|
-
"¥": "JPY",
|
|
1190
|
-
"₹": "INR",
|
|
1191
|
-
"₩": "KRW",
|
|
1192
|
-
"₽": "RUB",
|
|
1193
|
-
R$: "BRL",
|
|
1194
|
-
};
|
|
1195
|
-
|
|
1196
|
-
const currencyCodeCache = new Map<string, string | undefined>();
|
|
1197
|
-
|
|
1198
|
-
function normalizeCurrencyCode(currency: unknown): string | undefined {
|
|
1199
|
-
if (typeof currency !== "string" || !currency) return undefined;
|
|
1200
|
-
if (currencyCodeCache.has(currency)) return currencyCodeCache.get(currency);
|
|
1201
|
-
const mapped = CURRENCY_SYMBOL_TO_CODE[currency] ?? currency;
|
|
1202
|
-
let resolved: string | undefined;
|
|
1203
|
-
try {
|
|
1204
|
-
new Intl.NumberFormat(undefined, { style: "currency", currency: mapped });
|
|
1205
|
-
resolved = mapped;
|
|
1206
|
-
} catch {
|
|
1207
|
-
resolved = undefined;
|
|
1208
|
-
}
|
|
1209
|
-
currencyCodeCache.set(currency, resolved);
|
|
1210
|
-
return resolved;
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
1182
|
function CellWrapper({
|
|
1214
1183
|
children,
|
|
1215
1184
|
justify = "flex-start",
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useReactTable,
|
|
9
9
|
} from "@tanstack/react-table";
|
|
10
10
|
import { useState } from "react";
|
|
11
|
+
import { formatCurrency, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
11
12
|
import { Button } from "@multiplatform.one/forms";
|
|
12
13
|
import { Card, H3, Paragraph, ScrollView, Separator, Text, View, XStack, YStack } from "tamagui";
|
|
13
14
|
import { DataTableColumnPinning, useColumnPinning } from "./index";
|
|
@@ -25,10 +26,25 @@ interface Person {
|
|
|
25
26
|
performance: number;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Seeded PRNG (mulberry32). The fixture must produce byte-identical rows on
|
|
31
|
+
* every page load: `Math.random()` here redrew Salary and the performance-bar
|
|
32
|
+
* width per capture, which is what flapped the visual baselines for this
|
|
33
|
+
* family. Determinism at the source keeps every pixel under comparison, where
|
|
34
|
+
* a screenshot mask or delay would not.
|
|
35
|
+
*/
|
|
36
|
+
const seededRandom = (seed: number) => () => {
|
|
37
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
38
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
39
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
40
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
41
|
+
};
|
|
42
|
+
|
|
28
43
|
const generateData = (count: number): Person[] => {
|
|
29
44
|
const departments = ["Engineering", "Sales", "Marketing", "HR", "Finance"];
|
|
30
45
|
const roles = ["Manager", "Senior", "Junior", "Lead", "Intern"];
|
|
31
46
|
const statuses: ("active" | "inactive" | "pending")[] = ["active", "inactive", "pending"];
|
|
47
|
+
const random = seededRandom(0x5eed);
|
|
32
48
|
|
|
33
49
|
return Array.from({ length: count }, (_, i) => ({
|
|
34
50
|
id: i + 1,
|
|
@@ -37,16 +53,44 @@ const generateData = (count: number): Person[] => {
|
|
|
37
53
|
email: `user${i + 1}@example.com`,
|
|
38
54
|
department: departments[i % departments.length],
|
|
39
55
|
role: roles[i % roles.length],
|
|
40
|
-
salary: 50000 + Math.floor(
|
|
56
|
+
salary: 50000 + Math.floor(random() * 100000),
|
|
41
57
|
startDate: new Date(2020 + (i % 4), i % 12, (i % 28) + 1).toISOString().split("T")[0],
|
|
42
58
|
status: statuses[i % statuses.length],
|
|
43
|
-
performance: Math.floor(
|
|
59
|
+
performance: Math.floor(random() * 100),
|
|
44
60
|
}));
|
|
45
61
|
};
|
|
46
62
|
|
|
47
63
|
const defaultData = generateData(50);
|
|
48
64
|
const columnHelper = createColumnHelper<Person>();
|
|
49
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Mini performance track. A component rather than an inline cell renderer so
|
|
68
|
+
* the track's radius can follow the knob (LC-60): the previous pinned `$2`
|
|
69
|
+
* outlived `borderRadius:none` on an 8px-tall bar, which reads as an
|
|
70
|
+
* undeclared knob-immune shape.
|
|
71
|
+
*/
|
|
72
|
+
function PerformanceBar({ value }: { value: number }) {
|
|
73
|
+
const { knobProps } = useResolvedKnobs();
|
|
74
|
+
return (
|
|
75
|
+
<XStack alignItems="center" gap="$2">
|
|
76
|
+
<View
|
|
77
|
+
backgroundColor="$gray5"
|
|
78
|
+
height={8}
|
|
79
|
+
flex={1}
|
|
80
|
+
{...knobProps.containerRadius}
|
|
81
|
+
overflow="hidden"
|
|
82
|
+
>
|
|
83
|
+
<View
|
|
84
|
+
backgroundColor={value > 70 ? "$green10" : value > 40 ? "$yellow10" : "$red10"}
|
|
85
|
+
height="100%"
|
|
86
|
+
width={`${value}%`}
|
|
87
|
+
/>
|
|
88
|
+
</View>
|
|
89
|
+
<Text fontSize="$1">{value}%</Text>
|
|
90
|
+
</XStack>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
50
94
|
const defaultColumns = [
|
|
51
95
|
columnHelper.accessor("id", {
|
|
52
96
|
header: "ID",
|
|
@@ -76,7 +120,8 @@ const defaultColumns = [
|
|
|
76
120
|
}),
|
|
77
121
|
columnHelper.accessor("salary", {
|
|
78
122
|
header: "Salary",
|
|
79
|
-
cell: (info) =>
|
|
123
|
+
cell: (info) =>
|
|
124
|
+
formatCurrency(info.getValue() as number, { symbol: "$", fractionDigits: "auto" }),
|
|
80
125
|
}),
|
|
81
126
|
columnHelper.accessor("startDate", {
|
|
82
127
|
header: "Start Date",
|
|
@@ -100,20 +145,7 @@ const defaultColumns = [
|
|
|
100
145
|
}),
|
|
101
146
|
columnHelper.accessor("performance", {
|
|
102
147
|
header: "Performance",
|
|
103
|
-
cell: (info) => (
|
|
104
|
-
<XStack alignItems="center" gap="$2">
|
|
105
|
-
<View backgroundColor="$gray5" height={8} flex={1} borderRadius="$2" overflow="hidden">
|
|
106
|
-
<View
|
|
107
|
-
backgroundColor={
|
|
108
|
-
info.getValue() > 70 ? "$green10" : info.getValue() > 40 ? "$yellow10" : "$red10"
|
|
109
|
-
}
|
|
110
|
-
height="100%"
|
|
111
|
-
width={`${info.getValue()}%`}
|
|
112
|
-
/>
|
|
113
|
-
</View>
|
|
114
|
-
<Text fontSize="$1">{info.getValue()}%</Text>
|
|
115
|
-
</XStack>
|
|
116
|
-
),
|
|
148
|
+
cell: (info) => <PerformanceBar value={info.getValue()} />,
|
|
117
149
|
}),
|
|
118
150
|
];
|
|
119
151
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { action } from "@multiplatform.one/storybook";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import { formatAbsoluteDate } from "@multiplatform.one/theme";
|
|
3
4
|
import { Card, Text, XStack, YStack } from "tamagui";
|
|
4
5
|
import { DataTableDateFilter, type DateFilterValue, applyDateFilter } from "./index";
|
|
5
6
|
|
|
@@ -200,10 +201,10 @@ export const WithDateConstraints = () => {
|
|
|
200
201
|
|
|
201
202
|
<YStack gap="$2">
|
|
202
203
|
<Text fontSize="$3" color="$gray11">
|
|
203
|
-
Min Date: {minDate
|
|
204
|
+
Min Date: {formatAbsoluteDate(minDate, { year: "always" })}
|
|
204
205
|
</Text>
|
|
205
206
|
<Text fontSize="$3" color="$gray11">
|
|
206
|
-
Max Date: {maxDate
|
|
207
|
+
Max Date: {formatAbsoluteDate(maxDate, { year: "always" })}
|
|
207
208
|
</Text>
|
|
208
209
|
</YStack>
|
|
209
210
|
</YStack>
|
|
@@ -302,7 +303,7 @@ export const WithDataIntegration = () => {
|
|
|
302
303
|
{task.title}
|
|
303
304
|
</Text>
|
|
304
305
|
<Text fontSize="$2" color="$gray11">
|
|
305
|
-
Created: {
|
|
306
|
+
Created: {formatAbsoluteDate(task.createdAt)}
|
|
306
307
|
</Text>
|
|
307
308
|
</YStack>
|
|
308
309
|
<Text
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { action } from "@multiplatform.one/storybook";
|
|
2
|
+
import { formatAbsoluteDate, formatNumber } from "@multiplatform.one/theme";
|
|
2
3
|
import { Card, Text, YStack } from "tamagui";
|
|
3
4
|
import { Table } from "@multiplatform.one/table-primitives";
|
|
4
5
|
import { DataTableExport } from "./index";
|
|
@@ -97,7 +98,7 @@ const productColumns = [
|
|
|
97
98
|
{
|
|
98
99
|
accessorKey: "createdAt",
|
|
99
100
|
header: "Created",
|
|
100
|
-
cell: (info: any) =>
|
|
101
|
+
cell: (info: any) => formatAbsoluteDate(info.getValue()),
|
|
101
102
|
},
|
|
102
103
|
];
|
|
103
104
|
|
|
@@ -424,7 +425,7 @@ export const WithComplexData = () => {
|
|
|
424
425
|
{
|
|
425
426
|
accessorKey: "metrics",
|
|
426
427
|
header: "Users",
|
|
427
|
-
cell: (info: any) => info.getValue().users
|
|
428
|
+
cell: (info: any) => formatNumber(info.getValue().users),
|
|
428
429
|
},
|
|
429
430
|
{
|
|
430
431
|
accessorKey: "features",
|
|
@@ -298,6 +298,7 @@ function ExportOptionsPopover({
|
|
|
298
298
|
{...knobProps.borderRadius}
|
|
299
299
|
borderWidth={1}
|
|
300
300
|
borderColor="$borderColor"
|
|
301
|
+
transition={knobProps.transition}
|
|
301
302
|
enterStyle={{ x: 0, y: -10, opacity: 0 }}
|
|
302
303
|
exitStyle={{ x: 0, y: -10, opacity: 0 }}
|
|
303
304
|
minWidth={popoverMinWidth}
|
|
@@ -46,7 +46,10 @@ function FieldRow({ field, highlighted, onSelect }: FieldRowProps) {
|
|
|
46
46
|
cursor="pointer"
|
|
47
47
|
paddingHorizontal="$2.5"
|
|
48
48
|
paddingVertical="$2"
|
|
49
|
-
|
|
49
|
+
// LC-55 FRAGMENT-LICENSE (D-03, G-08 arm): a hoverable option row is
|
|
50
|
+
// wayfinding, not chrome-earning — the full borderRadius fragment
|
|
51
|
+
// carries borderWidth and drew a permanent ring under the hover fill.
|
|
52
|
+
{...knobProps.containerRadius}
|
|
50
53
|
backgroundColor={highlighted ? "$color4" : "transparent"}
|
|
51
54
|
hoverStyle={{ backgroundColor: "$color4" }}
|
|
52
55
|
pressStyle={{ backgroundColor: "$color5" }}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { fireEvent, screen } from "@testing-library/react";
|
|
2
2
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
3
|
+
import { formatNumber } from "@multiplatform.one/theme";
|
|
3
4
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import "@testing-library/jest-dom";
|
|
5
6
|
import { DataTable } from "../DataTable";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type AggregationConfig,
|
|
9
|
+
DataTableGrouping,
|
|
10
|
+
formatAggregateValue,
|
|
11
|
+
groupData,
|
|
12
|
+
} from "./index";
|
|
7
13
|
|
|
8
14
|
// Test data type
|
|
9
15
|
type SalesRecord = {
|
|
@@ -115,7 +121,58 @@ describe("DataTableGrouping", () => {
|
|
|
115
121
|
|
|
116
122
|
// Should show aggregation values
|
|
117
123
|
expect(screen.getByText("North America")).toBeInTheDocument();
|
|
118
|
-
//
|
|
124
|
+
// LC-12 render channel: the sum reaches the slot through the house
|
|
125
|
+
// number register, so it reads like the cells it summarises.
|
|
126
|
+
expect(screen.getByText(formatNumber(15400))).toBeInTheDocument();
|
|
127
|
+
expect(screen.queryByText("15400")).not.toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("routes the header item count through the house number register (LC-12)", () => {
|
|
131
|
+
// Same data class as the aggregates: a large group's count must read
|
|
132
|
+
// localized ("(1,000 items)"), never raw beside channel-formatted sums.
|
|
133
|
+
const bigData = Array.from({ length: 1000 }, (_, i) => ({
|
|
134
|
+
id: String(i),
|
|
135
|
+
region: "North America",
|
|
136
|
+
}));
|
|
137
|
+
renderWithProviders(
|
|
138
|
+
<DataTableGrouping
|
|
139
|
+
data={bigData}
|
|
140
|
+
columns={[{ accessorKey: "region", header: "Region", type: "string" as const }]}
|
|
141
|
+
groupBy={["region"]}
|
|
142
|
+
/>,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const expected = `(${formatNumber(1000, { maximumFractionDigits: 0 })} items)`;
|
|
146
|
+
expect(screen.getByText(expected)).toBeInTheDocument();
|
|
147
|
+
expect(screen.queryByText("(1000 items)")).not.toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("formatAggregateValue (LC-12 number channel)", () => {
|
|
151
|
+
const sum: AggregationConfig = { column: "totalRevenue", function: "sum" };
|
|
152
|
+
const count: AggregationConfig = { column: "quantity", function: "count" };
|
|
153
|
+
|
|
154
|
+
it("routes aggregates through the house number register", () => {
|
|
155
|
+
expect(formatAggregateValue(15400, sum)).toBe(formatNumber(15400));
|
|
156
|
+
expect(formatAggregateValue(5133.33, sum)).toBe(formatNumber(5133.33));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("keeps counts whole", () => {
|
|
160
|
+
expect(formatAggregateValue(3, count)).toBe("3");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("formats a numeric string from a custom aggregation", () => {
|
|
164
|
+
expect(formatAggregateValue("15400", sum)).toBe(formatNumber(15400));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("passes non-numeric custom results through as their own text", () => {
|
|
168
|
+
expect(formatAggregateValue("mixed", sum)).toBe("mixed");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("keeps the zero floor for missing and non-finite values", () => {
|
|
172
|
+
expect(formatAggregateValue(undefined, sum)).toBe("0");
|
|
173
|
+
expect(formatAggregateValue(Number.NaN, sum)).toBe("0");
|
|
174
|
+
expect(formatAggregateValue(0, sum)).toBe("0");
|
|
175
|
+
});
|
|
119
176
|
});
|
|
120
177
|
|
|
121
178
|
it("calls onGroupingChange when grouping config changes", () => {
|
|
@@ -442,19 +442,31 @@ export const WithGroupingControls = () => {
|
|
|
442
442
|
);
|
|
443
443
|
};
|
|
444
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Seeded PRNG (mulberry32). The aggregate rows this story renders (Total
|
|
447
|
+
* Revenue, Item Count) are derived from the fixture, so `Math.random()` made
|
|
448
|
+
* the shot differ on every capture and flapped the visual baseline. Seeding
|
|
449
|
+
* keeps the whole surface under pixel comparison.
|
|
450
|
+
*/
|
|
451
|
+
const seededRandom = (seed: number) => () => {
|
|
452
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
453
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
454
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
455
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
456
|
+
};
|
|
457
|
+
|
|
445
458
|
export const PerformanceTest = () => {
|
|
446
459
|
// Generate larger dataset
|
|
460
|
+
const random = seededRandom(0x5eed);
|
|
447
461
|
const largeDataset = Array.from({ length: 1000 }, (_, i) => ({
|
|
448
462
|
id: `item-${i + 1}`,
|
|
449
463
|
region: ["North America", "Europe", "Asia Pacific", "South America"][i % 4],
|
|
450
464
|
category: ["Electronics", "Furniture", "Appliances", "Books"][i % 4],
|
|
451
465
|
salesperson: `Person ${(i % 10) + 1}`,
|
|
452
|
-
quantity: Math.floor(
|
|
453
|
-
unitPrice: Math.floor(
|
|
466
|
+
quantity: Math.floor(random() * 20) + 1,
|
|
467
|
+
unitPrice: Math.floor(random() * 1000) + 100,
|
|
454
468
|
totalRevenue: 0, // Will be calculated
|
|
455
|
-
date: new Date(Date.now() -
|
|
456
|
-
.toISOString()
|
|
457
|
-
.split("T")[0],
|
|
469
|
+
date: new Date(Date.now() - random() * 365 * 24 * 60 * 60 * 1000).toISOString().split("T")[0],
|
|
458
470
|
})).map((item) => ({
|
|
459
471
|
...item,
|
|
460
472
|
totalRevenue: item.quantity * item.unitPrice,
|
|
@@ -559,6 +559,7 @@ export function DataTableGroupingConfig<T = any>(props: DataTableGroupingConfigP
|
|
|
559
559
|
{...knobProps.borderRadius}
|
|
560
560
|
borderWidth={1}
|
|
561
561
|
borderColor="$borderColor"
|
|
562
|
+
transition={knobProps.transition}
|
|
562
563
|
enterStyle={{ x: 0, y: -10, opacity: 0 }}
|
|
563
564
|
exitStyle={{ x: 0, y: -10, opacity: 0 }}
|
|
564
565
|
minWidth={popoverMinWidth}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { action } from "@multiplatform.one/storybook";
|
|
2
|
+
import type { GuardrailSpecimenParameter } from "@multiplatform.one/theme";
|
|
2
3
|
import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
|
|
3
4
|
import React from "react";
|
|
4
|
-
|
|
5
|
-
import {
|
|
5
|
+
// Catalog Button + Input (LC-56 STORY-HONESTY — no raw interactive primitives).
|
|
6
|
+
import { Button, Input } from "@multiplatform.one/forms";
|
|
7
|
+
import { Text, View, XStack, YStack } from "tamagui";
|
|
6
8
|
import type {
|
|
7
9
|
DataTableColumn,
|
|
8
10
|
PageLoadQuery,
|
|
@@ -80,7 +82,20 @@ export const Compact: Story = {
|
|
|
80
82
|
},
|
|
81
83
|
};
|
|
82
84
|
|
|
85
|
+
// LC-57 allowlist: the boundary stories below EXIST to demo pager edges —
|
|
86
|
+
// first/last/single/empty page rest with the unreachable-direction buttons
|
|
87
|
+
// disabled by design (the honest boundary state, LC-40-washed). The warns
|
|
88
|
+
// still fire; the harvester tolerates exactly these codes on these ids.
|
|
89
|
+
const pagerBoundarySpecimen = {
|
|
90
|
+
guardrailSpecimen: {
|
|
91
|
+
warns: ["bare-disabled"],
|
|
92
|
+
reason:
|
|
93
|
+
"Pager-boundary specimen: unreachable nav directions rest disabled by design at page edges.",
|
|
94
|
+
} satisfies GuardrailSpecimenParameter,
|
|
95
|
+
};
|
|
96
|
+
|
|
83
97
|
export const FirstPage: Story = {
|
|
98
|
+
parameters: pagerBoundarySpecimen,
|
|
84
99
|
args: {
|
|
85
100
|
...Basic.args,
|
|
86
101
|
pagination: {
|
|
@@ -101,6 +116,7 @@ export const MiddlePage: Story = {
|
|
|
101
116
|
};
|
|
102
117
|
|
|
103
118
|
export const LastPage: Story = {
|
|
119
|
+
parameters: pagerBoundarySpecimen,
|
|
104
120
|
args: {
|
|
105
121
|
...Basic.args,
|
|
106
122
|
pagination: {
|
|
@@ -172,6 +188,7 @@ export const SmallDataSet: Story = {
|
|
|
172
188
|
};
|
|
173
189
|
|
|
174
190
|
export const SinglePage: Story = {
|
|
191
|
+
parameters: pagerBoundarySpecimen,
|
|
175
192
|
args: {
|
|
176
193
|
...Basic.args,
|
|
177
194
|
pagination: {
|
|
@@ -406,6 +423,7 @@ export const PageJumpDemo: Story = {
|
|
|
406
423
|
...samplePagination,
|
|
407
424
|
pageIndex: 5,
|
|
408
425
|
});
|
|
426
|
+
const [jumpValue, setJumpValue] = React.useState("");
|
|
409
427
|
|
|
410
428
|
const handlePaginationChange = (newPagination: TablePagination) => {
|
|
411
429
|
setPagination({
|
|
@@ -445,24 +463,19 @@ export const PageJumpDemo: Story = {
|
|
|
445
463
|
</Text>
|
|
446
464
|
<XStack gap="$2" marginTop="$2" alignItems="center">
|
|
447
465
|
<Text>Go to page:</Text>
|
|
448
|
-
<
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
handlePageJump("1");
|
|
462
|
-
}}
|
|
463
|
-
size="$2"
|
|
464
|
-
padding="$1"
|
|
465
|
-
>
|
|
466
|
+
<YStack width={110}>
|
|
467
|
+
<Input
|
|
468
|
+
keyboardType="numeric"
|
|
469
|
+
placeholder={`1-${pagination.totalPages}`}
|
|
470
|
+
onChangeText={setJumpValue}
|
|
471
|
+
inputProps={{
|
|
472
|
+
"aria-label": "Go to page",
|
|
473
|
+
onSubmitEditing: (e: { nativeEvent: { text: string } }) =>
|
|
474
|
+
handlePageJump(e.nativeEvent.text),
|
|
475
|
+
}}
|
|
476
|
+
/>
|
|
477
|
+
</YStack>
|
|
478
|
+
<Button onPress={() => handlePageJump(jumpValue)} size="$2" padding="$1">
|
|
466
479
|
Go
|
|
467
480
|
</Button>
|
|
468
481
|
</XStack>
|
|
@@ -571,6 +584,7 @@ export const ErrorStates: Story = {
|
|
|
571
584
|
};
|
|
572
585
|
|
|
573
586
|
export const EdgeCases: Story = {
|
|
587
|
+
parameters: pagerBoundarySpecimen,
|
|
574
588
|
args: {
|
|
575
589
|
...Basic.args,
|
|
576
590
|
pagination: {
|