fix(filter-bar): responsive layout broken on narrow viewports

The two-zone layout (left: filters, right: sort+save) hardcoded
flexWrap: "nowrap" on the outer container so the right zone would never
get pushed below the left on desktop. On a phone that's the same
two-column squeeze with no room for either side: the search box and
right-hand controls got clipped/overlapped instead of stacking.

- outer container: flex-col below the `sm` breakpoint (stacks to two full-
  width rows), flex-row + nowrap at `sm` and up (the original two-zone,
  right-pinned behavior)
- right zone swapped from <Group wrap="nowrap"> to a plain div with
  Tailwind classes — Group's `wrap` prop sets an inline flex-wrap style,
  which always wins over a `sm:flex-nowrap` class regardless of
  breakpoint, so the old responsive attempt could never have worked
- search input's hardcoded minWidth: 220 (wider than a lot of phones)
  dropped to a shrinkable 160
- saved-view card grid: base 2 columns forced text-clipping card overflow
  on a phone; base is now 1, growing to 2+ only once there's room
This commit is contained in:
Nathnael
2026-08-14 13:51:48 +00:00
parent bec9575311
commit 524f9aab56
2 changed files with 24 additions and 11 deletions

View File

@@ -65,13 +65,17 @@ export function FilterBar({
/>
)}
{/* Two independent flex zones, not one big wrapping Group: the left side
(search + pills + more filters + clear) wraps to as many lines as it
needs; the right side (sort) stays put on the first line — `nowrap` +
`flexShrink: 0` on the right zone stop it from ever getting pushed
down when the left side overflows. */}
<div style={{ display: "flex", alignItems: "flex-start", gap: 8, flexWrap: "nowrap" }}>
<Group gap="xs" wrap="wrap" align="center" style={{ flex: 1, minWidth: 0 }}>
{/*
Two independent zones on wide screens — left (search + pills + more
filters + clear) wraps to as many lines as it needs, right (sort +
save) stays pinned on the first line via `sm:flex-nowrap` +
`sm:shrink-0`. `nowrap` unconditionally (the old inline style) forced
that same two-column layout on a phone too: neither zone had room and
both got squeezed/clipped. Below the `sm` breakpoint this stacks to a
single column instead — full-width left row, full-width right row.
*/}
<div className="flex flex-col sm:flex-row sm:flex-nowrap items-start gap-2">
<Group gap="xs" wrap="wrap" align="center" className="flex-1 min-w-0 w-full">
{showSearch && (
<TextInput
placeholder={searchPlaceholder}
@@ -80,7 +84,7 @@ export function FilterBar({
onChange={(e) => controls.setSearchText(e.currentTarget.value)}
size="xs"
radius="lg"
style={{ minWidth: 220 }}
style={{ minWidth: 160, flex: "1 1 160px" }}
/>
)}
@@ -119,7 +123,13 @@ export function FilterBar({
{/* Sorting is a different kind of control (view order, not scope) —
cut off from the filter pills by a vertical divider and pinned to
the right, independent of how the left side wraps. */}
<Group gap="xs" wrap="nowrap" style={{ flexShrink: 0 }}>
{/*
Plain div, not <Group>: Group's `wrap` prop sets an inline
flex-wrap style, which always beats a Tailwind class regardless of
breakpoint — `sm:flex-nowrap` would never win against `wrap="wrap"`.
Wrap on mobile (own row, room is tight), pinned nowrap from `sm` up.
*/}
<div className="flex flex-wrap sm:flex-nowrap items-center gap-2 shrink-0">
{children}
{sortOptions && sortOptions.length > 0 && (
<>
@@ -133,7 +143,7 @@ export function FilterBar({
<SaveViewButton defs={defs} query={activeQuery} onSave={savedViews.save} />
</>
)}
</Group>
</div>
</div>
</div>
);

View File

@@ -21,7 +21,10 @@ export function SavedViewCards({ defs, views, activeQuery, applyQueryString, onR
if (views.length === 0) return null;
return (
<SimpleGrid cols={{ base: 2, sm: 3, md: 4, lg: 5 }} spacing="xs" mb="sm">
// base: 1 — a phone-width viewport forcing 2 columns is what clipped
// card text and overflowed the row; one full-width card per row until
// there's actually room for more.
<SimpleGrid cols={{ base: 1, xs: 2, sm: 3, md: 4, lg: 5 }} spacing="xs" mb="sm">
{views.map((v) => {
const active = v.query === activeQuery;
const label = describeQuery(defs, v.query);