@marimo-team/frontend 0.24.1-dev109 → 0.24.1-dev110
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/assets/{JsonOutput-DflXi8WP.js → JsonOutput-BYSpdF43.js} +10 -10
- package/dist/assets/{agent-panel-CvylOk8N.js → agent-panel-CIGDXQtn.js} +1 -1
- package/dist/assets/{cell-editor-B2FYa_r2.js → cell-editor-D7fjZc8_.js} +2 -2
- package/dist/assets/{column-preview-4ZtjRVCL.js → column-preview-Bnrq4bEC.js} +1 -1
- package/dist/assets/{command-palette-_ZYa5wz-.js → command-palette-CYaDy4p2.js} +1 -1
- package/dist/assets/{edit-page-DhK8zpUw.js → edit-page-DvStrWEF.js} +6 -6
- package/dist/assets/{file-explorer-panel-CBi7IrTJ.js → file-explorer-panel-CC4C9SnN.js} +1 -1
- package/dist/assets/index-BHZDiHTq.js +29 -0
- package/dist/assets/{layout-B1OYn1x9.js → layout-BBzwwt8g.js} +5 -5
- package/dist/assets/{panels-Bpc7gKzJ.js → panels-BTHaPtUZ.js} +1 -1
- package/dist/assets/{reveal-component-C-5eIjh7.js → reveal-component-CawytCeQ.js} +1 -1
- package/dist/assets/{run-page-Jmrd4hPR.js → run-page-DrxVLavD.js} +1 -1
- package/dist/assets/{scratchpad-panel-B37VTDcH.js → scratchpad-panel-DUioP0f7.js} +1 -1
- package/dist/assets/session-panel-DiKPWSPU.js +1 -0
- package/dist/assets/{skeleton-BOqU4fSS.js → skeleton-BDmfpU3X.js} +1 -1
- package/dist/assets/{useNotebookActions-Bh1LWIwk.js → useNotebookActions-AeYQuSXc.js} +2 -2
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/components/data-table/__tests__/column-explorer.test.tsx +61 -2
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +130 -1
- package/src/components/data-table/__tests__/useColumnVisibility.test.ts +153 -1
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +54 -25
- package/src/components/data-table/column-visibility-dropdown.tsx +62 -14
- package/src/components/data-table/hooks/use-column-visibility.ts +76 -0
- package/src/components/data-table/show-only-column-button.tsx +58 -0
- package/dist/assets/index-PQMiMaaL.js +0 -29
- package/dist/assets/session-panel-DNhueSlc.js +0 -1
|
@@ -1,8 +1,62 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
type ColumnDef,
|
|
5
|
+
getCoreRowModel,
|
|
6
|
+
useReactTable,
|
|
7
|
+
} from "@tanstack/react-table";
|
|
3
8
|
import { act, renderHook } from "@testing-library/react";
|
|
4
9
|
import { describe, expect, it } from "vitest";
|
|
5
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
applyShowOnlyColumns,
|
|
12
|
+
getShowOnlyColumnState,
|
|
13
|
+
getShowOnlyVisibility,
|
|
14
|
+
isShowingOnly,
|
|
15
|
+
isShowingOnlyColumns,
|
|
16
|
+
useColumnVisibility,
|
|
17
|
+
} from "../hooks/use-column-visibility";
|
|
18
|
+
import { INDEX_COLUMN_NAME, SELECT_COLUMN_ID } from "../types";
|
|
19
|
+
|
|
20
|
+
type Row = Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
const TEST_COLUMNS: ColumnDef<Row>[] = [
|
|
23
|
+
{ id: SELECT_COLUMN_ID, accessorKey: SELECT_COLUMN_ID, enableHiding: false },
|
|
24
|
+
{
|
|
25
|
+
id: INDEX_COLUMN_NAME,
|
|
26
|
+
accessorKey: INDEX_COLUMN_NAME,
|
|
27
|
+
enableHiding: false,
|
|
28
|
+
},
|
|
29
|
+
{ id: "customer_name", accessorKey: "customer_name" },
|
|
30
|
+
{ id: "cust_age", accessorKey: "cust_age" },
|
|
31
|
+
{ id: "order_total", accessorKey: "order_total" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
function createTestTable({
|
|
35
|
+
initiallyHidden = [],
|
|
36
|
+
nonHideable = [],
|
|
37
|
+
}: {
|
|
38
|
+
initiallyHidden?: string[];
|
|
39
|
+
nonHideable?: string[];
|
|
40
|
+
} = {}) {
|
|
41
|
+
const { result } = renderHook(() =>
|
|
42
|
+
useReactTable<Row>({
|
|
43
|
+
data: [],
|
|
44
|
+
columns: TEST_COLUMNS.map((column) =>
|
|
45
|
+
nonHideable.includes(column.id as string)
|
|
46
|
+
? { ...column, enableHiding: false }
|
|
47
|
+
: column,
|
|
48
|
+
),
|
|
49
|
+
getCoreRowModel: getCoreRowModel(),
|
|
50
|
+
locale: "en-US",
|
|
51
|
+
initialState: {
|
|
52
|
+
columnVisibility: Object.fromEntries(
|
|
53
|
+
initiallyHidden.map((id) => [id, false]),
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
return result.current;
|
|
59
|
+
}
|
|
6
60
|
|
|
7
61
|
describe("useColumnVisibility", () => {
|
|
8
62
|
it("should initialize with correct default values", () => {
|
|
@@ -40,3 +94,101 @@ describe("useColumnVisibility", () => {
|
|
|
40
94
|
expect(result.current.columnVisibility).toEqual({ a: false, c: false });
|
|
41
95
|
});
|
|
42
96
|
});
|
|
97
|
+
|
|
98
|
+
describe("getShowOnlyVisibility", () => {
|
|
99
|
+
it("shows matching hideable columns and hides the rest", () => {
|
|
100
|
+
const table = createTestTable({ initiallyHidden: ["cust_age"] });
|
|
101
|
+
|
|
102
|
+
expect(
|
|
103
|
+
getShowOnlyVisibility(table, ["customer_name", "order_total"]),
|
|
104
|
+
).toEqual({
|
|
105
|
+
customer_name: true,
|
|
106
|
+
cust_age: false,
|
|
107
|
+
order_total: true,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("ignores non-hideable columns in the input", () => {
|
|
112
|
+
const table = createTestTable({ nonHideable: ["customer_name"] });
|
|
113
|
+
|
|
114
|
+
expect(
|
|
115
|
+
getShowOnlyVisibility(table, [
|
|
116
|
+
SELECT_COLUMN_ID,
|
|
117
|
+
"customer_name",
|
|
118
|
+
"cust_age",
|
|
119
|
+
]),
|
|
120
|
+
).toEqual({
|
|
121
|
+
cust_age: true,
|
|
122
|
+
order_total: false,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("hides every hideable column when the input is empty", () => {
|
|
127
|
+
const table = createTestTable();
|
|
128
|
+
|
|
129
|
+
expect(getShowOnlyVisibility(table, [])).toEqual({
|
|
130
|
+
customer_name: false,
|
|
131
|
+
cust_age: false,
|
|
132
|
+
order_total: false,
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("isShowingOnly", () => {
|
|
138
|
+
it("returns true when visible hideable columns match the input", () => {
|
|
139
|
+
const table = createTestTable({
|
|
140
|
+
initiallyHidden: ["cust_age", "order_total"],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(isShowingOnly(table, ["customer_name"])).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("returns false when extra hideable columns remain visible", () => {
|
|
147
|
+
const table = createTestTable();
|
|
148
|
+
|
|
149
|
+
expect(isShowingOnly(table, ["customer_name"])).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("ignores non-hideable columns in the input", () => {
|
|
153
|
+
const table = createTestTable({
|
|
154
|
+
initiallyHidden: ["cust_age", "order_total"],
|
|
155
|
+
nonHideable: ["customer_name"],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(isShowingOnly(table, [SELECT_COLUMN_ID, "customer_name"])).toBe(
|
|
159
|
+
true,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("returns true when every hideable column is hidden and the input is empty", () => {
|
|
164
|
+
const table = createTestTable({
|
|
165
|
+
initiallyHidden: ["customer_name", "cust_age", "order_total"],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(isShowingOnly(table, [])).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("isShowingOnlyColumns", () => {
|
|
173
|
+
it("reuses precomputed visible hideable columns", () => {
|
|
174
|
+
const table = createTestTable({
|
|
175
|
+
initiallyHidden: ["cust_age", "order_total"],
|
|
176
|
+
});
|
|
177
|
+
const state = getShowOnlyColumnState(table);
|
|
178
|
+
|
|
179
|
+
expect(isShowingOnlyColumns(state, ["customer_name"])).toBe(true);
|
|
180
|
+
expect(isShowingOnly(table, ["customer_name"])).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("applyShowOnlyColumns", () => {
|
|
185
|
+
it("shows only the requested hideable columns", () => {
|
|
186
|
+
const table = createTestTable({ initiallyHidden: ["cust_age"] });
|
|
187
|
+
|
|
188
|
+
act(() => {
|
|
189
|
+
applyShowOnlyColumns(table, ["customer_name", "order_total"]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(isShowingOnly(table, ["customer_name", "order_total"])).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
EyeIcon,
|
|
11
11
|
EyeOffIcon,
|
|
12
12
|
} from "lucide-react";
|
|
13
|
-
import { useState } from "react";
|
|
13
|
+
import { useMemo, useState } from "react";
|
|
14
14
|
import { useLocale } from "react-aria";
|
|
15
15
|
import {
|
|
16
16
|
AddDataframeChart,
|
|
@@ -52,8 +52,12 @@ import type { Column, Table } from "@tanstack/react-table";
|
|
|
52
52
|
import { cn } from "@/utils/cn";
|
|
53
53
|
import {
|
|
54
54
|
getColumnCountForDisplay,
|
|
55
|
+
getShowOnlyColumnState,
|
|
55
56
|
getUserColumnVisibilityCounts,
|
|
57
|
+
isShowingOnlyColumns,
|
|
58
|
+
type ShowOnlyColumnState,
|
|
56
59
|
} from "../hooks/use-column-visibility";
|
|
60
|
+
import { ShowOnlyColumnButton } from "../show-only-column-button";
|
|
57
61
|
|
|
58
62
|
interface ColumnExplorerPanelProps<TData> {
|
|
59
63
|
previewColumn: PreviewColumn;
|
|
@@ -94,6 +98,11 @@ export function ColumnExplorerPanel<TData>({
|
|
|
94
98
|
hiddenColumns: hiddenColumnCount,
|
|
95
99
|
} = getColumnCountForDisplay(table, totalColumns);
|
|
96
100
|
const { visible: visibleColumnCount } = getUserColumnVisibilityCounts(table);
|
|
101
|
+
const columnVisibility = table.getState().columnVisibility;
|
|
102
|
+
const showOnlyColumnState = useMemo(
|
|
103
|
+
() => getShowOnlyColumnState(table),
|
|
104
|
+
[table, columnVisibility],
|
|
105
|
+
);
|
|
97
106
|
|
|
98
107
|
const { rowsAndColumns, hiddenSuffix } = prettifyRowColumnCount({
|
|
99
108
|
numRows: totalRows,
|
|
@@ -162,6 +171,8 @@ export function ColumnExplorerPanel<TData>({
|
|
|
162
171
|
dataType={dataType}
|
|
163
172
|
externalType={externalType}
|
|
164
173
|
previewColumn={previewColumn}
|
|
174
|
+
table={table}
|
|
175
|
+
showOnlyColumnState={showOnlyColumnState}
|
|
165
176
|
defaultExpanded={index === 0}
|
|
166
177
|
/>
|
|
167
178
|
);
|
|
@@ -179,6 +190,8 @@ function ColumnItem<TData>({
|
|
|
179
190
|
dataType,
|
|
180
191
|
externalType,
|
|
181
192
|
previewColumn,
|
|
193
|
+
table,
|
|
194
|
+
showOnlyColumnState,
|
|
182
195
|
defaultExpanded = false,
|
|
183
196
|
}: {
|
|
184
197
|
columnName: string;
|
|
@@ -186,6 +199,8 @@ function ColumnItem<TData>({
|
|
|
186
199
|
dataType: DataType;
|
|
187
200
|
externalType: string;
|
|
188
201
|
previewColumn: PreviewColumn;
|
|
202
|
+
table: Table<TData>;
|
|
203
|
+
showOnlyColumnState: ShowOnlyColumnState;
|
|
189
204
|
defaultExpanded?: boolean;
|
|
190
205
|
}) {
|
|
191
206
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
@@ -218,36 +233,50 @@ function ColumnItem<TData>({
|
|
|
218
233
|
)}
|
|
219
234
|
/>
|
|
220
235
|
{column?.getCanHide() && (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
size="icon"
|
|
229
|
-
aria-label={
|
|
230
|
-
column.getIsVisible() ? "Hide column" : "Show column"
|
|
231
|
-
}
|
|
236
|
+
<>
|
|
237
|
+
<ShowOnlyColumnButton
|
|
238
|
+
table={table}
|
|
239
|
+
columnIds={[columnName]}
|
|
240
|
+
disabled={isShowingOnlyColumns(showOnlyColumnState, [
|
|
241
|
+
columnName,
|
|
242
|
+
])}
|
|
232
243
|
className={cn(
|
|
233
|
-
"hover:bg-muted text-muted-foreground hover:text-primary",
|
|
234
244
|
column.getIsVisible()
|
|
235
245
|
? "group-hover:opacity-100 opacity-0"
|
|
236
246
|
: "opacity-100",
|
|
237
247
|
)}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}}
|
|
248
|
+
/>
|
|
249
|
+
<Tooltip
|
|
250
|
+
content={column.getIsVisible() ? "Hide column" : "Show column"}
|
|
251
|
+
delayDuration={400}
|
|
243
252
|
>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
253
|
+
<Button
|
|
254
|
+
type="button"
|
|
255
|
+
variant="text"
|
|
256
|
+
size="icon"
|
|
257
|
+
aria-label={
|
|
258
|
+
column.getIsVisible() ? "Hide column" : "Show column"
|
|
259
|
+
}
|
|
260
|
+
className={cn(
|
|
261
|
+
"hover:bg-muted text-muted-foreground hover:text-primary",
|
|
262
|
+
column.getIsVisible()
|
|
263
|
+
? "group-hover:opacity-100 opacity-0"
|
|
264
|
+
: "opacity-100",
|
|
265
|
+
)}
|
|
266
|
+
onClick={(e) => {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
column.toggleVisibility(!column.getIsVisible());
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
{column.getIsVisible() ? (
|
|
273
|
+
<EyeIcon className="h-3 w-3" strokeWidth={2.5} />
|
|
274
|
+
) : (
|
|
275
|
+
<EyeOffIcon className="h-3 w-3" strokeWidth={2.5} />
|
|
276
|
+
)}
|
|
277
|
+
</Button>
|
|
278
|
+
</Tooltip>
|
|
279
|
+
</>
|
|
251
280
|
)}
|
|
252
281
|
<span className="text-xs text-muted-foreground">{externalType}</span>
|
|
253
282
|
</div>
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// https://github.com/TanStack/table/issues/5567
|
|
6
6
|
|
|
7
7
|
import type { Table } from "@tanstack/react-table";
|
|
8
|
-
import { Columns3Icon, EyeIcon, EyeOffIcon } from "lucide-react";
|
|
9
|
-
import React from "react";
|
|
8
|
+
import { Columns3Icon, EyeIcon, EyeOffIcon, ScanEyeIcon } from "lucide-react";
|
|
9
|
+
import React, { useMemo } from "react";
|
|
10
10
|
import { ColumnName } from "@/components/datasources/components";
|
|
11
11
|
import { Button } from "@/components/ui/button";
|
|
12
12
|
import {
|
|
@@ -28,6 +28,11 @@ import { cn } from "@/utils/cn";
|
|
|
28
28
|
import { Events } from "@/utils/events";
|
|
29
29
|
import { smartMatchFilter } from "@/utils/smartMatch";
|
|
30
30
|
import { NAMELESS_COLUMN_PREFIX } from "./columns";
|
|
31
|
+
import {
|
|
32
|
+
applyShowOnlyColumns,
|
|
33
|
+
isShowingOnlyColumns,
|
|
34
|
+
} from "./hooks/use-column-visibility";
|
|
35
|
+
import { ShowOnlyColumnButton } from "./show-only-column-button";
|
|
31
36
|
import { INDEX_COLUMN_NAME, SELECT_COLUMN_ID } from "./types";
|
|
32
37
|
|
|
33
38
|
function getUserColumns<TData>(table: Table<TData>) {
|
|
@@ -95,6 +100,23 @@ export const ColumnVisibilityDropdown = <TData,>({
|
|
|
95
100
|
> =>
|
|
96
101
|
action.kind === "select-matching" || action.kind === "deselect-matching",
|
|
97
102
|
);
|
|
103
|
+
const showOnlyMatchIds =
|
|
104
|
+
list.searchQuery !== ""
|
|
105
|
+
? list.visibleOptions
|
|
106
|
+
.filter((option) => !option.disabled)
|
|
107
|
+
.map((option) => option.value)
|
|
108
|
+
: [];
|
|
109
|
+
const showOnlyColumnState = useMemo(
|
|
110
|
+
() => ({
|
|
111
|
+
visibleHideableColumnIds: hideableIds.filter(
|
|
112
|
+
(id) => !hiddenIds.includes(id),
|
|
113
|
+
),
|
|
114
|
+
hideableColumnIdSet: new Set(hideableIds),
|
|
115
|
+
}),
|
|
116
|
+
[hideableIds, hiddenIds],
|
|
117
|
+
);
|
|
118
|
+
const hasSearchBulkActions =
|
|
119
|
+
showOnlyMatchIds.length > 0 || matchingActions.length > 0;
|
|
98
120
|
|
|
99
121
|
return (
|
|
100
122
|
<Popover open={list.open} onOpenChange={list.setOpen}>
|
|
@@ -145,8 +167,25 @@ export const ColumnVisibilityDropdown = <TData,>({
|
|
|
145
167
|
<CommandSeparator />
|
|
146
168
|
</>
|
|
147
169
|
) : (
|
|
148
|
-
|
|
170
|
+
hasSearchBulkActions && (
|
|
149
171
|
<>
|
|
172
|
+
<CommandItem
|
|
173
|
+
value="__show_only_matching__"
|
|
174
|
+
disabled={
|
|
175
|
+
showOnlyMatchIds.length === 0 ||
|
|
176
|
+
isShowingOnlyColumns(
|
|
177
|
+
showOnlyColumnState,
|
|
178
|
+
showOnlyMatchIds,
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
onSelect={() => {
|
|
182
|
+
applyShowOnlyColumns(table, showOnlyMatchIds);
|
|
183
|
+
}}
|
|
184
|
+
className="cursor-pointer"
|
|
185
|
+
>
|
|
186
|
+
<ScanEyeIcon className="w-3 h-3 mr-1.5" />
|
|
187
|
+
Show only {showOnlyMatchIds.length} matching
|
|
188
|
+
</CommandItem>
|
|
150
189
|
{matchingActions.map((action) => (
|
|
151
190
|
<CommandItem
|
|
152
191
|
key={action.kind}
|
|
@@ -194,17 +233,26 @@ export const ColumnVisibilityDropdown = <TData,>({
|
|
|
194
233
|
/>
|
|
195
234
|
)}
|
|
196
235
|
{!option.disabled && (
|
|
197
|
-
<span
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
236
|
+
<span className="ml-auto flex items-center gap-0.5">
|
|
237
|
+
<ShowOnlyColumnButton
|
|
238
|
+
table={table}
|
|
239
|
+
columnIds={[option.value]}
|
|
240
|
+
disabled={isShowingOnlyColumns(showOnlyColumnState, [
|
|
241
|
+
option.value,
|
|
242
|
+
])}
|
|
243
|
+
iconClassName="w-3 h-3"
|
|
244
|
+
/>
|
|
245
|
+
<span
|
|
246
|
+
className={cn(
|
|
247
|
+
hidden ? "text-primary" : "text-muted-foreground",
|
|
248
|
+
)}
|
|
249
|
+
>
|
|
250
|
+
{hidden ? (
|
|
251
|
+
<EyeOffIcon className="w-3 h-3" />
|
|
252
|
+
) : (
|
|
253
|
+
<EyeIcon className="w-3 h-3" />
|
|
254
|
+
)}
|
|
255
|
+
</span>
|
|
208
256
|
</span>
|
|
209
257
|
)}
|
|
210
258
|
</CommandItem>
|
|
@@ -54,3 +54,79 @@ export function getColumnCountForDisplay<TData>(
|
|
|
54
54
|
hiddenColumns: counts.hidden,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
export function getShowOnlyVisibility<TData>(
|
|
59
|
+
table: Table<TData>,
|
|
60
|
+
columnIds: string[],
|
|
61
|
+
): VisibilityState {
|
|
62
|
+
const showOnlySet = new Set(columnIds);
|
|
63
|
+
const visibility: VisibilityState = {};
|
|
64
|
+
|
|
65
|
+
for (const column of table.getAllLeafColumns()) {
|
|
66
|
+
if (!column.getCanHide()) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
visibility[column.id] = showOnlySet.has(column.id);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return visibility;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function applyShowOnlyColumns<TData>(
|
|
76
|
+
table: Table<TData>,
|
|
77
|
+
columnIds: string[],
|
|
78
|
+
): void {
|
|
79
|
+
table.setColumnVisibility((previous) => ({
|
|
80
|
+
...previous,
|
|
81
|
+
...getShowOnlyVisibility(table, columnIds),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ShowOnlyColumnState {
|
|
86
|
+
visibleHideableColumnIds: readonly string[];
|
|
87
|
+
hideableColumnIdSet: ReadonlySet<string>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getShowOnlyColumnState<TData>(
|
|
91
|
+
table: Table<TData>,
|
|
92
|
+
): ShowOnlyColumnState {
|
|
93
|
+
const hideableIds: string[] = [];
|
|
94
|
+
const visibleHideableColumnIds: string[] = [];
|
|
95
|
+
|
|
96
|
+
for (const column of table.getAllLeafColumns()) {
|
|
97
|
+
if (!column.getCanHide()) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
hideableIds.push(column.id);
|
|
101
|
+
if (column.getIsVisible()) {
|
|
102
|
+
visibleHideableColumnIds.push(column.id);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
visibleHideableColumnIds,
|
|
108
|
+
hideableColumnIdSet: new Set(hideableIds),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function isShowingOnlyColumns(
|
|
113
|
+
state: ShowOnlyColumnState,
|
|
114
|
+
columnIds: readonly string[],
|
|
115
|
+
): boolean {
|
|
116
|
+
const targetIds = new Set(
|
|
117
|
+
columnIds.filter((id) => state.hideableColumnIdSet.has(id)),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (state.visibleHideableColumnIds.length !== targetIds.size) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return state.visibleHideableColumnIds.every((id) => targetIds.has(id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function isShowingOnly<TData>(
|
|
128
|
+
table: Table<TData>,
|
|
129
|
+
columnIds: string[],
|
|
130
|
+
): boolean {
|
|
131
|
+
return isShowingOnlyColumns(getShowOnlyColumnState(table), columnIds);
|
|
132
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
"use no memo";
|
|
3
|
+
|
|
4
|
+
import type { Table } from "@tanstack/react-table";
|
|
5
|
+
import { ScanEyeIcon } from "lucide-react";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
8
|
+
import { cn } from "@/utils/cn";
|
|
9
|
+
import { applyShowOnlyColumns } from "./hooks/use-column-visibility";
|
|
10
|
+
|
|
11
|
+
export const SHOW_ONLY_COLUMN_LABEL = "Show only this column";
|
|
12
|
+
|
|
13
|
+
interface ShowOnlyColumnButtonProps<TData> {
|
|
14
|
+
table: Table<TData>;
|
|
15
|
+
columnIds: string[];
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
iconClassName?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ShowOnlyColumnButton<TData>({
|
|
22
|
+
table,
|
|
23
|
+
columnIds,
|
|
24
|
+
disabled,
|
|
25
|
+
className,
|
|
26
|
+
iconClassName = "h-3 w-3",
|
|
27
|
+
}: ShowOnlyColumnButtonProps<TData>) {
|
|
28
|
+
return (
|
|
29
|
+
<Tooltip content={SHOW_ONLY_COLUMN_LABEL} delayDuration={400}>
|
|
30
|
+
<Button
|
|
31
|
+
type="button"
|
|
32
|
+
variant="text"
|
|
33
|
+
size="icon"
|
|
34
|
+
aria-label={SHOW_ONLY_COLUMN_LABEL}
|
|
35
|
+
aria-disabled={disabled}
|
|
36
|
+
tabIndex={disabled ? -1 : 0}
|
|
37
|
+
className={cn(
|
|
38
|
+
"h-6 w-6 hover:bg-muted text-muted-foreground hover:text-primary",
|
|
39
|
+
disabled && "opacity-50",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
onPointerDown={(event) => {
|
|
43
|
+
event.stopPropagation();
|
|
44
|
+
}}
|
|
45
|
+
onClick={(event) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
event.stopPropagation();
|
|
48
|
+
if (disabled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
applyShowOnlyColumns(table, columnIds);
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<ScanEyeIcon className={iconClassName} strokeWidth={2.5} />
|
|
55
|
+
</Button>
|
|
56
|
+
</Tooltip>
|
|
57
|
+
);
|
|
58
|
+
}
|