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 (
+ | toggle(c.key) : undefined}
+ style={sortable ? { cursor: 'pointer', userSelect: 'none', whiteSpace: 'nowrap' } : undefined}
+ title={sortable ? 'Sort' : undefined}>
+ {c.label}{arrow}
+ |
+ )
+ })}
+
+
+
+ {ordered.map(({ row, i }) => {
+ const tone = props.rowTone?.(i)
+ const clickable = props.onRowClick !== undefined
return (
- toggle(c.key) : undefined}
- style={sortable ? { cursor: 'pointer', userSelect: 'none', whiteSpace: 'nowrap' } : undefined}
- title={sortable ? 'Sort' : undefined}>
- {c.label}{arrow}
- |
+ 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) => (
+ | {row[c.key]} |
+ ))}
+
)
})}
-
-
-
- {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) => (
- | {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; }
+}