← Cycles
Design · c93

Table Column Picker

activecreated 2026-09-22· last activity 2026-09-27· mode: assemblage → evolution — the popover-checkbox idiom already exists three times hand-rolled (design framework-picker-ideas Idea 2, qdash /controls, qdash live FilterPopover); this composes it once as two layer modules

A checkbox popover that shows/hides a table's columns — `UxColumnPicker` on a new `UxPopover` primitive — prototyped on the design site, then extracted to q-nuxt-layer and wired into a qdash listing

New in this cycle: UxColumnPicker on a new UxPopover primitive — a checkbox popover above the table that shows/hides DataTable columns, with a locked identity column and a min-one guard; prototyped as design-local components before layer extraction

Why

qdash listing tables are getting wide (Devices carries 7 columns, Sessions and the /inventory/* listings more) and nothing in the ecosystem lets a user hide the columns they don't need. DataTable already takes a columns array, so the missing piece is only the control. The anchored panel it needs has been hand-rolled in three places, which is the signal for a primitive.

Objective

  1. UxPopover — trigger + anchored panel primitive on Headless UI (toggle, Escape, click-outside, focus return owned by Headless; chrome and alignment owned here).
  2. UxColumnPicker — columns: { key, label, locked? }[] in, visible keys out via v-model in catalog order; locked columns checked + disabled; min-one guard; Show all / Done footer; trigger with a N of M badge when anything is hidden.
  3. Both demonstrated on the design site with minimal / overflow / guard / long-label / left-aligned scenarios and the UxTableList v-if pattern, then extracted to the layer, documented, released, and placed on one qdash listing.

Decisions (2026-09-22): target DataTable, popover idiom, trigger above the whole component, v-model only (no persistence), show/hide only (no reorder), locked identity column + min-one, UxPopover as its own primitive.

Scope

  • Design: cycles/c93.table-column-picker/ (initial-prompt.md, plan.md, this record); prototypes app/components/ux/{Popover,ColumnPicker}.vue (auto-import as UxPopover / UxColumnPicker so extraction is a file move); demo app/pages/pages/column-picker.vue + page-registry.ts entry; later _demos/UxPopover.vue, _demos/UxColumnPicker.vue, component-registry.ts.
  • q-nuxt-layer (extraction): components/ux/Popover.vue, components/ux/ColumnPicker.vue; possible DataTable sort fallback when the sorted column is hidden.
  • qcontrol (first consumer, feat/new-pages): crates/qdash/ui/app/components/DevicesOverview.vue.

Key Changes

  • plan (2026-09-22): survey of existing pieces + seven open questions → plan.md; all seven answered the same day.
  • prototype (2026-09-22): UxPopover + UxColumnPicker built as design-local components under app/components/ux/; /pages/column-picker demo with the Devices catalog live table, five scenarios, and the UxTableList pattern. Verified on the live :3000 server via scripts/shot-programs/c93-column-picker.json (closed → open → Owner toggled off: badge 7→6 of 10, column drops); captures public/cycles/design/c93-column-picker{,-full,-open,-toggled}.png. Polish from the shots: locked/last-standing rows dim; list cap raised to max-h-96 so a 10-column catalog doesn't scroll. Not committed — Mark reviews first (push = deploy).
  • component docs (2026-09-22): both prototypes registered in component-registry.ts (UxPopover under Interactive, UxColumnPicker under Table) with _demos/UxPopover.vue + _demos/UxColumnPicker.vue (adminProps cover every prop; minimal / typical / overflow scenarios, guard state) — so they appear on /components now, not only after extraction. source is display-only, so it reads the design-local path with a note.
  • side prop (2026-09-22): Mark's note that the panel covered the very columns being toggled → UxPopover gains side: 'below' | 'above' (passed through UxColumnPicker); the live demo and the DevCanvas default open upward over the title area so the table stays fully visible. Trade-off: 'above' needs headroom — a picker at the very top of a page would push the list off-screen, so it suits tables that sit under a title band (every qdash listing does) and the default stays 'below'.
  • known gap: sorting by a column and then hiding it leaves DataTable unsorted (sort column not found). Candidate rule at extraction: fall back to the first visible sortable column.

Outcome

(open)

Visual record

5 captures — heroes, page slices, and full-page renders. Click to view.

The Devices listing with the picker closed — 7 of 10 columns visible, the badge on the trigger says so; scenarios below: minimal, overflow, guard, long labels, UxTableList
The Devices listing with the picker closed — 7 of 10 columns visible, the badge on the trigger says so; scenarios below: minimal, overflow, guard, long labels, UxTableList open page →
Picker open with side=above — the list rises over the title area, so every column of the Devices table stays visible while toggling; Device locked, Show all in the header, count + Done in the footer
Picker open with side=above — the list rises over the title area, so every column of the Devices table stays visible while toggling; Device locked, Show all in the header, count + Done in the footer open page →
Owner unchecked — the column drops out of the fully visible table while the panel stays open; the badge reads 6 of 10
Owner unchecked — the column drops out of the fully visible table while the panel stays open; the badge reads 6 of 10 open page →
c93 hero
c93 full
open page →

Artifacts

Qpoint Brand Style Guide