From 0050f73a7077ab1a7fdb9a6bc3e28398563786b3 Mon Sep 17 00:00:00 2001 From: Thomas Joise Date: Mon, 20 Jul 2026 04:16:28 +0530 Subject: [PATCH] fix(ui): stack record-header actions and scroll wide tables on mobile RecordHeader's action-button cluster crowded/overlapped the client name on narrow phones, and DataTable (Tickets, etc.) ran wider than the viewport, clipping columns with no way to reach them. Add a <=640px media query: .wf-rec wraps so .wf-rec-actions drops to its own full-width row below the avatar/name/meta; desktop layout is untouched outside the query. Wrap DataTable's in a new .wf-table-wrap div that scrolls horizontally on overflow at that breakpoint (nowrap cells so the table can exceed 100% instead of being squeezed illegibly), keeping the page body itself from scrolling sideways. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ui/src/components.tsx | 90 ++++++++++++++++++---------------- packages/ui/src/tokens.css | 15 ++++++ 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/packages/ui/src/components.tsx b/packages/ui/src/components.tsx index c80162a..0c875da 100644 --- a/packages/ui/src/components.tsx +++ b/packages/ui/src/components.tsx @@ -134,52 +134,56 @@ export function DataTable(props: { } return ( -
- - - {props.columns.map((c) => { - const sortable = canSort(c) - const arrow = sort?.key === c.key ? (sort.dir === 'asc' ? ' ▲' : ' ▼') : '' + // Horizontal-scroll boundary: on narrow screens a wide table scrolls inside this box + // instead of pushing the page body wider than the viewport (see .wf-table-wrap CSS). +
+
+ + + {props.columns.map((c) => { + const sortable = canSort(c) + const arrow = sort?.key === c.key ? (sort.dir === 'asc' ? ' ▲' : ' ▼') : '' + return ( + + ) + })} + + + + {ordered.map(({ row, i }) => { + const tone = props.rowTone?.(i) + const clickable = props.onRowClick !== undefined return ( - + props.onRowClick?.(row, i) : undefined} + onKeyDown={clickable + ? (e) => { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); props.onRowClick?.(row, i) } + } + : undefined} + role={clickable ? 'button' : undefined} + tabIndex={clickable ? 0 : undefined} + style={clickable ? { cursor: 'pointer' } : undefined} + > + {props.columns.map((c) => ( + + ))} + ) })} - - - - {ordered.map(({ row, i }) => { - const tone = props.rowTone?.(i) - const clickable = props.onRowClick !== undefined - return ( - props.onRowClick?.(row, i) : undefined} - onKeyDown={clickable - ? (e) => { - if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); props.onRowClick?.(row, i) } - } - : undefined} - role={clickable ? 'button' : undefined} - tabIndex={clickable ? 0 : undefined} - style={clickable ? { cursor: 'pointer' } : undefined} - > - {props.columns.map((c) => ( - - ))} - - ) - })} - -
toggle(c.key) : undefined} + style={sortable ? { cursor: 'pointer', userSelect: 'none', whiteSpace: 'nowrap' } : undefined} + title={sortable ? 'Sort' : undefined}> + {c.label}{arrow} +
toggle(c.key) : undefined} - style={sortable ? { cursor: 'pointer', userSelect: 'none', whiteSpace: 'nowrap' } : undefined} - title={sortable ? 'Sort' : undefined}> - {c.label}{arrow} -
{row[c.key]}
{row[c.key]}
+ + + ) } diff --git a/packages/ui/src/tokens.css b/packages/ui/src/tokens.css index 822785b..24bb41d 100644 --- a/packages/ui/src/tokens.css +++ b/packages/ui/src/tokens.css @@ -461,3 +461,18 @@ button.wf.pill { border-radius: 999px; padding: 8px 18px; } .wf-formgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 12px; } .wf-formgrid .wide { grid-column: 1 / -1; } @media (max-width: 560px) { .wf-formgrid { grid-template-columns: 1fr; } } + +/* ================= mobile (phone) layout ================= */ +@media (max-width: 640px) { + /* Record header: stack the action-button cluster below the avatar/name/meta instead of + crowding them on one row. Desktop layout (outside this query) is untouched. */ + .wf-rec { flex-wrap: wrap; } + .wf-rec-actions { width: 100%; } + + /* Wide tables (e.g. Tickets) scroll horizontally inside their own box rather than + clipping columns or pushing the page body wider than the viewport. `white-space: + nowrap` stops cells from being squeezed/wrapped illegibly, which is what actually + lets the table exceed 100% and become scrollable. */ + .wf-table-wrap { overflow-x: auto; max-width: 100%; -webkit-overflow-scrolling: touch; } + .wf-table-wrap table.wf th, .wf-table-wrap table.wf td { white-space: nowrap; } +}