Skip to content

Commit ba44512

Browse files
authored
table: Add row_selector option to hide selector column (#2357)
Closes #2351. `TableState::cell_selectable(true)` always renders the leftmost row-selector column. Add a `row_selector(bool)` builder (default `true`) so the column can be hidden while keeping cell selection. When hidden, clicking the already-selected cell again to select row.
1 parent ec4e972 commit ba44512

3 files changed

Lines changed: 64 additions & 5 deletions

File tree

crates/story/src/stories/data_table_story.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,13 @@ impl DataTableStory {
952952
});
953953
}
954954

955+
fn toggle_row_selector(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {
956+
self.table.update(cx, |table, cx| {
957+
table.row_selector = *checked;
958+
cx.notify();
959+
});
960+
}
961+
955962
fn toggle_stripe(&mut self, checked: &bool, _: &mut Window, cx: &mut Context<Self>) {
956963
self.stripe = *checked;
957964
cx.notify();
@@ -1119,6 +1126,12 @@ impl Render for DataTableStory {
11191126
.selected(table.cell_selectable)
11201127
.on_click(cx.listener(Self::toggle_cell_selection)),
11211128
)
1129+
.child(
1130+
Checkbox::new("row-selector")
1131+
.label("Row Selector")
1132+
.selected(table.row_selector)
1133+
.on_click(cx.listener(Self::toggle_row_selector)),
1134+
)
11221135
.child(
11231136
Checkbox::new("fixed")
11241137
.label("Column Fixed")

crates/ui/src/table/data_table.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ impl Default for TableOptions {
6767
/// When cell selection is enabled via [`TableState::cell_selectable()`]:
6868
/// - Click on cells to select them
6969
/// - A row selector column appears on the left for selecting entire rows
70+
/// (use [`TableState::row_selector()`] to hide it)
7071
/// - Keyboard navigation (arrow keys, Tab, Home, End, PageUp, PageDown) works at cell level
7172
/// - Right-click and double-click events are supported
7273
///

crates/ui/src/table/state.rs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,19 @@ pub struct TableState<D: TableDelegate> {
201201
/// When enabled:
202202
/// - Users can click on individual cells to select them
203203
/// - A row selector column appears on the left for selecting entire rows
204+
/// (can be hidden via [`Self::row_selector`])
204205
/// - Keyboard navigation works at the cell level (arrow keys move between cells)
205206
/// - Right-click and double-click events are supported for cells
206207
pub cell_selectable: bool,
208+
/// Whether the row selector column is visible when `cell_selectable` is enabled,
209+
/// default is `true`.
210+
///
211+
/// Set to `false` to hide the narrow leftmost selector column while keeping cell
212+
/// selection — useful when you want to put your own content (e.g. a row index
213+
/// column) on the left. When hidden, clicking the already-selected cell again
214+
/// escalates the selection to the whole row so users can still pick rows; row
215+
/// escalation requires `row_selectable` to be enabled.
216+
pub row_selector: bool,
207217
/// Whether the table can sort.
208218
pub sortable: bool,
209219
/// Whether the table can resize columns.
@@ -261,6 +271,7 @@ where
261271
col_selectable: true,
262272
row_selectable: true,
263273
cell_selectable: false,
274+
row_selector: true,
264275
sortable: true,
265276
col_movable: true,
266277
col_resizable: true,
@@ -323,7 +334,7 @@ where
323334
///
324335
/// When enabled:
325336
/// - Individual cells become selectable by clicking
326-
/// - A row selector column appears on the left side
337+
/// - A row selector column appears on the left side (can be hidden via [`Self::row_selector`])
327338
/// - Keyboard navigation operates at the cell level
328339
/// - Cell-specific events (SelectCell, DoubleClickedCell, RightClickedCell) are emitted
329340
///
@@ -341,6 +352,21 @@ where
341352
self
342353
}
343354

355+
/// Set whether the row selector column is shown, default is `true`.
356+
///
357+
/// Only effective when `cell_selectable` is `true` — otherwise the row selector
358+
/// column is never rendered. Hide it when you want to use the leftmost column
359+
/// for your own content (e.g. a row index column).
360+
///
361+
/// When hidden, the first click on a cell selects the cell; clicking the
362+
/// already-selected cell again escalates to selecting the whole row, so users
363+
/// can still pick rows without the dedicated selector column. The row escalation
364+
/// requires `row_selectable` to be enabled.
365+
pub fn row_selector(mut self, row_selector: bool) -> Self {
366+
self.row_selector = row_selector;
367+
self
368+
}
369+
344370
/// When we update columns or rows, we need to refresh the table.
345371
pub fn refresh(&mut self, cx: &mut Context<Self>) {
346372
self.prepare_col_groups(cx);
@@ -663,9 +689,28 @@ where
663689
}
664690

665691
cx.stop_propagation();
692+
693+
let is_double_click = e.click_count() == 2;
694+
695+
// When the row selector column is hidden, a single click on the
696+
// already-selected cell escalates the selection to the entire row —
697+
// giving users a way to pick rows without the dedicated selector column.
698+
// Double-clicks are passed through to `DoubleClickedCell` and never
699+
// trigger the escalation.
700+
let is_reselect = self.selection_mode.is_cell()
701+
&& self.selected_cell == Some((row_ix, col_ix));
702+
let should_escalate_to_row = !self.row_selector
703+
&& self.row_selectable
704+
&& is_reselect
705+
&& !is_double_click;
706+
if should_escalate_to_row {
707+
self.set_selected_row(row_ix, cx);
708+
return;
709+
}
710+
666711
self.set_selected_cell(row_ix, col_ix, cx);
667712

668-
if e.click_count() == 2 {
713+
if is_double_click {
669714
cx.emit(TableEvent::DoubleClickedCell(row_ix, col_ix));
670715
}
671716
}
@@ -1481,7 +1526,7 @@ where
14811526
.bg(cx.theme().table_head)
14821527
.text_color(cx.theme().table_head_foreground)
14831528
.refine_style(&style)
1484-
.when(self.cell_selectable, |this| {
1529+
.when(self.cell_selectable && self.row_selector, |this| {
14851530
this.child(self.render_row_selector_cell(0, true, cx))
14861531
})
14871532
.when(left_columns_count > 0, |this| {
@@ -1664,7 +1709,7 @@ where
16641709
this.bg(cx.theme().table_hover)
16651710
}
16661711
})
1667-
.when(self.cell_selectable, |this| {
1712+
.when(self.cell_selectable && self.row_selector, |this| {
16681713
this.child(self.render_row_selector_cell(row_ix, false, cx))
16691714
})
16701715
.when(left_columns_count > 0, |this| {
@@ -1931,7 +1976,7 @@ where
19311976
.border_b_1()
19321977
.border_color(cx.theme().table_row_border)
19331978
.when(is_stripe_row, |this| this.bg(cx.theme().table_even))
1934-
.when(self.cell_selectable, |this| {
1979+
.when(self.cell_selectable && self.row_selector, |this| {
19351980
// Render empty row selector cell for fake rows
19361981
this.child(
19371982
div()

0 commit comments

Comments
 (0)