@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.
@@ -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>