@marimo-team/islands 0.23.10-dev29 → 0.23.10-dev30
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/{code-visibility-C_uXSW2l.js → code-visibility-wR7WSQ4c.js} +1227 -986
- package/dist/main.js +1103 -1165
- package/dist/{reveal-component-BdSVsJMP.js → reveal-component-BjnkUAZ9.js} +23 -23
- package/package.json +1 -1
- package/src/components/data-table/TableBottomBar.tsx +1 -15
- package/src/components/data-table/TableTopBar.tsx +8 -13
- package/src/components/data-table/__tests__/TableBottomBar.test.tsx +6 -12
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +227 -0
- package/src/components/data-table/column-visibility-dropdown.tsx +204 -0
- package/src/components/data-table/data-table.tsx +1 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
"use no memo";
|
|
3
|
+
|
|
4
|
+
// tanstack/table is not compatible with React compiler
|
|
5
|
+
// https://github.com/TanStack/table/issues/5567
|
|
6
|
+
|
|
7
|
+
import type { Table } from "@tanstack/react-table";
|
|
8
|
+
import { Columns3Icon, EyeIcon, EyeOffIcon } from "lucide-react";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { ColumnName } from "@/components/datasources/components";
|
|
11
|
+
import { Button } from "@/components/ui/button";
|
|
12
|
+
import {
|
|
13
|
+
Command,
|
|
14
|
+
CommandEmpty,
|
|
15
|
+
CommandInput,
|
|
16
|
+
CommandItem,
|
|
17
|
+
CommandList,
|
|
18
|
+
CommandSeparator,
|
|
19
|
+
} from "@/components/ui/command";
|
|
20
|
+
import {
|
|
21
|
+
Popover,
|
|
22
|
+
PopoverContent,
|
|
23
|
+
PopoverTrigger,
|
|
24
|
+
} from "@/components/ui/popover";
|
|
25
|
+
import { type BulkAction, useSelectList } from "@/components/ui/select-core";
|
|
26
|
+
import type { DataType } from "@/core/kernel/messages";
|
|
27
|
+
import { cn } from "@/utils/cn";
|
|
28
|
+
import { Events } from "@/utils/events";
|
|
29
|
+
import { smartMatchFilter } from "@/utils/smartMatch";
|
|
30
|
+
import { NAMELESS_COLUMN_PREFIX } from "./columns";
|
|
31
|
+
import { INDEX_COLUMN_NAME, SELECT_COLUMN_ID } from "./types";
|
|
32
|
+
|
|
33
|
+
function getUserColumns<TData>(table: Table<TData>) {
|
|
34
|
+
return table
|
|
35
|
+
.getAllLeafColumns()
|
|
36
|
+
.filter(
|
|
37
|
+
(column) =>
|
|
38
|
+
column.id !== SELECT_COLUMN_ID &&
|
|
39
|
+
column.id !== INDEX_COLUMN_NAME &&
|
|
40
|
+
!column.id.startsWith(NAMELESS_COLUMN_PREFIX),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const ColumnVisibilityDropdown = <TData,>({
|
|
45
|
+
table,
|
|
46
|
+
}: {
|
|
47
|
+
table: Table<TData>;
|
|
48
|
+
}) => {
|
|
49
|
+
const userColumns = getUserColumns(table);
|
|
50
|
+
const options = userColumns.map((column) => ({
|
|
51
|
+
value: column.id,
|
|
52
|
+
label: column.id,
|
|
53
|
+
disabled: !column.getCanHide(),
|
|
54
|
+
data: { dataType: column.columnDef.meta?.dataType },
|
|
55
|
+
}));
|
|
56
|
+
// Modeled as a select list over hidden columns: "selected" means hidden, so
|
|
57
|
+
// the hook's pinning floats hidden columns to the top and freezes that order
|
|
58
|
+
// while the menu is open.
|
|
59
|
+
const hiddenIds = userColumns
|
|
60
|
+
.filter((column) => !column.getIsVisible())
|
|
61
|
+
.map((column) => column.id);
|
|
62
|
+
|
|
63
|
+
const applyHidden = (next: string[] | string | null) => {
|
|
64
|
+
const hidden = new Set(Array.isArray(next) ? next : []);
|
|
65
|
+
table.setColumnVisibility((previous) => ({
|
|
66
|
+
...previous,
|
|
67
|
+
...Object.fromEntries(
|
|
68
|
+
userColumns.map((column) => [column.id, !hidden.has(column.id)]),
|
|
69
|
+
),
|
|
70
|
+
}));
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const list = useSelectList<string>({
|
|
74
|
+
options,
|
|
75
|
+
value: hiddenIds,
|
|
76
|
+
onChange: applyHidden,
|
|
77
|
+
multiple: true,
|
|
78
|
+
filterFn: smartMatchFilter,
|
|
79
|
+
pinSelected: true,
|
|
80
|
+
});
|
|
81
|
+
// With selection modeling hidden columns, select-matching hides the visible
|
|
82
|
+
// matches and deselect-matching shows the hidden ones.
|
|
83
|
+
const matchingActions = list.bulkActions.filter(
|
|
84
|
+
(
|
|
85
|
+
action,
|
|
86
|
+
): action is Extract<
|
|
87
|
+
BulkAction<string>,
|
|
88
|
+
{ kind: "select-matching" | "deselect-matching" }
|
|
89
|
+
> =>
|
|
90
|
+
action.kind === "select-matching" || action.kind === "deselect-matching",
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Popover open={list.open} onOpenChange={list.setOpen}>
|
|
95
|
+
<PopoverTrigger asChild={true}>
|
|
96
|
+
<Button
|
|
97
|
+
variant="text"
|
|
98
|
+
size="xs"
|
|
99
|
+
data-testid="column-visibility-trigger"
|
|
100
|
+
onMouseDown={Events.preventFocus}
|
|
101
|
+
className={cn(
|
|
102
|
+
"print:hidden text-xs gap-1",
|
|
103
|
+
list.open ? "text-primary" : "text-muted-foreground",
|
|
104
|
+
)}
|
|
105
|
+
>
|
|
106
|
+
<Columns3Icon className="w-3.5 h-3.5" />
|
|
107
|
+
Columns
|
|
108
|
+
</Button>
|
|
109
|
+
</PopoverTrigger>
|
|
110
|
+
<PopoverContent className="w-64 p-0" align="end">
|
|
111
|
+
<Command shouldFilter={false}>
|
|
112
|
+
<CommandInput
|
|
113
|
+
placeholder="Search columns..."
|
|
114
|
+
value={list.searchQuery}
|
|
115
|
+
onValueChange={list.setSearchQuery}
|
|
116
|
+
/>
|
|
117
|
+
<CommandList>
|
|
118
|
+
<CommandEmpty>No results.</CommandEmpty>
|
|
119
|
+
{list.searchQuery === "" ? (
|
|
120
|
+
<>
|
|
121
|
+
<CommandItem
|
|
122
|
+
value="__show_all__"
|
|
123
|
+
disabled={hiddenIds.length === 0}
|
|
124
|
+
onSelect={() => applyHidden([])}
|
|
125
|
+
className="cursor-pointer"
|
|
126
|
+
>
|
|
127
|
+
<EyeIcon className="w-3 h-3 mr-1.5" />
|
|
128
|
+
Show all
|
|
129
|
+
</CommandItem>
|
|
130
|
+
<CommandSeparator />
|
|
131
|
+
</>
|
|
132
|
+
) : (
|
|
133
|
+
matchingActions.length > 0 && (
|
|
134
|
+
<>
|
|
135
|
+
{matchingActions.map((action) => (
|
|
136
|
+
<CommandItem
|
|
137
|
+
key={action.kind}
|
|
138
|
+
value={`__bulk_${action.kind}`}
|
|
139
|
+
onSelect={action.run}
|
|
140
|
+
className="cursor-pointer"
|
|
141
|
+
>
|
|
142
|
+
{action.kind === "select-matching" ? (
|
|
143
|
+
<EyeOffIcon className="w-3 h-3 mr-1.5" />
|
|
144
|
+
) : (
|
|
145
|
+
<EyeIcon className="w-3 h-3 mr-1.5" />
|
|
146
|
+
)}
|
|
147
|
+
{action.kind === "select-matching" ? "Hide" : "Show"}{" "}
|
|
148
|
+
{action.items.length} matching
|
|
149
|
+
</CommandItem>
|
|
150
|
+
))}
|
|
151
|
+
<CommandSeparator />
|
|
152
|
+
</>
|
|
153
|
+
)
|
|
154
|
+
)}
|
|
155
|
+
{list.visibleOptions.map((option, index) => {
|
|
156
|
+
const hidden = list.isChecked(option.value);
|
|
157
|
+
const { dataType } = option.data as {
|
|
158
|
+
dataType: DataType | undefined;
|
|
159
|
+
};
|
|
160
|
+
const isSectionBoundary =
|
|
161
|
+
index === list.pinnedCount &&
|
|
162
|
+
list.pinnedCount > 0 &&
|
|
163
|
+
list.pinnedCount < list.visibleOptions.length;
|
|
164
|
+
return (
|
|
165
|
+
<React.Fragment key={option.value}>
|
|
166
|
+
{isSectionBoundary && <CommandSeparator />}
|
|
167
|
+
<CommandItem
|
|
168
|
+
value={option.value}
|
|
169
|
+
disabled={option.disabled}
|
|
170
|
+
onSelect={() => list.toggle(option.value)}
|
|
171
|
+
className="flex items-center gap-1.5 cursor-pointer"
|
|
172
|
+
>
|
|
173
|
+
{dataType === undefined ? (
|
|
174
|
+
<span>{option.label}</span>
|
|
175
|
+
) : (
|
|
176
|
+
<ColumnName
|
|
177
|
+
columnName={option.label}
|
|
178
|
+
dataType={dataType}
|
|
179
|
+
/>
|
|
180
|
+
)}
|
|
181
|
+
{!option.disabled && (
|
|
182
|
+
<span
|
|
183
|
+
className={cn(
|
|
184
|
+
"ml-auto",
|
|
185
|
+
hidden ? "text-primary" : "text-muted-foreground",
|
|
186
|
+
)}
|
|
187
|
+
>
|
|
188
|
+
{hidden ? (
|
|
189
|
+
<EyeOffIcon className="w-3 h-3" />
|
|
190
|
+
) : (
|
|
191
|
+
<EyeIcon className="w-3 h-3" />
|
|
192
|
+
)}
|
|
193
|
+
</span>
|
|
194
|
+
)}
|
|
195
|
+
</CommandItem>
|
|
196
|
+
</React.Fragment>
|
|
197
|
+
);
|
|
198
|
+
})}
|
|
199
|
+
</CommandList>
|
|
200
|
+
</Command>
|
|
201
|
+
</PopoverContent>
|
|
202
|
+
</Popover>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
@@ -356,6 +356,7 @@ const DataTableInternal = <TData,>({
|
|
|
356
356
|
className={cn(className || "rounded-md border overflow-hidden")}
|
|
357
357
|
>
|
|
358
358
|
<TableTopBar
|
|
359
|
+
table={table}
|
|
359
360
|
enableSearch={enableSearch}
|
|
360
361
|
searchQuery={searchQuery}
|
|
361
362
|
onSearchQueryChange={onSearchQueryChange}
|
|
@@ -413,7 +414,6 @@ const DataTableInternal = <TData,>({
|
|
|
413
414
|
getRowIds={getRowIds}
|
|
414
415
|
showPageSizeSelector={showPageSizeSelector}
|
|
415
416
|
tableLoading={reloading}
|
|
416
|
-
togglePanel={togglePanel}
|
|
417
417
|
/>
|
|
418
418
|
</div>
|
|
419
419
|
</CellSelectionProvider>
|