@multiplatform.one/table 6.4.3 → 6.7.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/ColumnHeaderFilter/index.cjs +1 -0
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js +1 -0
- package/dist/cjs/components/ColumnHeaderFilter/index.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 +159 -13
- package/dist/cjs/components/DataTable/DataTableRoot.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 +39 -2
- 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/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/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 +4 -1
- package/dist/cjs/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableGrouping/index.cjs +1 -0
- package/dist/cjs/components/DataTableGrouping/index.native.js +1 -0
- package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableToolbar/index.cjs +1 -0
- package/dist/cjs/components/DataTableToolbar/index.native.js +1 -0
- package/dist/cjs/components/DataTableToolbar/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/TableInput/index.native.js +3 -0
- package/dist/cjs/components/TableInput/index.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/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/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 +39 -2
- 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/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/DataTableGrouping/index.mjs +1 -0
- package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/esm/components/DataTableGrouping/index.native.js +1 -0
- 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/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/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 +39 -2
- 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/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/DataTableGrouping/index.mjs +1 -0
- package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableGrouping/index.native.js +1 -0
- 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/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/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 +13 -13
- 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/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/DataTableFilterButton/index.d.ts.map +1 -1
- 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
|
@@ -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: {
|
|
@@ -176,6 +176,7 @@ export function DataTableToolbar(props: DataTableToolbarProps) {
|
|
|
176
176
|
<Popover.Content
|
|
177
177
|
{...knobProps.borderRadius}
|
|
178
178
|
borderColor="$color6"
|
|
179
|
+
transition={knobProps.transition}
|
|
179
180
|
enterStyle={{ x: 0, y: -10, opacity: 0 }}
|
|
180
181
|
exitStyle={{ x: 0, y: -10, opacity: 0 }}
|
|
181
182
|
>
|
|
@@ -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, View, XStack, YStack } from "tamagui";
|
|
4
5
|
import { DataTableViewOptions } from "./index";
|
|
5
6
|
|
|
@@ -41,7 +42,7 @@ const allColumns = [
|
|
|
41
42
|
{
|
|
42
43
|
accessorKey: "createdAt",
|
|
43
44
|
header: "Created",
|
|
44
|
-
cell: (info: any) =>
|
|
45
|
+
cell: (info: any) => formatAbsoluteDate(info.getValue()),
|
|
45
46
|
},
|
|
46
47
|
{
|
|
47
48
|
accessorKey: "description",
|
|
@@ -24,6 +24,12 @@ export interface FilterManagerProps {
|
|
|
24
24
|
* "no filters applied" disable carries its own reason.
|
|
25
25
|
*/
|
|
26
26
|
disabledReason?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Reason placement forwarded to the trigger Button — `"inline"` keeps the
|
|
29
|
+
* reason on the control line in toolbar rows (the stacked default breaks
|
|
30
|
+
* the row's shared control height). See the Button contract.
|
|
31
|
+
*/
|
|
32
|
+
disabledReasonPlacement?: "below" | "inline";
|
|
27
33
|
canCreateGlobal?: boolean;
|
|
28
34
|
trigger?: React.ReactNode;
|
|
29
35
|
}
|
|
@@ -33,6 +39,7 @@ export function FilterManager({
|
|
|
33
39
|
onSaveFilter,
|
|
34
40
|
disabled = false,
|
|
35
41
|
disabledReason,
|
|
42
|
+
disabledReasonPlacement,
|
|
36
43
|
canCreateGlobal = false,
|
|
37
44
|
trigger,
|
|
38
45
|
}: FilterManagerProps) {
|
|
@@ -49,7 +56,7 @@ export function FilterManager({
|
|
|
49
56
|
|
|
50
57
|
const handleSave = async () => {
|
|
51
58
|
if (!filterName.trim()) {
|
|
52
|
-
setError("Filter name is required");
|
|
59
|
+
setError(t("Filter name is required"));
|
|
53
60
|
return;
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -62,7 +69,7 @@ export function FilterManager({
|
|
|
62
69
|
setIsGlobal(false);
|
|
63
70
|
setOpen(false);
|
|
64
71
|
} catch (e) {
|
|
65
|
-
setError(e instanceof Error ? e.message : "Failed to save filter");
|
|
72
|
+
setError(e instanceof Error ? e.message : t("Failed to save filter"));
|
|
66
73
|
} finally {
|
|
67
74
|
setIsSaving(false);
|
|
68
75
|
}
|
|
@@ -83,31 +90,32 @@ export function FilterManager({
|
|
|
83
90
|
disabledReason={
|
|
84
91
|
disabled ? disabledReason : !hasFilters ? t("Apply filters to save them") : undefined
|
|
85
92
|
}
|
|
86
|
-
|
|
93
|
+
disabledReasonPlacement={disabledReasonPlacement}
|
|
94
|
+
aria-label={t("Save filter")}
|
|
87
95
|
>
|
|
88
|
-
Save Filter
|
|
96
|
+
{t("Save Filter")}
|
|
89
97
|
</Button>
|
|
90
98
|
)}
|
|
91
99
|
</KnobDialogTrigger>
|
|
92
100
|
|
|
93
101
|
<KnobDialogContent minWidth={dialogMinWidth}>
|
|
94
|
-
<KnobDialogTitle>Save Filter</KnobDialogTitle>
|
|
102
|
+
<KnobDialogTitle>{t("Save Filter")}</KnobDialogTitle>
|
|
95
103
|
<KnobDialogDescription>
|
|
96
|
-
Save the current filter configuration for quick access later.
|
|
104
|
+
{t("Save the current filter configuration for quick access later.")}
|
|
97
105
|
</KnobDialogDescription>
|
|
98
106
|
|
|
99
107
|
<YStack gap="$3">
|
|
100
108
|
<Input
|
|
101
|
-
label="Filter Name"
|
|
109
|
+
label={t("Filter Name")}
|
|
102
110
|
value={filterName}
|
|
103
111
|
onChangeText={setFilterName}
|
|
104
|
-
placeholder="e.g., Big Pokemon"
|
|
112
|
+
placeholder={t("e.g., Big Pokemon")}
|
|
105
113
|
disabled={isSaving}
|
|
106
114
|
/>
|
|
107
115
|
|
|
108
116
|
{canCreateGlobal && (
|
|
109
117
|
<Checkbox
|
|
110
|
-
label="Make this filter global (visible to all users)"
|
|
118
|
+
label={t("Make this filter global (visible to all users)")}
|
|
111
119
|
checked={isGlobal}
|
|
112
120
|
onCheckedChange={(checked) => setIsGlobal(checked === true)}
|
|
113
121
|
disabled={isSaving}
|
|
@@ -121,7 +129,7 @@ export function FilterManager({
|
|
|
121
129
|
)}
|
|
122
130
|
|
|
123
131
|
<Text color="$color10" fontSize="$2">
|
|
124
|
-
Current filters: {currentFilters.length}
|
|
132
|
+
{t("Current filters: {{count}} active", { count: currentFilters.length })}
|
|
125
133
|
</Text>
|
|
126
134
|
</YStack>
|
|
127
135
|
|
|
@@ -129,7 +137,7 @@ export function FilterManager({
|
|
|
129
137
|
<KnobDialogClose asChild>
|
|
130
138
|
{/* DG-ACT-05: the cancel action never renders disabled. */}
|
|
131
139
|
<Button size={knobProps.sizeToken} variant="outlined">
|
|
132
|
-
Cancel
|
|
140
|
+
{t("Cancel")}
|
|
133
141
|
</Button>
|
|
134
142
|
</KnobDialogClose>
|
|
135
143
|
{/* Stays pressable on an empty name — handleSave surfaces the
|
|
@@ -143,7 +151,7 @@ export function FilterManager({
|
|
|
143
151
|
onPress={handleSave}
|
|
144
152
|
loading={isSaving}
|
|
145
153
|
>
|
|
146
|
-
Save Filter
|
|
154
|
+
{t("Save Filter")}
|
|
147
155
|
</Button>
|
|
148
156
|
</XStack>
|
|
149
157
|
|
|
@@ -155,7 +163,7 @@ export function FilterManager({
|
|
|
155
163
|
size="$2"
|
|
156
164
|
circular
|
|
157
165
|
icon={XIcon}
|
|
158
|
-
aria-label="Close dialog"
|
|
166
|
+
aria-label={t("Close dialog")}
|
|
159
167
|
/>
|
|
160
168
|
</KnobDialogClose>
|
|
161
169
|
</KnobDialogContent>
|
|
@@ -149,6 +149,9 @@ export function TableInput<TData extends DataTableData = DataTableData>({
|
|
|
149
149
|
<Field form={resolvedForm} name={name} validators={resolvedValidators}>
|
|
150
150
|
{(field) => (
|
|
151
151
|
<Inner
|
|
152
|
+
// Form state wins; the `data` fallback covers only the uninitialized
|
|
153
|
+
// instant before the field registers. A `data` prop diverging from
|
|
154
|
+
// form state is never load-bearing here (D-16 rider).
|
|
152
155
|
rows={(field.state.value as TData[] | undefined) ?? data}
|
|
153
156
|
commit={(next) => {
|
|
154
157
|
field.handleChange(next);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import type { DataTableColumn } from "../../types";
|
|
4
|
+
import { DataTable } from "../DataTable";
|
|
5
|
+
|
|
6
|
+
type Row = { id: number; name: string; amount: number };
|
|
7
|
+
const data: Row[] = [
|
|
8
|
+
{ id: 1, name: "Alpha", amount: 10 },
|
|
9
|
+
{ id: 2, name: "Beta", amount: 20 },
|
|
10
|
+
];
|
|
11
|
+
const columns: DataTableColumn<Row>[] = [
|
|
12
|
+
{ id: "name", accessorKey: "name", header: "Name" },
|
|
13
|
+
{
|
|
14
|
+
id: "amount",
|
|
15
|
+
accessorKey: "amount",
|
|
16
|
+
header: "Amount",
|
|
17
|
+
align: "right",
|
|
18
|
+
cell: (info: any) => info.getValue(),
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
describe("scratch", () => {
|
|
23
|
+
it("dumps selection checkbox DOM", () => {
|
|
24
|
+
const { container } = renderWithProviders(
|
|
25
|
+
<DataTable
|
|
26
|
+
data={data}
|
|
27
|
+
columns={columns}
|
|
28
|
+
enableRowSelection
|
|
29
|
+
layout="table"
|
|
30
|
+
enablePagination={false}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
33
|
+
const labeled = container.querySelectorAll('[aria-label^="Select row"]');
|
|
34
|
+
console.log("SELECT-LABELED COUNT:", labeled.length);
|
|
35
|
+
labeled.forEach((el, i) => {
|
|
36
|
+
if (i < 2) console.log("EL", i, el.outerHTML.slice(0, 800));
|
|
37
|
+
});
|
|
38
|
+
const roleCheckbox = container.querySelectorAll('[role="checkbox"]');
|
|
39
|
+
console.log("ROLE CHECKBOX COUNT:", roleCheckbox.length);
|
|
40
|
+
const ariaChecked = container.querySelectorAll("[aria-checked]");
|
|
41
|
+
console.log("ARIA-CHECKED COUNT:", ariaChecked.length);
|
|
42
|
+
const firstCell = container.querySelector('[role="row"][aria-rowindex="1"]');
|
|
43
|
+
console.log("FIRST DATA ROW:", firstCell ? firstCell.outerHTML.slice(0, 500) : "none");
|
|
44
|
+
expect(true).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -408,9 +408,35 @@ export function useDataTable<TData extends DataTableData = DataTableData>({
|
|
|
408
408
|
return String(index);
|
|
409
409
|
}, []);
|
|
410
410
|
|
|
411
|
+
// Map the package column aliases onto TanStack's per-column switches:
|
|
412
|
+
// `sortable`/`filterable` were documented API but previously reached no
|
|
413
|
+
// TanStack option, so `sortable: false` columns still rendered a live sort
|
|
414
|
+
// header (caret, tab stop, aria-sort) — chrome asserting an interaction
|
|
415
|
+
// the column declined (Axiom 6 HONEST STATE). Explicit TanStack props win.
|
|
416
|
+
const normalizedColumns = useMemo(
|
|
417
|
+
() =>
|
|
418
|
+
columns.map((column) => {
|
|
419
|
+
const { sortable, filterable } = column as {
|
|
420
|
+
sortable?: boolean;
|
|
421
|
+
filterable?: boolean;
|
|
422
|
+
};
|
|
423
|
+
if (sortable === undefined && filterable === undefined) return column;
|
|
424
|
+
return {
|
|
425
|
+
...column,
|
|
426
|
+
...(sortable !== undefined && column.enableSorting === undefined
|
|
427
|
+
? { enableSorting: sortable }
|
|
428
|
+
: {}),
|
|
429
|
+
...(filterable !== undefined && column.enableColumnFilter === undefined
|
|
430
|
+
? { enableColumnFilter: filterable }
|
|
431
|
+
: {}),
|
|
432
|
+
};
|
|
433
|
+
}),
|
|
434
|
+
[columns],
|
|
435
|
+
);
|
|
436
|
+
|
|
411
437
|
const table = useReactTable({
|
|
412
438
|
data: preFilteredData,
|
|
413
|
-
columns:
|
|
439
|
+
columns: normalizedColumns as ColumnDef<TData>[],
|
|
414
440
|
defaultColumn: { filterFn: operatorFilterFn as any },
|
|
415
441
|
getCoreRowModel: getCoreRowModel(),
|
|
416
442
|
getExpandedRowModel: enableRowExpansion ? getExpandedRowModel() : undefined,
|