Skip to content

Commit c2c097e

Browse files
committed
release: @datasketch/monkeytab@0.3.0
1 parent d33de68 commit c2c097e

14 files changed

Lines changed: 765 additions & 35 deletions

File tree

BROWSER.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,29 @@ function App() {
6060
| `sortDirection` | `'asc' \| 'desc' \| null` || Controlled sort direction |
6161
| `onSortChange` | `(fieldId, direction) => void` || Fires when user changes sort. Use for server-side sorting. |
6262

63+
### Selection Actions
64+
65+
| Prop | Type | Default | Description |
66+
|---|---|---|---|
67+
| `selectionActions` | `(ids: string[], clear: () => void) => ReactNode` || Render prop for custom bulk actions when rows are selected |
68+
69+
### Grouping
70+
71+
| Prop | Type | Default | Description |
72+
|---|---|---|---|
73+
| `groupBy` | `string \| null` || Field ID to group rows by. Omit for no grouping |
74+
| `groupCollapsed` | `boolean` | `false` | Start groups collapsed |
75+
| `onGroupByChange` | `(fieldId: string \| null) => void` || Fires when user changes grouping via column menu |
76+
| `groupOrder` | `'auto' \| 'asc' \| 'desc' \| 'count-asc' \| 'count-desc' \| string[]` | `'auto'` | Group display order. `'auto'` uses SingleSelect option order, or alphabetical |
77+
78+
### Row Coloring
79+
80+
| Prop | Type | Default | Description |
81+
|---|---|---|---|
82+
| `colorBy` | `string \| null` || Field ID whose value tints each row (SingleSelect colors, Boolean green/red) |
83+
| `onColorByChange` | `(fieldId: string \| null) => void` || Fires when user changes coloring via column menu |
84+
| `colorByMap` | `Record<string, string>` || Optional per-value color overrides |
85+
6386
### Pagination
6487

6588
| Prop | Type | Default | Description |
@@ -77,6 +100,9 @@ function App() {
77100
|---|---|---|---|
78101
| `height` | `'auto' \| number \| string` | `'100%'` | `'auto'` fits content, number = fixed pixels (ghost rows on), string = CSS |
79102
| `maxHeight` | `number` || Max height in px. Caps `'auto'` growth or fluid containers |
103+
| `columnFit` | `'auto' \| 'fill' \| 'fixed'` | `'auto'` | `'auto'` fits content, `'fill'` distributes container width, `'fixed'` uses 180px per column |
104+
| `autoFitMin` | `number` | `60` | Minimum column width in `'auto'` mode |
105+
| `autoFitMax` | `number` | `320` | Maximum column width in `'auto'` mode |
80106
| `rowHeight` | `RowHeightOption` | `'medium'` | `'short'` \| `'medium'` \| `'tall'` \| `'extra-tall'` \| `'fit'` |
81107
| `showRowNumbers` | `boolean` | `false` | Show row number column |
82108
| `compactMode` | `boolean` | `false` | Denser layout — smaller fonts, tighter padding |

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66

77
## [Unreleased]
88

9+
## [0.3.0] — 2026-04-16
10+
11+
### New
12+
- **`selectionActions` render prop** — Custom bulk-action buttons in the toolbar when rows are selected.
13+
- **Row grouping**`groupBy` / `groupCollapsed` / `onGroupByChange` with collapsible headers, row counts, and select-all-in-group checkboxes. Context menu adds "Group by this column".
14+
- **`groupOrder` prop** — Control group display order: `'auto'` (default, uses SingleSelect option order), `'asc'`, `'desc'`, `'count-asc'`, `'count-desc'`, or explicit `string[]`.
15+
- **Row coloring**`colorBy` / `onColorByChange` / `colorByMap` tints rows by a field value. Uses SingleSelect option colors and Boolean defaults (pastel green/red).
16+
- **`columnFit` prop**`'auto'` (new default, sizes to content), `'fill'` (distributes container width), `'fixed'` (180px per column).
17+
- **`autoFitMin` / `autoFitMax`** — Tune auto column-width bounds (defaults 60 / 320).
18+
19+
### Changed
20+
- Default column width is now content-aware (`columnFit="auto"`) instead of fixed 180px.
21+
- Cell overflow fade inherits the row's background color instead of always fading to white.
22+
- Column header type icon auto-hides when the column is too narrow to show it with the label.
23+
24+
### Fixed
25+
- Horizontal overflow when tables are inside flex parents (side-by-side panels).
26+
- Email / URL / Phone cells now fade like other types instead of showing `...` ellipsis.
27+
928
## [0.2.1] — 2026-04-15
1029

1130
### New
@@ -75,6 +94,7 @@ First public release.
7594
- `onUpload` prop — bring your own file upload (S3, Cloudinary, etc.)
7695
- Drag-and-drop and paste support for Image cells
7796

78-
[Unreleased]: https://github.com/datasketch/monkeytab/compare/v0.2.1...HEAD
97+
[Unreleased]: https://github.com/datasketch/monkeytab/compare/v0.3.0...HEAD
98+
[0.3.0]: https://github.com/datasketch/monkeytab/compare/v0.2.1...v0.3.0
7999
[0.2.1]: https://github.com/datasketch/monkeytab/compare/v0.2.0...v0.2.1
80100
[0.2.0]: https://github.com/datasketch/monkeytab/releases/tag/v0.2.0

examples/browser-standalone/main.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,8 @@ function App() {
581581
const [sortBy, setSortBy] = useState<string | null>(null);
582582
const [sortDirection, setSortDirection] = useState<'asc' | 'desc' | null>(null);
583583
const [ghostGrid, setGhostGrid] = useState(true);
584+
const [groupBy, setGroupBy] = useState<string | null>(null);
585+
const [colorBy, setColorBy] = useState<string | null>(null);
584586

585587
const locale = language === 'es' ? 'es-CO' : 'en-US';
586588

@@ -630,6 +632,33 @@ function App() {
630632
<input type="checkbox" checked={ghostGrid} onChange={(e) => setGhostGrid(e.target.checked)} />
631633
Ghost grid
632634
</label>
635+
<label style={{ fontSize: '14px', display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
636+
Group:
637+
<select
638+
value={groupBy ?? ''}
639+
onChange={(e) => setGroupBy(e.target.value || null)}
640+
style={{ padding: '2px 6px', fontSize: '13px', borderRadius: '4px', border: '1px solid #d1d5db' }}
641+
>
642+
<option value="">None</option>
643+
<option value="Role">Role</option>
644+
<option value="Active">Active</option>
645+
</select>
646+
</label>
647+
<label style={{ fontSize: '14px', display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
648+
Color:
649+
<select
650+
value={colorBy ?? ''}
651+
onChange={(e) => setColorBy(e.target.value || null)}
652+
style={{ padding: '2px 6px', fontSize: '13px', borderRadius: '4px', border: '1px solid #d1d5db' }}
653+
>
654+
<option value="">None</option>
655+
{COLUMNS
656+
.filter((c) => c.type === 'SingleSelect' || c.type === 'MultiSelect' || c.type === 'Boolean')
657+
.map((c) => (
658+
<option key={c.id} value={c.id}>{c.id}</option>
659+
))}
660+
</select>
661+
</label>
633662
{tab === 'editable' && (
634663
<>
635664
<span style={{ fontSize: '14px', color: '#6b7280' }}>
@@ -677,6 +706,27 @@ function App() {
677706
await new Promise(r => setTimeout(r, 1000));
678707
return URL.createObjectURL(file);
679708
}}
709+
groupBy={groupBy}
710+
onGroupByChange={setGroupBy}
711+
colorBy={colorBy}
712+
onColorByChange={setColorBy}
713+
selectionActions={(ids, clear) => (
714+
<button
715+
onClick={() => { alert(`Processing ${ids.length} rows: ${ids.join(', ')}`); clear(); }}
716+
style={{
717+
padding: '4px 8px',
718+
background: '#dbeafe',
719+
color: '#1d4ed8',
720+
border: 'none',
721+
borderRadius: '4px',
722+
fontSize: '12px',
723+
fontWeight: 500,
724+
cursor: 'pointer',
725+
}}
726+
>
727+
Process
728+
</button>
729+
)}
680730
/>
681731
</div>
682732
{log.length > 0 && (

examples/browser-standalone/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "browser-standalone",
3-
"version": "0.2.1",
3+
"version": "0.3.0",
44
"private": true,
55
"type": "module",
66
"scripts": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@datasketch/monkeytab",
3-
"version": "0.2.1",
3+
"version": "0.3.0",
44
"description": "Embeddable, editable React table component",
55
"keywords": [
66
"react",

src/browser/MonkeyTable.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,33 @@ export interface MonkeyTableProps {
8585
/** Called when a single cell value changes — fires before the mutation with rowId, fieldId, new and old values */
8686
onCellChange?: (rowId: string, fieldId: string, newValue: Value, oldValue: Value) => void;
8787

88+
/** Render prop for custom bulk actions shown when rows are selected.
89+
* Receives the selected row IDs and a function to clear the selection. */
90+
selectionActions?: (selectedIds: string[], clearSelection: () => void) => React.ReactNode;
91+
92+
// ── Grouping ──────────────────────────────────────────────────────────────
93+
/** Field ID to group rows by (single column). Omit or null for no grouping. */
94+
groupBy?: string | null;
95+
/** Default collapsed state for groups (default: false = expanded) */
96+
groupCollapsed?: boolean;
97+
/** Called when user changes grouping via column header menu */
98+
onGroupByChange?: (fieldId: string | null) => void;
99+
/** Group display order.
100+
* - `'auto'` (default): SingleSelect/MultiSelect use their option order; other fields sort alphabetically.
101+
* - `'asc'` / `'desc'`: alphabetical / reverse-alphabetical by group value.
102+
* - `'count-desc'` / `'count-asc'`: by row count within each group.
103+
* - `string[]`: explicit order — list of group values in the desired sequence. */
104+
groupOrder?: 'auto' | 'asc' | 'desc' | 'count-asc' | 'count-desc' | string[];
105+
106+
// ── Row Coloring ──────────────────────────────────────────────────────────
107+
/** Field ID whose value determines each row's background tint. Uses the field's
108+
* existing color mapping (SingleSelect option colors, Boolean true/false). */
109+
colorBy?: string | null;
110+
/** Called when user changes coloring via column header menu */
111+
onColorByChange?: (fieldId: string | null) => void;
112+
/** Optional per-value color overrides, keyed by stringified value. */
113+
colorByMap?: Record<string, string>;
114+
88115
// ── Sorting ───────────────────────────────────────────────────────────────
89116
/** Controlled sort field — the column currently sorted */
90117
sortBy?: string | null;
@@ -119,6 +146,15 @@ export interface MonkeyTableProps {
119146
/** Maximum height in pixels. Useful with `height="auto"` to cap growth, or with
120147
* `height="100%"` to limit fluid containers. Ignored when `height` is a fixed number. */
121148
maxHeight?: number;
149+
/** Column sizing strategy.
150+
* - `'auto'` (default): each column sized to fit its content (header + data). Table may scroll horizontally.
151+
* - `'fill'`: distribute available container width evenly across columns (no horizontal overflow).
152+
* - `'fixed'`: each column starts at 180px (or its `width` prop). */
153+
columnFit?: 'auto' | 'fill' | 'fixed';
154+
/** Minimum column width in `'auto'` mode (default: 60). Per-column `minWidth` overrides this. */
155+
autoFitMin?: number;
156+
/** Maximum column width in `'auto'` mode (default: 320). Per-column `maxWidth` overrides this. */
157+
autoFitMax?: number;
122158
/** Row height preset (default: 'medium') */
123159
rowHeight?: RowHeightOption;
124160
/** Show row number column (default: false) */
@@ -214,6 +250,16 @@ export function MonkeyTable({
214250
selectedRowIds,
215251
onRowClick,
216252
onCellChange,
253+
selectionActions,
254+
// Grouping
255+
groupBy,
256+
groupCollapsed,
257+
onGroupByChange,
258+
groupOrder,
259+
// Row coloring
260+
colorBy,
261+
onColorByChange,
262+
colorByMap,
217263
// Sorting
218264
sortBy,
219265
sortDirection,
@@ -230,6 +276,9 @@ export function MonkeyTable({
230276
height = '100%',
231277
maxHeight,
232278
rowHeight,
279+
columnFit = 'auto',
280+
autoFitMin,
281+
autoFitMax,
233282
showRowNumbers,
234283
compactMode,
235284
// Permissions
@@ -680,6 +729,7 @@ export function MonkeyTable({
680729
display: 'flex',
681730
flexDirection: 'column',
682731
overflow: 'hidden',
732+
minWidth: 0, // Allow shrinking inside flex parents (prevents horizontal page overflow)
683733
};
684734

685735
return (
@@ -712,8 +762,19 @@ export function MonkeyTable({
712762
pageSize={pageSize}
713763
onPageChange={onPageChange}
714764
ghostGrid={resolvedGhostGrid}
765+
columnFit={columnFit}
766+
autoFitMin={autoFitMin}
767+
autoFitMax={autoFitMax}
715768
paginationMode={paginationMode}
716769
paginationLoading={paginationLoading}
770+
selectionActions={selectionActions}
771+
groupBy={groupBy}
772+
groupCollapsed={groupCollapsed}
773+
onGroupByChange={onGroupByChange}
774+
groupOrder={groupOrder}
775+
colorBy={colorBy}
776+
onColorByChange={onColorByChange}
777+
colorByMap={colorByMap}
717778
/>
718779
</GridStoreProvider>
719780
</I18nProvider>

src/browser/TableView.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,31 @@ export interface TableViewProps {
9292
paginationLoading?: boolean;
9393
/** Show faint ghost rows/columns to fill the viewport, spreadsheet-style */
9494
ghostGrid?: boolean | { rows?: number; columns?: number };
95+
/** Column sizing strategy — 'auto' (default) fits content, 'fill' distributes width, 'fixed' uses 180px */
96+
columnFit?: 'auto' | 'fill' | 'fixed';
97+
/** Min column width in 'auto' mode (default: 60) */
98+
autoFitMin?: number;
99+
/** Max column width in 'auto' mode (default: 320) */
100+
autoFitMax?: number;
101+
/** Render prop for custom bulk actions when rows are selected */
102+
selectionActions?: (selectedIds: string[], clearSelection: () => void) => React.ReactNode;
103+
/** Field ID to group rows by */
104+
groupBy?: string | null;
105+
/** Default collapsed state for groups */
106+
groupCollapsed?: boolean;
107+
/** Called when user changes grouping via column header menu */
108+
onGroupByChange?: (fieldId: string | null) => void;
109+
/** Group display order */
110+
groupOrder?: 'auto' | 'asc' | 'desc' | 'count-asc' | 'count-desc' | string[];
111+
/** Field ID whose value determines each row's background tint */
112+
colorBy?: string | null;
113+
/** Called when user changes coloring via column header menu */
114+
onColorByChange?: (fieldId: string | null) => void;
115+
/** Optional per-value color overrides */
116+
colorByMap?: Record<string, string>;
95117
}
96118

97-
export function TableView({ baseId, tableId, onNavigate, onNavigateHome, onSelectionChange, selectedRowIds, onRowClick, customRenderers, customIcons, columnEditable, columnWidth, columnMinWidth, columnMaxWidth, columnSortable, columnAlign, onUpload, onCellChange, onSortChange, sortBy, sortDirection, totalRows, page = 1, pageSize = 500, onPageChange, paginationMode = 'simple', paginationLoading, ghostGrid }: TableViewProps) {
119+
export function TableView({ baseId, tableId, onNavigate, onNavigateHome, onSelectionChange, selectedRowIds, onRowClick, customRenderers, customIcons, columnEditable, columnWidth, columnMinWidth, columnMaxWidth, columnSortable, columnAlign, onUpload, onCellChange, onSortChange, sortBy, sortDirection, totalRows, page = 1, pageSize = 500, onPageChange, paginationMode = 'simple', paginationLoading, ghostGrid, columnFit, autoFitMin, autoFitMax, selectionActions, groupBy, groupCollapsed, onGroupByChange, groupOrder, colorBy, onColorByChange, colorByMap }: TableViewProps) {
98120
const { t } = useI18n();
99121
const client = useClient();
100122
const { data: base } = useBase(baseId);
@@ -434,7 +456,10 @@ export function TableView({ baseId, tableId, onNavigate, onNavigateHome, onSelec
434456
showRowNumbers={settings.showRowNumbersControl ? displayShowRowNumbers : undefined}
435457
onShowRowNumbersChange={settings.showRowNumbersControl ? handleShowRowNumbersChange : undefined}
436458
selectedCount={selectedRows.size}
459+
selectedRowIds={Array.from(selectedRows)}
437460
onDeleteSelected={!isReadOnly ? handleDeleteSelected : undefined}
461+
onClearSelection={() => { setSelectedRows(new Set()); onSelectionChange?.([]); }}
462+
selectionActions={selectionActions}
438463
totalRecords={rows.length}
439464
searchQuery={settings.showSearch ? searchQuery : undefined}
440465
onSearchChange={settings.showSearch ? setSearchQuery : undefined}
@@ -492,6 +517,16 @@ export function TableView({ baseId, tableId, onNavigate, onNavigateHome, onSelec
492517
onUpload={onUpload}
493518
loading={isFetching || rowsLoading}
494519
ghostGrid={ghostGrid}
520+
columnFit={columnFit}
521+
autoFitMin={autoFitMin}
522+
autoFitMax={autoFitMax}
523+
groupBy={groupBy}
524+
groupCollapsed={groupCollapsed}
525+
onGroupByChange={onGroupByChange}
526+
groupOrder={groupOrder}
527+
colorBy={colorBy}
528+
onColorByChange={onColorByChange}
529+
colorByMap={colorByMap}
495530
/>
496531
</div>
497532

0 commit comments

Comments
 (0)