@multiplatform.one/table 7.10.0 → 7.15.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/README.md +61 -1
- package/dist/cjs/components/ChildTable/index.cjs +8 -2
- package/dist/cjs/components/ChildTable/index.native.js +8 -2
- package/dist/cjs/components/ChildTable/index.native.js.map +1 -1
- package/dist/cjs/components/ColumnHeaderFilter/index.cjs +77 -111
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js +89 -129
- package/dist/cjs/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableCardControls.cjs +90 -54
- package/dist/cjs/components/DataTable/DataTableCardControls.native.js +93 -56
- package/dist/cjs/components/DataTable/DataTableCardControls.native.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTableRoot.cjs +68 -9
- package/dist/cjs/components/DataTable/DataTableRoot.native.js +78 -11
- package/dist/cjs/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/cjs/components/DataTableCell/CellModalEditor.cjs +72 -70
- package/dist/cjs/components/DataTableCell/index.cjs +2 -2
- package/dist/cjs/components/DataTableCell/index.native.js +2 -11
- package/dist/cjs/components/DataTableCell/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableDateFilter/index.cjs +135 -158
- package/dist/cjs/components/DataTableDateFilter/index.native.js +153 -183
- package/dist/cjs/components/DataTableDateFilter/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableExport/index.cjs +122 -139
- package/dist/cjs/components/DataTableExport/index.native.js +135 -157
- package/dist/cjs/components/DataTableExport/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableFilterButton/index.cjs +14 -0
- package/dist/cjs/components/DataTableFilterButton/index.native.js +15 -0
- package/dist/cjs/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableGrouping/index.cjs +192 -212
- package/dist/cjs/components/DataTableGrouping/index.native.js +209 -230
- package/dist/cjs/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableSearch/index.cjs +103 -115
- package/dist/cjs/components/DataTableSearch/index.native.js +111 -123
- package/dist/cjs/components/DataTableSearch/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableToolbar/index.cjs +161 -101
- package/dist/cjs/components/DataTableToolbar/index.native.js +168 -109
- package/dist/cjs/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/cjs/components/DataTableViewOptions/index.cjs +38 -54
- package/dist/cjs/components/DataTableViewOptions/index.native.js +40 -56
- package/dist/cjs/components/DataTableViewOptions/index.native.js.map +1 -1
- package/dist/cjs/components/FilterOperatorValueEditor/index.cjs +3 -11
- package/dist/cjs/components/FilterOperatorValueEditor/index.native.js +3 -11
- package/dist/cjs/components/FilterOperatorValueEditor/index.native.js.map +1 -1
- package/dist/cjs/components/SimpleTable/index.cjs +2 -6
- package/dist/cjs/components/SimpleTable/index.native.js +2 -6
- package/dist/cjs/components/SimpleTable/index.native.js.map +1 -1
- package/dist/cjs/components/TableInput/RowDetailEditor.cjs +190 -0
- package/dist/cjs/components/TableInput/RowDetailEditor.native.js +172 -0
- package/dist/cjs/components/TableInput/RowDetailEditor.native.js.map +1 -0
- package/dist/cjs/components/TableInput/index.cjs +42 -5
- package/dist/cjs/components/TableInput/index.native.js +64 -1
- package/dist/cjs/components/TableInput/index.native.js.map +1 -1
- package/dist/cjs/components/TableInput/shared.cjs +8 -0
- package/dist/cjs/components/TableInput/shared.native.js +10 -0
- package/dist/cjs/components/TableInput/shared.native.js.map +1 -1
- package/dist/cjs/hooks/useDataTable.cjs +43 -2
- package/dist/cjs/hooks/useDataTable.native.js +49 -3
- package/dist/cjs/hooks/useDataTable.native.js.map +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/utils/rowPress.cjs +4 -1
- package/dist/cjs/utils/rowPress.native.js +4 -1
- package/dist/cjs/utils/rowPress.native.js.map +1 -1
- package/dist/esm/components/ChildTable/index.mjs +8 -2
- package/dist/esm/components/ChildTable/index.mjs.map +1 -1
- package/dist/esm/components/ChildTable/index.native.js +8 -2
- package/dist/esm/components/ChildTable/index.native.js.map +1 -1
- package/dist/esm/components/ColumnHeaderFilter/index.mjs +80 -114
- package/dist/esm/components/ColumnHeaderFilter/index.mjs.map +1 -1
- package/dist/esm/components/ColumnHeaderFilter/index.native.js +92 -132
- package/dist/esm/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableCardControls.mjs +89 -54
- package/dist/esm/components/DataTable/DataTableCardControls.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableCardControls.native.js +92 -56
- package/dist/esm/components/DataTable/DataTableCardControls.native.js.map +1 -1
- package/dist/esm/components/DataTable/DataTableRoot.mjs +70 -11
- package/dist/esm/components/DataTable/DataTableRoot.mjs.map +1 -1
- package/dist/esm/components/DataTable/DataTableRoot.native.js +80 -13
- package/dist/esm/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/esm/components/DataTableCell/CellModalEditor.mjs +76 -74
- package/dist/esm/components/DataTableCell/CellModalEditor.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.mjs +3 -3
- package/dist/esm/components/DataTableCell/index.mjs.map +1 -1
- package/dist/esm/components/DataTableCell/index.native.js +3 -12
- package/dist/esm/components/DataTableCell/index.native.js.map +1 -1
- package/dist/esm/components/DataTableDateFilter/index.mjs +137 -160
- package/dist/esm/components/DataTableDateFilter/index.mjs.map +1 -1
- package/dist/esm/components/DataTableDateFilter/index.native.js +155 -185
- package/dist/esm/components/DataTableDateFilter/index.native.js.map +1 -1
- package/dist/esm/components/DataTableExport/index.mjs +124 -141
- package/dist/esm/components/DataTableExport/index.mjs.map +1 -1
- package/dist/esm/components/DataTableExport/index.native.js +137 -159
- package/dist/esm/components/DataTableExport/index.native.js.map +1 -1
- package/dist/esm/components/DataTableFilterButton/index.mjs +14 -1
- package/dist/esm/components/DataTableFilterButton/index.mjs.map +1 -1
- package/dist/esm/components/DataTableFilterButton/index.native.js +15 -1
- package/dist/esm/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/esm/components/DataTableGrouping/index.mjs +194 -214
- package/dist/esm/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/esm/components/DataTableGrouping/index.native.js +211 -232
- package/dist/esm/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/esm/components/DataTableSearch/index.mjs +105 -117
- package/dist/esm/components/DataTableSearch/index.mjs.map +1 -1
- package/dist/esm/components/DataTableSearch/index.native.js +113 -125
- package/dist/esm/components/DataTableSearch/index.native.js.map +1 -1
- package/dist/esm/components/DataTableToolbar/index.mjs +165 -106
- package/dist/esm/components/DataTableToolbar/index.mjs.map +1 -1
- package/dist/esm/components/DataTableToolbar/index.native.js +172 -114
- package/dist/esm/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/esm/components/DataTableViewOptions/index.mjs +40 -56
- package/dist/esm/components/DataTableViewOptions/index.mjs.map +1 -1
- package/dist/esm/components/DataTableViewOptions/index.native.js +42 -58
- package/dist/esm/components/DataTableViewOptions/index.native.js.map +1 -1
- package/dist/esm/components/FilterOperatorValueEditor/index.mjs +3 -11
- package/dist/esm/components/FilterOperatorValueEditor/index.mjs.map +1 -1
- package/dist/esm/components/FilterOperatorValueEditor/index.native.js +3 -11
- package/dist/esm/components/FilterOperatorValueEditor/index.native.js.map +1 -1
- package/dist/esm/components/SimpleTable/index.mjs +3 -7
- package/dist/esm/components/SimpleTable/index.mjs.map +1 -1
- package/dist/esm/components/SimpleTable/index.native.js +3 -7
- package/dist/esm/components/SimpleTable/index.native.js.map +1 -1
- package/dist/esm/components/TableInput/RowDetailEditor.mjs +165 -0
- package/dist/esm/components/TableInput/RowDetailEditor.mjs.map +1 -0
- package/dist/esm/components/TableInput/RowDetailEditor.native.js +144 -0
- package/dist/esm/components/TableInput/RowDetailEditor.native.js.map +1 -0
- package/dist/esm/components/TableInput/index.mjs +42 -5
- package/dist/esm/components/TableInput/index.mjs.map +1 -1
- package/dist/esm/components/TableInput/index.native.js +64 -1
- package/dist/esm/components/TableInput/index.native.js.map +1 -1
- package/dist/esm/components/TableInput/shared.mjs +7 -1
- package/dist/esm/components/TableInput/shared.mjs.map +1 -1
- package/dist/esm/components/TableInput/shared.native.js +9 -1
- package/dist/esm/components/TableInput/shared.native.js.map +1 -1
- package/dist/esm/hooks/useDataTable.mjs +43 -2
- package/dist/esm/hooks/useDataTable.mjs.map +1 -1
- package/dist/esm/hooks/useDataTable.native.js +49 -3
- package/dist/esm/hooks/useDataTable.native.js.map +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/utils/rowPress.mjs +4 -1
- package/dist/esm/utils/rowPress.mjs.map +1 -1
- package/dist/esm/utils/rowPress.native.js +4 -1
- package/dist/esm/utils/rowPress.native.js.map +1 -1
- package/dist/jsx/components/ChildTable/index.mjs +8 -2
- package/dist/jsx/components/ChildTable/index.mjs.map +1 -1
- package/dist/jsx/components/ChildTable/index.native.js +8 -2
- package/dist/jsx/components/ChildTable/index.native.js.map +1 -1
- package/dist/jsx/components/ColumnHeaderFilter/index.mjs +80 -114
- package/dist/jsx/components/ColumnHeaderFilter/index.mjs.map +1 -1
- package/dist/jsx/components/ColumnHeaderFilter/index.native.js +89 -129
- package/dist/jsx/components/ColumnHeaderFilter/index.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableCardControls.mjs +89 -54
- package/dist/jsx/components/DataTable/DataTableCardControls.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableCardControls.native.js +93 -56
- package/dist/jsx/components/DataTable/DataTableCardControls.native.js.map +1 -1
- package/dist/jsx/components/DataTable/DataTableRoot.mjs +70 -11
- package/dist/jsx/components/DataTable/DataTableRoot.mjs.map +1 -1
- package/dist/jsx/components/DataTable/DataTableRoot.native.js +78 -11
- package/dist/jsx/components/DataTable/DataTableRoot.native.js.map +1 -1
- package/dist/jsx/components/DataTableCell/CellModalEditor.mjs +76 -74
- package/dist/jsx/components/DataTableCell/CellModalEditor.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.mjs +3 -3
- package/dist/jsx/components/DataTableCell/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableCell/index.native.js +2 -11
- package/dist/jsx/components/DataTableCell/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableDateFilter/index.mjs +137 -160
- package/dist/jsx/components/DataTableDateFilter/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableDateFilter/index.native.js +153 -183
- package/dist/jsx/components/DataTableDateFilter/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableExport/index.mjs +124 -141
- package/dist/jsx/components/DataTableExport/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableExport/index.native.js +135 -157
- package/dist/jsx/components/DataTableExport/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.mjs +14 -1
- package/dist/jsx/components/DataTableFilterButton/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableFilterButton/index.native.js +15 -0
- package/dist/jsx/components/DataTableFilterButton/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableGrouping/index.mjs +194 -214
- package/dist/jsx/components/DataTableGrouping/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableGrouping/index.native.js +209 -230
- package/dist/jsx/components/DataTableGrouping/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableSearch/index.mjs +105 -117
- package/dist/jsx/components/DataTableSearch/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableSearch/index.native.js +111 -123
- package/dist/jsx/components/DataTableSearch/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableToolbar/index.mjs +165 -106
- package/dist/jsx/components/DataTableToolbar/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableToolbar/index.native.js +168 -109
- package/dist/jsx/components/DataTableToolbar/index.native.js.map +1 -1
- package/dist/jsx/components/DataTableViewOptions/index.mjs +40 -56
- package/dist/jsx/components/DataTableViewOptions/index.mjs.map +1 -1
- package/dist/jsx/components/DataTableViewOptions/index.native.js +40 -56
- package/dist/jsx/components/DataTableViewOptions/index.native.js.map +1 -1
- package/dist/jsx/components/FilterOperatorValueEditor/index.mjs +3 -11
- package/dist/jsx/components/FilterOperatorValueEditor/index.mjs.map +1 -1
- package/dist/jsx/components/FilterOperatorValueEditor/index.native.js +3 -11
- package/dist/jsx/components/FilterOperatorValueEditor/index.native.js.map +1 -1
- package/dist/jsx/components/SimpleTable/index.mjs +3 -7
- package/dist/jsx/components/SimpleTable/index.mjs.map +1 -1
- package/dist/jsx/components/SimpleTable/index.native.js +2 -6
- package/dist/jsx/components/SimpleTable/index.native.js.map +1 -1
- package/dist/jsx/components/TableInput/RowDetailEditor.mjs +165 -0
- package/dist/jsx/components/TableInput/RowDetailEditor.mjs.map +1 -0
- package/dist/jsx/components/TableInput/RowDetailEditor.native.js +172 -0
- package/dist/jsx/components/TableInput/RowDetailEditor.native.js.map +1 -0
- package/dist/jsx/components/TableInput/index.mjs +42 -5
- package/dist/jsx/components/TableInput/index.mjs.map +1 -1
- package/dist/jsx/components/TableInput/index.native.js +64 -1
- package/dist/jsx/components/TableInput/index.native.js.map +1 -1
- package/dist/jsx/components/TableInput/shared.mjs +7 -1
- package/dist/jsx/components/TableInput/shared.mjs.map +1 -1
- package/dist/jsx/components/TableInput/shared.native.js +10 -0
- package/dist/jsx/components/TableInput/shared.native.js.map +1 -1
- package/dist/jsx/hooks/useDataTable.mjs +43 -2
- package/dist/jsx/hooks/useDataTable.mjs.map +1 -1
- package/dist/jsx/hooks/useDataTable.native.js +49 -3
- package/dist/jsx/hooks/useDataTable.native.js.map +1 -1
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/utils/rowPress.mjs +4 -1
- package/dist/jsx/utils/rowPress.mjs.map +1 -1
- package/dist/jsx/utils/rowPress.native.js +4 -1
- package/dist/jsx/utils/rowPress.native.js.map +1 -1
- package/package.json +16 -16
- package/src/components/ChildTable/childTable.cellHidden.spec.tsx +57 -0
- package/src/components/ChildTable/childTable.rowDetail.spec.tsx +120 -0
- package/src/components/ChildTable/index.tsx +16 -0
- package/src/components/ColumnHeaderFilter/index.tsx +89 -116
- package/src/components/DataTable/DataTable.stories.tsx +130 -6
- package/src/components/DataTable/DataTableCardControls.stories.tsx +61 -2
- package/src/components/DataTable/DataTableCardControls.tsx +92 -66
- package/src/components/DataTable/DataTableRoot.tsx +109 -18
- package/src/components/DataTable/cardLayout.spec.tsx +4 -3
- package/src/components/DataTable/cardToolbar.spec.tsx +238 -0
- package/src/components/DataTable/chromeInterplay.spec.tsx +7 -5
- package/src/components/DataTable/columnWidthLayout.spec.tsx +189 -0
- package/src/components/DataTable/dataTableCardControls.spec.tsx +3 -2
- package/src/components/DataTable/dataTableMobileCards.spec.tsx +1 -4
- package/src/components/DataTable/footerLayout.spec.tsx +250 -0
- package/src/components/DataTable/headerContainment.spec.tsx +91 -0
- package/src/components/DataTable/pinningGeometry.spec.tsx +66 -1
- package/src/components/DataTable/rowPress.spec.tsx +2 -6
- package/src/components/DataTableBulkActions/DataTableBulkActions.stories.tsx +5 -4
- package/src/components/DataTableCell/CellModalEditor.tsx +95 -75
- package/src/components/DataTableCell/cellDraftPolicy.spec.tsx +12 -2
- package/src/components/DataTableCell/cellModalEditor.spec.tsx +75 -49
- package/src/components/DataTableCell/dataTableCell.spec.tsx +31 -14
- package/src/components/DataTableCell/index.tsx +3 -2
- package/src/components/DataTableDateFilter/DataTableDateFilter.spec.tsx +6 -3
- package/src/components/DataTableDateFilter/index.tsx +151 -180
- package/src/components/DataTableExport/DataTableExport.spec.tsx +42 -18
- package/src/components/DataTableExport/index.tsx +135 -142
- package/src/components/DataTableFilterButton/index.tsx +9 -0
- package/src/components/DataTableGrouping/index.tsx +193 -195
- package/src/components/DataTableSearch/index.tsx +111 -109
- package/src/components/DataTableToolbar/index.tsx +179 -107
- package/src/components/DataTableViewOptions/DataTableViewOptions.stories.tsx +52 -0
- package/src/components/DataTableViewOptions/index.tsx +32 -40
- package/src/components/FilterOperatorValueEditor/FilterOperatorValueEditor.spec.tsx +64 -2
- package/src/components/FilterOperatorValueEditor/index.tsx +0 -8
- package/src/components/SimpleTable/index.tsx +3 -2
- package/src/components/SimpleTable/simpleTable.spec.tsx +15 -0
- package/src/components/Table/accessibility.spec.tsx +7 -19
- package/src/components/TableInput/RowDetailEditor.native.tsx +134 -0
- package/src/components/TableInput/RowDetailEditor.tsx +173 -0
- package/src/components/TableInput/index.native.tsx +49 -1
- package/src/components/TableInput/index.tsx +65 -10
- package/src/components/TableInput/shared.ts +29 -0
- package/src/components/tableDropdowns.spec.tsx +172 -0
- package/src/hooks/useDataTable.spec.ts +153 -0
- package/src/hooks/useDataTable.ts +38 -2
- package/src/index.ts +1 -0
- package/src/types.ts +14 -12
- package/src/utils/rowPress.ts +3 -0
- package/types/components/ChildTable/index.d.ts +10 -0
- package/types/components/ChildTable/index.d.ts.map +1 -1
- package/types/components/ColumnHeaderFilter/index.d.ts +1 -1
- package/types/components/ColumnHeaderFilter/index.d.ts.map +1 -1
- package/types/components/DataTable/DataTableCardControls.d.ts +3 -0
- package/types/components/DataTable/DataTableCardControls.d.ts.map +1 -1
- package/types/components/DataTable/DataTableRoot.d.ts.map +1 -1
- package/types/components/DataTableCell/CellModalEditor.d.ts +10 -7
- package/types/components/DataTableCell/CellModalEditor.d.ts.map +1 -1
- package/types/components/DataTableCell/index.d.ts.map +1 -1
- package/types/components/DataTableDateFilter/index.d.ts.map +1 -1
- package/types/components/DataTableFilterButton/index.d.ts +2 -1
- package/types/components/DataTableFilterButton/index.d.ts.map +1 -1
- package/types/components/DataTableGrouping/index.d.ts.map +1 -1
- package/types/components/DataTableSearch/index.d.ts.map +1 -1
- package/types/components/DataTableToolbar/index.d.ts +1 -0
- package/types/components/DataTableToolbar/index.d.ts.map +1 -1
- package/types/components/FilterOperatorValueEditor/index.d.ts.map +1 -1
- package/types/components/SimpleTable/index.d.ts.map +1 -1
- package/types/components/TableInput/RowDetailEditor.d.ts +24 -0
- package/types/components/TableInput/RowDetailEditor.d.ts.map +1 -0
- package/types/components/TableInput/RowDetailEditor.native.d.ts +11 -0
- package/types/components/TableInput/RowDetailEditor.native.d.ts.map +1 -0
- package/types/components/TableInput/index.d.ts +11 -2
- package/types/components/TableInput/index.d.ts.map +1 -1
- package/types/components/TableInput/index.native.d.ts +8 -1
- package/types/components/TableInput/index.native.d.ts.map +1 -1
- package/types/components/TableInput/shared.d.ts +11 -0
- package/types/components/TableInput/shared.d.ts.map +1 -1
- package/types/hooks/useDataTable.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/types.d.ts +14 -12
- package/types/types.d.ts.map +1 -1
- package/types/utils/rowPress.d.ts.map +1 -1
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
} from "@phosphor-icons/react";
|
|
8
8
|
import { getGroupPosition, stackRadiusProps, useResolvedKnobs } from "@multiplatform.one/theme";
|
|
9
9
|
import React, { useState } from "react";
|
|
10
|
-
import { Button, Input, Slider, Switch } from "@multiplatform.one/forms";
|
|
10
|
+
import { Button, FloatingPanel, Input, Slider, Switch } from "@multiplatform.one/forms";
|
|
11
11
|
import { DropdownMenu } from "@multiplatform.one/components";
|
|
12
|
-
import {
|
|
12
|
+
import { ScrollView, Separator, Text, View, XStack, YStack, useTheme } from "tamagui";
|
|
13
13
|
import type { TableView } from "../../types";
|
|
14
14
|
import { t } from "../../shared/t";
|
|
15
15
|
import { iconSizeForToken } from "../../utils/chromeIcons";
|
|
@@ -415,47 +415,39 @@ function ViewManagementSection({
|
|
|
415
415
|
</Text>
|
|
416
416
|
|
|
417
417
|
{/* Save Current View */}
|
|
418
|
-
<
|
|
419
|
-
|
|
418
|
+
<FloatingPanel
|
|
419
|
+
open={showSaveDialog}
|
|
420
|
+
onOpenChange={setShowSaveDialog}
|
|
421
|
+
trigger={
|
|
420
422
|
<Button size={knobProps.sizeToken} icon={<FloppyDiskIcon size={iconPx} />}>
|
|
421
423
|
{t("Save Current View")}
|
|
422
424
|
</Button>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
size={knobProps.sizeToken}
|
|
450
|
-
onPress={handleSaveView}
|
|
451
|
-
disabled={!viewName.trim()}
|
|
452
|
-
>
|
|
453
|
-
{t("Save")}
|
|
454
|
-
</Button>
|
|
455
|
-
</XStack>
|
|
456
|
-
</YStack>
|
|
457
|
-
</Popover.Content>
|
|
458
|
-
</Popover>
|
|
425
|
+
}
|
|
426
|
+
>
|
|
427
|
+
<YStack gap="$3" minWidth={250} data-dismiss-class="compose">
|
|
428
|
+
<Text {...knobProps.label} fontWeight="400">
|
|
429
|
+
{t("Save View")}
|
|
430
|
+
</Text>
|
|
431
|
+
<Input
|
|
432
|
+
placeholder={t("Enter view name...")}
|
|
433
|
+
value={viewName}
|
|
434
|
+
onChangeText={setViewName}
|
|
435
|
+
inputProps={{
|
|
436
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
437
|
+
if (e.key === "Enter" && viewName.trim()) handleSaveView();
|
|
438
|
+
},
|
|
439
|
+
}}
|
|
440
|
+
/>
|
|
441
|
+
<XStack gap="$2" justifyContent="flex-end">
|
|
442
|
+
<Button size={knobProps.sizeToken} onPress={() => setShowSaveDialog(false)}>
|
|
443
|
+
{t("Cancel")}
|
|
444
|
+
</Button>
|
|
445
|
+
<Button size={knobProps.sizeToken} onPress={handleSaveView} disabled={!viewName.trim()}>
|
|
446
|
+
{t("Save")}
|
|
447
|
+
</Button>
|
|
448
|
+
</XStack>
|
|
449
|
+
</YStack>
|
|
450
|
+
</FloatingPanel>
|
|
459
451
|
|
|
460
452
|
{/* Saved Views List */}
|
|
461
453
|
{views.length > 0 && (
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
9
9
|
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
10
|
+
import { type Knobs, Preset } from "@multiplatform.one/theme";
|
|
10
11
|
import React, { useState } from "react";
|
|
11
12
|
import { describe, expect, it, vi } from "vitest";
|
|
12
13
|
import type { DataTableColumnType, FilterOperator } from "../../types";
|
|
@@ -28,14 +29,16 @@ const selectTriggers = (container: HTMLElement) =>
|
|
|
28
29
|
const textInputs = (container: HTMLElement) =>
|
|
29
30
|
Array.from(container.querySelectorAll("input")) as HTMLInputElement[];
|
|
30
31
|
|
|
32
|
+
/** The listbox portals out to the page (MPO-335, LC-90), so options are read from it. */
|
|
31
33
|
async function openSelect(container: HTMLElement, trigger: HTMLElement) {
|
|
34
|
+
const page = container.ownerDocument.body;
|
|
32
35
|
fireEvent.pointerDown(trigger);
|
|
33
36
|
fireEvent.mouseDown(trigger);
|
|
34
37
|
fireEvent.click(trigger);
|
|
35
38
|
await waitFor(() => {
|
|
36
|
-
expect(
|
|
39
|
+
expect(page.querySelectorAll('[role="option"]').length).toBeGreaterThan(0);
|
|
37
40
|
});
|
|
38
|
-
return Array.from(
|
|
41
|
+
return Array.from(page.querySelectorAll('[role="option"]')) as HTMLElement[];
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
/**
|
|
@@ -258,4 +261,63 @@ describe("FilterOperatorValueEditor", () => {
|
|
|
258
261
|
await waitFor(() => expect(onValue).toHaveBeenLastCalledWith("in-progress"));
|
|
259
262
|
});
|
|
260
263
|
});
|
|
264
|
+
describe("knob laws (MPO-118)", () => {
|
|
265
|
+
const valueBox = (container: HTMLElement) =>
|
|
266
|
+
container.querySelector("[data-mp-input-field='true']") as HTMLElement;
|
|
267
|
+
|
|
268
|
+
const padX = (el: Element) =>
|
|
269
|
+
String((el as HTMLElement).className || "")
|
|
270
|
+
.split(" ")
|
|
271
|
+
.filter((c) => c.startsWith("_px-") || c.startsWith("_pl-") || c.startsWith("_pr-"))
|
|
272
|
+
.sort();
|
|
273
|
+
|
|
274
|
+
function renderAtKnobs(overrides: Partial<Knobs>) {
|
|
275
|
+
return renderWithProviders(
|
|
276
|
+
<Preset overrides={overrides}>
|
|
277
|
+
<FilterOperatorValueEditor
|
|
278
|
+
fieldType="text"
|
|
279
|
+
operator="contains"
|
|
280
|
+
value=""
|
|
281
|
+
onOperatorChange={vi.fn()}
|
|
282
|
+
onValueChange={vi.fn()}
|
|
283
|
+
/>
|
|
284
|
+
</Preset>,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
it("size small: Condition and Value both paint the small recipe", () => {
|
|
289
|
+
const { container } = renderAtKnobs({ size: "small" });
|
|
290
|
+
const box = valueBox(container);
|
|
291
|
+
expect(box.getAttribute("data-size")).toBe("$3");
|
|
292
|
+
expect(box.getAttribute("data-visual-height")).toBe("36");
|
|
293
|
+
expect(selectTriggers(container)[0].getAttribute("data-visual-height")).toBe("36");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("size large: the Value input follows the knob instead of a pinned $3", () => {
|
|
297
|
+
const { container } = renderAtKnobs({ size: "large" });
|
|
298
|
+
const box = valueBox(container);
|
|
299
|
+
expect(box.getAttribute("data-size")).toBe("$5");
|
|
300
|
+
expect(box.getAttribute("data-visual-height")).toBe("52");
|
|
301
|
+
expect(selectTriggers(container)[0].getAttribute("data-visual-height")).toBe("52");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("density comfortable: compact is not forced on the inner fields", () => {
|
|
305
|
+
const { container } = renderAtKnobs({ density: "comfortable" });
|
|
306
|
+
expect(valueBox(container).getAttribute("data-density")).toBe("comfortable");
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("density compact: the inner fields step with the density knob and keep their height", () => {
|
|
310
|
+
const comfortable = renderAtKnobs({ density: "comfortable", size: "medium" });
|
|
311
|
+
const comfortablePad = padX(selectTriggers(comfortable.container)[0]);
|
|
312
|
+
const comfortableHeight = valueBox(comfortable.container).getAttribute("data-visual-height");
|
|
313
|
+
comfortable.unmount();
|
|
314
|
+
|
|
315
|
+
const { container } = renderAtKnobs({ density: "compact", size: "medium" });
|
|
316
|
+
const box = valueBox(container);
|
|
317
|
+
expect(box.getAttribute("data-density")).toBe("compact");
|
|
318
|
+
expect(box.getAttribute("data-visual-height")).toBe(comfortableHeight);
|
|
319
|
+
expect(comfortablePad.length).toBeGreaterThan(0);
|
|
320
|
+
expect(padX(selectTriggers(container)[0])).not.toEqual(comfortablePad);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
261
323
|
});
|
|
@@ -72,8 +72,6 @@ export function FilterOperatorValueEditor({
|
|
|
72
72
|
}
|
|
73
73
|
}}
|
|
74
74
|
options={operatorOptions}
|
|
75
|
-
size="$3"
|
|
76
|
-
compact
|
|
77
75
|
/>
|
|
78
76
|
|
|
79
77
|
{needsValueInput && (
|
|
@@ -89,8 +87,6 @@ export function FilterOperatorValueEditor({
|
|
|
89
87
|
}}
|
|
90
88
|
options={booleanOptions}
|
|
91
89
|
placeholder={t("Select...")}
|
|
92
|
-
size="$3"
|
|
93
|
-
compact
|
|
94
90
|
/>
|
|
95
91
|
) : fieldType === "select" && options ? (
|
|
96
92
|
<Select
|
|
@@ -103,8 +99,6 @@ export function FilterOperatorValueEditor({
|
|
|
103
99
|
}}
|
|
104
100
|
options={selectOptions}
|
|
105
101
|
placeholder={t("Select...")}
|
|
106
|
-
size="$3"
|
|
107
|
-
compact
|
|
108
102
|
/>
|
|
109
103
|
) : (
|
|
110
104
|
<Input
|
|
@@ -118,8 +112,6 @@ export function FilterOperatorValueEditor({
|
|
|
118
112
|
? t("0.00")
|
|
119
113
|
: t("Enter value...")
|
|
120
114
|
}
|
|
121
|
-
size="$3"
|
|
122
|
-
compact
|
|
123
115
|
{...((fieldType === "number" || fieldType === "money") && {
|
|
124
116
|
keyboardType: "numeric",
|
|
125
117
|
})}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ensureFocusVisibleRing,
|
|
15
15
|
useResolvedKnobs,
|
|
16
16
|
type DisabledRecipe,
|
|
17
|
+
transitionProps,
|
|
17
18
|
} from "@multiplatform.one/theme";
|
|
18
19
|
import { useCallback, useState, type ReactNode } from "react";
|
|
19
20
|
import { useTranslation } from "react-i18next";
|
|
@@ -331,7 +332,7 @@ function SimpleTableMobileCards({
|
|
|
331
332
|
const tableZebraOn = knobProps.tableZebra === "on";
|
|
332
333
|
const [titleCol, ...detailCols] = columns;
|
|
333
334
|
const typeSize = knobProps.label.fontSize;
|
|
334
|
-
const motion = knobProps.transition
|
|
335
|
+
const motion = transitionProps(knobProps.transition);
|
|
335
336
|
|
|
336
337
|
if (data.length === 0) {
|
|
337
338
|
return (
|
|
@@ -478,7 +479,7 @@ export function SimpleTableDesktop({
|
|
|
478
479
|
const tableZebraOn = knobProps.tableZebra === "on";
|
|
479
480
|
const ariaLabel = caption ? undefined : props["aria-label"];
|
|
480
481
|
const typeSize = knobProps.label.fontSize;
|
|
481
|
-
const motion = knobProps.transition
|
|
482
|
+
const motion = transitionProps(knobProps.transition);
|
|
482
483
|
const pad = cellPad(knobProps.gap.gap);
|
|
483
484
|
const frameBorder = knobProps.surface.borderWidth;
|
|
484
485
|
|
|
@@ -417,4 +417,19 @@ describe("SimpleTable", () => {
|
|
|
417
417
|
fireEvent.click(container.querySelector("tbody tr, [role='row']") as Element);
|
|
418
418
|
expect(onRowPress).not.toHaveBeenCalled();
|
|
419
419
|
});
|
|
420
|
+
it("survives the animation knob flipping live from none to bouncy (MPO-327)", () => {
|
|
421
|
+
const columns: SimpleTableColumn[] = [{ accessorKey: "name", header: "Name" }];
|
|
422
|
+
const data = [{ name: "Alice" }, { name: "Bob" }];
|
|
423
|
+
const Flip = ({ animation }: { animation: "none" | "bouncy" }) => (
|
|
424
|
+
<Preset overrides={{ animation }}>
|
|
425
|
+
<SimpleTableDesktop data={data} columns={columns} onRowPress={() => {}} />
|
|
426
|
+
</Preset>
|
|
427
|
+
);
|
|
428
|
+
const { container, rerender } = renderWithProviders(<Flip animation="none" />);
|
|
429
|
+
expect(container.querySelector("table")?.getAttribute("data-animation")).toBe("none");
|
|
430
|
+
|
|
431
|
+
expect(() => rerender(<Flip animation="bouncy" />)).not.toThrow();
|
|
432
|
+
expect(container.querySelector("table")?.getAttribute("data-animation")).toBe("on");
|
|
433
|
+
expect(container.textContent).toContain("Bob");
|
|
434
|
+
});
|
|
420
435
|
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { renderWithProviders, fireEvent } from "@multiplatform.one/test-utils";
|
|
11
|
+
import { waitFor } from "@testing-library/react";
|
|
11
12
|
import { describe, expect, it, vi } from "vitest";
|
|
12
13
|
import { Table } from "@multiplatform.one/table-primitives";
|
|
13
14
|
import { useTableKeyboard } from "../../hooks/useTableKeyboard";
|
|
@@ -111,14 +112,7 @@ describe("Table Accessibility", () => {
|
|
|
111
112
|
expect(true).toBe(true);
|
|
112
113
|
});
|
|
113
114
|
|
|
114
|
-
it("Escape
|
|
115
|
-
// Tamagui's Popover component handles Escape key natively to close.
|
|
116
|
-
// We verify the popover can be opened (controlled state) and that
|
|
117
|
-
// the Escape key handler exists in the DOM event chain.
|
|
118
|
-
//
|
|
119
|
-
// Note: In happy-dom test environment, Tamagui popover portals may
|
|
120
|
-
// not fully render, so we test the controlled open/close pattern.
|
|
121
|
-
|
|
115
|
+
it("Escape closes the column filter panel", async () => {
|
|
122
116
|
const onFilterChange = vi.fn();
|
|
123
117
|
|
|
124
118
|
const { container } = renderWithProviders(
|
|
@@ -130,22 +124,16 @@ describe("Table Accessibility", () => {
|
|
|
130
124
|
/>,
|
|
131
125
|
);
|
|
132
126
|
|
|
133
|
-
// The filter trigger should be present and clickable
|
|
134
127
|
const trigger = container.querySelector('[aria-label="Filter column Status"]');
|
|
135
128
|
expect(trigger).not.toBeNull();
|
|
136
129
|
|
|
137
|
-
// Click to open the popover
|
|
138
130
|
fireEvent.click(trigger!);
|
|
131
|
+
const panel = () => document.querySelector('[data-testid="floating-panel-viewport"]');
|
|
132
|
+
await waitFor(() => expect(panel()).not.toBeNull());
|
|
139
133
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// ColumnHeaderFilter uses Tamagui Popover with controlled open/onOpenChange,
|
|
144
|
-
// which natively handles Escape key to call onOpenChange(false).
|
|
145
|
-
//
|
|
146
|
-
// We verify the pattern is correct by checking the trigger exists
|
|
147
|
-
// and the component renders without error after click interaction.
|
|
148
|
-
expect(trigger).not.toBeNull();
|
|
134
|
+
fireEvent.keyDown(document.activeElement ?? document.body, { key: "Escape" });
|
|
135
|
+
await waitFor(() => expect(panel()).toBeNull());
|
|
136
|
+
expect(onFilterChange).not.toHaveBeenCalled();
|
|
149
137
|
});
|
|
150
138
|
|
|
151
139
|
it("semantic HTML and ARIA roles on table primitives", () => {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RowDetailEditor (native) — the row form as a FloatingPanel bottom sheet,
|
|
3
|
+
* the same dialog/sheet split CellModalEditor.native makes. Props, staging
|
|
4
|
+
* and footer match ./RowDetailEditor.tsx; backdrop tap and Android back
|
|
5
|
+
* commit like "Done".
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Button, FloatingPanel } from "@multiplatform.one/forms";
|
|
9
|
+
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
10
|
+
import { PencilSimpleIcon } from "@phosphor-icons/react";
|
|
11
|
+
import { useCallback, useRef, useState } from "react";
|
|
12
|
+
import { Text, View, XStack, YStack } from "tamagui";
|
|
13
|
+
import type { DataTableData, DataTableField, DataTableFieldProps } from "../../types";
|
|
14
|
+
import { t } from "../../shared/t";
|
|
15
|
+
import { withInterp } from "../../utils/withInterp";
|
|
16
|
+
import { DataTableCell } from "../DataTableCell";
|
|
17
|
+
import type { RowDetailEditorProps } from "./RowDetailEditor";
|
|
18
|
+
import { changedColumns, columnKey } from "./shared";
|
|
19
|
+
|
|
20
|
+
export type { RowDetailEditorProps } from "./RowDetailEditor";
|
|
21
|
+
|
|
22
|
+
export function RowDetailEditor<TData extends DataTableData>({
|
|
23
|
+
row,
|
|
24
|
+
rowIndex,
|
|
25
|
+
columns,
|
|
26
|
+
readOnly,
|
|
27
|
+
compact,
|
|
28
|
+
isCellHidden,
|
|
29
|
+
onCommit,
|
|
30
|
+
}: RowDetailEditorProps<TData>) {
|
|
31
|
+
const { knobProps } = useResolvedKnobs({ compact });
|
|
32
|
+
const [draft, setDraft] = useState<TData | null>(null);
|
|
33
|
+
const draftRef = useRef(draft);
|
|
34
|
+
draftRef.current = draft;
|
|
35
|
+
const rowRef = useRef(row);
|
|
36
|
+
rowRef.current = row;
|
|
37
|
+
const onCommitRef = useRef(onCommit);
|
|
38
|
+
onCommitRef.current = onCommit;
|
|
39
|
+
const discardRef = useRef(false);
|
|
40
|
+
const rowNumber = rowIndex + 1;
|
|
41
|
+
|
|
42
|
+
const handleOpenChange = useCallback(
|
|
43
|
+
(next: boolean) => {
|
|
44
|
+
if (next) {
|
|
45
|
+
discardRef.current = false;
|
|
46
|
+
const seeded = { ...rowRef.current };
|
|
47
|
+
draftRef.current = seeded;
|
|
48
|
+
setDraft(seeded);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const active = draftRef.current;
|
|
52
|
+
if (!discardRef.current && !readOnly && active) {
|
|
53
|
+
const changed = changedColumns(rowRef.current, active, columns);
|
|
54
|
+
if (changed.length > 0) onCommitRef.current(active, changed);
|
|
55
|
+
}
|
|
56
|
+
discardRef.current = false;
|
|
57
|
+
draftRef.current = null;
|
|
58
|
+
setDraft(null);
|
|
59
|
+
},
|
|
60
|
+
[columns, readOnly],
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const handleCancel = useCallback(() => {
|
|
64
|
+
discardRef.current = true;
|
|
65
|
+
handleOpenChange(false);
|
|
66
|
+
}, [handleOpenChange]);
|
|
67
|
+
|
|
68
|
+
const setDraftValue = useCallback((columnId: string, value: unknown) => {
|
|
69
|
+
const current = draftRef.current;
|
|
70
|
+
if (!current) return;
|
|
71
|
+
const next = { ...current, [columnId]: value } as TData;
|
|
72
|
+
draftRef.current = next;
|
|
73
|
+
setDraft(next);
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const triggerLabel = withInterp(
|
|
77
|
+
t(readOnly ? "View row {{row}}" : "Edit row {{row}}", { row: rowNumber }),
|
|
78
|
+
{ row: rowNumber },
|
|
79
|
+
);
|
|
80
|
+
const current = draft ?? row;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<FloatingPanel
|
|
84
|
+
open={draft !== null}
|
|
85
|
+
onOpenChange={handleOpenChange}
|
|
86
|
+
scrollable
|
|
87
|
+
triggerSizing="content"
|
|
88
|
+
triggerA11y={{ label: triggerLabel }}
|
|
89
|
+
trigger={
|
|
90
|
+
<View alignItems="center" justifyContent="center">
|
|
91
|
+
<PencilSimpleIcon size={16} />
|
|
92
|
+
</View>
|
|
93
|
+
}
|
|
94
|
+
>
|
|
95
|
+
<YStack {...knobProps.gapLg}>
|
|
96
|
+
<Text {...knobProps.heading}>
|
|
97
|
+
{withInterp(t("Row {{row}}", { row: rowNumber }), { row: rowNumber })}
|
|
98
|
+
</Text>
|
|
99
|
+
{columns.map((col) => {
|
|
100
|
+
const columnId = columnKey(col);
|
|
101
|
+
if (isCellHidden?.(current, columnId, rowIndex)) return null;
|
|
102
|
+
const label = typeof col.header === "string" ? col.header : columnId;
|
|
103
|
+
return (
|
|
104
|
+
<YStack key={columnId} {...knobProps.gap}>
|
|
105
|
+
<Text {...knobProps.label}>{label}</Text>
|
|
106
|
+
<DataTableCell
|
|
107
|
+
value={current[columnId as keyof TData]}
|
|
108
|
+
onChange={(value) => setDraftValue(columnId, value)}
|
|
109
|
+
field={(col.field || "input") as DataTableField}
|
|
110
|
+
fieldProps={col.fieldProps as DataTableFieldProps | undefined}
|
|
111
|
+
readOnly={readOnly}
|
|
112
|
+
compact={false}
|
|
113
|
+
columnId={columnId}
|
|
114
|
+
columnLabel={label}
|
|
115
|
+
rowIndex={rowIndex}
|
|
116
|
+
rowData={current}
|
|
117
|
+
/>
|
|
118
|
+
</YStack>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
<XStack {...knobProps.gap} justifyContent="flex-end">
|
|
122
|
+
{readOnly ? (
|
|
123
|
+
<Button onPress={handleCancel}>{t("Close")}</Button>
|
|
124
|
+
) : (
|
|
125
|
+
<>
|
|
126
|
+
<Button onPress={handleCancel}>{t("Cancel")}</Button>
|
|
127
|
+
<Button onPress={() => handleOpenChange(false)}>{t("Done")}</Button>
|
|
128
|
+
</>
|
|
129
|
+
)}
|
|
130
|
+
</XStack>
|
|
131
|
+
</YStack>
|
|
132
|
+
</FloatingPanel>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RowDetailEditor — the child-table row opened as a form (Frappe's grid
|
|
3
|
+
* row "Edit" form). The grid shows the list-view columns; the detail editor
|
|
4
|
+
* shows every `columns` entry for one row, full-size, in a dialog.
|
|
5
|
+
*
|
|
6
|
+
* Same staging contract as CellModalEditor (UX-023): the fields edit a draft
|
|
7
|
+
* copy of the row seeded on open. "Done" and an outside click commit the
|
|
8
|
+
* draft once; "Cancel" and Escape discard it. A read-only row opens for
|
|
9
|
+
* viewing with a single "Close".
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { DialogContent, DialogOverlay } from "@multiplatform.one/components";
|
|
13
|
+
import { Button } from "@multiplatform.one/forms";
|
|
14
|
+
import { TableCellContext } from "@multiplatform.one/table-primitives";
|
|
15
|
+
import { useResolvedKnobs } from "@multiplatform.one/theme";
|
|
16
|
+
import { PencilSimpleIcon } from "@phosphor-icons/react";
|
|
17
|
+
import { useCallback, useRef, useState } from "react";
|
|
18
|
+
import { Dialog, ScrollView, Text, XStack, YStack } from "tamagui";
|
|
19
|
+
import type {
|
|
20
|
+
DataTableColumn,
|
|
21
|
+
DataTableData,
|
|
22
|
+
DataTableField,
|
|
23
|
+
DataTableFieldProps,
|
|
24
|
+
} from "../../types";
|
|
25
|
+
import { t } from "../../shared/t";
|
|
26
|
+
import { withInterp } from "../../utils/withInterp";
|
|
27
|
+
import { DataTableCell } from "../DataTableCell";
|
|
28
|
+
import { changedColumns, columnKey, type ChildCellHidden } from "./shared";
|
|
29
|
+
|
|
30
|
+
export interface RowDetailEditorProps<TData extends DataTableData> {
|
|
31
|
+
row: TData;
|
|
32
|
+
rowIndex: number;
|
|
33
|
+
columns: DataTableColumn<TData>[];
|
|
34
|
+
readOnly: boolean;
|
|
35
|
+
compact: boolean;
|
|
36
|
+
isCellHidden?: ChildCellHidden<TData>;
|
|
37
|
+
/** Receives the edited row once per session, only when a value changed. */
|
|
38
|
+
onCommit: (row: TData, changed: string[]) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function RowDetailEditor<TData extends DataTableData>({
|
|
42
|
+
row,
|
|
43
|
+
rowIndex,
|
|
44
|
+
columns,
|
|
45
|
+
readOnly,
|
|
46
|
+
compact,
|
|
47
|
+
isCellHidden,
|
|
48
|
+
onCommit,
|
|
49
|
+
}: RowDetailEditorProps<TData>) {
|
|
50
|
+
const { knobProps } = useResolvedKnobs({ compact });
|
|
51
|
+
const [draft, setDraft] = useState<TData | null>(null);
|
|
52
|
+
const draftRef = useRef(draft);
|
|
53
|
+
draftRef.current = draft;
|
|
54
|
+
const rowRef = useRef(row);
|
|
55
|
+
rowRef.current = row;
|
|
56
|
+
const onCommitRef = useRef(onCommit);
|
|
57
|
+
onCommitRef.current = onCommit;
|
|
58
|
+
const discardRef = useRef(false);
|
|
59
|
+
const rowNumber = rowIndex + 1;
|
|
60
|
+
|
|
61
|
+
const handleOpenChange = useCallback(
|
|
62
|
+
(next: boolean) => {
|
|
63
|
+
if (next) {
|
|
64
|
+
discardRef.current = false;
|
|
65
|
+
const seeded = { ...rowRef.current };
|
|
66
|
+
draftRef.current = seeded;
|
|
67
|
+
setDraft(seeded);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const active = draftRef.current;
|
|
71
|
+
if (!discardRef.current && !readOnly && active) {
|
|
72
|
+
const changed = changedColumns(rowRef.current, active, columns);
|
|
73
|
+
if (changed.length > 0) onCommitRef.current(active, changed);
|
|
74
|
+
}
|
|
75
|
+
discardRef.current = false;
|
|
76
|
+
draftRef.current = null;
|
|
77
|
+
setDraft(null);
|
|
78
|
+
},
|
|
79
|
+
[columns, readOnly],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const handleCancel = useCallback(() => {
|
|
83
|
+
discardRef.current = true;
|
|
84
|
+
handleOpenChange(false);
|
|
85
|
+
}, [handleOpenChange]);
|
|
86
|
+
|
|
87
|
+
const setDraftValue = useCallback((columnId: string, value: unknown) => {
|
|
88
|
+
const current = draftRef.current;
|
|
89
|
+
if (!current) return;
|
|
90
|
+
const next = { ...current, [columnId]: value } as TData;
|
|
91
|
+
draftRef.current = next;
|
|
92
|
+
setDraft(next);
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const triggerLabel = withInterp(
|
|
96
|
+
t(readOnly ? "View row {{row}}" : "Edit row {{row}}", { row: rowNumber }),
|
|
97
|
+
{ row: rowNumber },
|
|
98
|
+
);
|
|
99
|
+
const current = draft ?? row;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
<Button
|
|
104
|
+
chromeless
|
|
105
|
+
compact={compact}
|
|
106
|
+
onPress={() => handleOpenChange(true)}
|
|
107
|
+
hitSlop={knobProps.nestedControl.hitSlop}
|
|
108
|
+
aria-label={triggerLabel}
|
|
109
|
+
aria-haspopup="dialog"
|
|
110
|
+
aria-expanded={draft !== null}
|
|
111
|
+
>
|
|
112
|
+
<PencilSimpleIcon size={16} />
|
|
113
|
+
</Button>
|
|
114
|
+
<Dialog open={draft !== null} onOpenChange={handleOpenChange} modal>
|
|
115
|
+
<Dialog.Portal>
|
|
116
|
+
<DialogOverlay opacity={0.5} />
|
|
117
|
+
<DialogContent
|
|
118
|
+
maxWidth={720}
|
|
119
|
+
maxHeight="80vh"
|
|
120
|
+
width="90vw"
|
|
121
|
+
onEscapeKeyDown={() => {
|
|
122
|
+
discardRef.current = true;
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
<Dialog.Title {...knobProps.heading}>
|
|
126
|
+
{withInterp(t("Row {{row}}", { row: rowNumber }), { row: rowNumber })}
|
|
127
|
+
</Dialog.Title>
|
|
128
|
+
<ScrollView flexShrink={1} minHeight={0}>
|
|
129
|
+
<TableCellContext.Provider value={{ inTableCell: false }}>
|
|
130
|
+
<YStack {...knobProps.gapLg}>
|
|
131
|
+
{columns.map((col) => {
|
|
132
|
+
const columnId = columnKey(col);
|
|
133
|
+
if (isCellHidden?.(current, columnId, rowIndex)) return null;
|
|
134
|
+
const label = typeof col.header === "string" ? col.header : columnId;
|
|
135
|
+
return (
|
|
136
|
+
<YStack key={columnId} {...knobProps.gap}>
|
|
137
|
+
<Text {...knobProps.label}>{label}</Text>
|
|
138
|
+
<DataTableCell
|
|
139
|
+
value={current[columnId as keyof TData]}
|
|
140
|
+
onChange={(value) => setDraftValue(columnId, value)}
|
|
141
|
+
field={(col.field || "input") as DataTableField}
|
|
142
|
+
fieldProps={col.fieldProps as DataTableFieldProps | undefined}
|
|
143
|
+
readOnly={readOnly}
|
|
144
|
+
compact={false}
|
|
145
|
+
columnId={columnId}
|
|
146
|
+
columnLabel={label}
|
|
147
|
+
rowIndex={rowIndex}
|
|
148
|
+
rowData={current}
|
|
149
|
+
/>
|
|
150
|
+
</YStack>
|
|
151
|
+
);
|
|
152
|
+
})}
|
|
153
|
+
</YStack>
|
|
154
|
+
</TableCellContext.Provider>
|
|
155
|
+
</ScrollView>
|
|
156
|
+
<XStack {...knobProps.gap} justifyContent="flex-end">
|
|
157
|
+
{readOnly ? (
|
|
158
|
+
<Button onPress={handleCancel}>{t("Close")}</Button>
|
|
159
|
+
) : (
|
|
160
|
+
<>
|
|
161
|
+
<Button onPress={handleCancel}>{t("Cancel")}</Button>
|
|
162
|
+
<Button theme="accent" onPress={() => handleOpenChange(false)}>
|
|
163
|
+
{t("Done")}
|
|
164
|
+
</Button>
|
|
165
|
+
</>
|
|
166
|
+
)}
|
|
167
|
+
</XStack>
|
|
168
|
+
</DialogContent>
|
|
169
|
+
</Dialog.Portal>
|
|
170
|
+
</Dialog>
|
|
171
|
+
</>
|
|
172
|
+
);
|
|
173
|
+
}
|