Skip to content

feat(ui): make components responsive across full breakpoint scale#25

Merged
pachev merged 1 commit into
mainfrom
feat/responsive-ui-and-showcase
May 23, 2026
Merged

feat(ui): make components responsive across full breakpoint scale#25
pachev merged 1 commit into
mainfrom
feat/responsive-ui-and-showcase

Conversation

@pachev
Copy link
Copy Markdown
Owner

@pachev pachev commented May 23, 2026

Closes #19.

What changed

The ui_* component suite was built desktop-first. Tablets and phones got truncated text, overflowing tables, and a sidebar that ate ~224px of viewport with no way to dismiss it. This PR tunes every shared component across Tailwind's full sm/md/lg/xl/2xl scale, adds a daisyUI drawer for the sidebar below lg (so iPad portrait works too), and ships a dev-only /dev/ui showcase with a viewport-width toggle to make future responsive work easier to verify.

Component-level fixes

  • ui_sidebar: now hidden lg:flex; below lg it collapses and a daisyUI drawer (ui_mobile_drawer) takes over, surfaced via a hamburger header in Layouts.app. The drawer reuses the same nav slot through extracted sidebar_brand / sidebar_nav / sidebar_footer private components.
  • ui_table: action bar wraps, pagination stacks flex-col sm:flex-row, numbered page buttons hidden on xs in favor of "Page X of Y".
  • ui_tabs: overflow-x-auto whitespace-nowrap so 4+ tabs scroll instead of wrapping silently.
  • ui_page_header: stacks actions below title on xs; action buttons become full-width.
  • ui_card header: flex-col sm:flex-row so title + wide actions don't cramp.
  • ui_modal: max-h-[calc(100vh-2rem)] + internal overflow-y-auto body; tall content scrolls inside the panel.
  • ui_kv_table: rows stack label-above-value on xs, side-by-side from sm.
  • ui_release_card: metric strip flex-col sm:flex-row.
  • ui_audit_row: timestamp drops below the body on xs, indented to align.
  • ui_card_title: rewritten — the old flex-1 spacer cramped title and meta when the card cell was narrow. Now flex flex-wrap with ml-auto for the meta badge so each can take the space it needs.

Chart resize bug

ui_chart_card + line_chart were not shrinking when the viewport got smaller. Root cause: the canvas's intrinsic width was pushing the grid cell open, creating a circular ResizeObserver dependency — Chart.js never saw a shrink.

Fix: min-w-0 + overflow-hidden on the card/wrapper, !w-full !max-w-full on the canvas to defeat Chart.js's inline width: NNNpx, plus a window.resize fallback in the hook that calls chart.resize() defensively.

Dev showcase

New LiveView at /dev/ui (compiled out in :prod via Mix.env() guard in the router). Renders every ui_* component grouped by family with a top-bar width toggle: 375 / 414 / 768 / 1024 / 1440 / full. Used it to do the audit pass for this PR; should make future responsive work much faster.

Tests

  • test/mast_web/components/ui/responsive_test.exs — 13 unit assertions for class hooks on each component, including a regression test for the ui_card_title flex-1 spacer fix.
  • test/mast_web/live/dev_ui_live_test.exs — smoke test for the /dev/ui route and width toggle.
  • Full suite: 277/277 passing, mix precommit clean (format, deps.unlock --unused, credo --strict, test).

Verified at

Walked the dashboard, server detail, and /dev/ui at 500px (Chrome's min — phone-ish), 768px (iPad portrait), 1024px (iPad landscape / small laptop), and 1440px (desktop). No horizontal overflow, no clipped content, sidebar drawer works, charts re-render correctly on resize.

Out of scope

  • No new tests for individual content pages (Dashboard, Audit, etc.) — they consume the shared components and inherit the fixes.
  • No changes to buttons.ex / feedback.ex / forms.ex — they were fine as-is per the issue audit.

The ui_* component suite was built desktop-first with no responsive
breakpoints. Tablets and phones got truncated text, overflowing tables,
and a sidebar that ate ~224px of viewport with no way to dismiss it.

Component fixes:
- Sidebar collapses to a daisyUI drawer + hamburger header below lg
  (1024px), so iPad portrait gets the drawer too. The drawer reuses
  the same nav slot via extracted sidebar_brand/sidebar_nav/sidebar_footer
  private components.
- ui_table: action_bar wraps; pagination stacks flex-col sm:flex-row;
  numbered pages hidden on xs, replaced by "Page X of Y".
- ui_tabs: overflow-x-auto whitespace-nowrap so 4+ tabs scroll instead
  of wrapping silently.
- ui_page_header: stacks actions below title on xs; action buttons go
  full-width.
- ui_card header: flex-col sm:flex-row so title and wide actions don't
  cramp on narrow.
- ui_modal: max-h-[calc(100vh-2rem)] with overflow-y-auto body region,
  so tall content scrolls inside the panel instead of the viewport.
- ui_kv_table: rows stack label-above-value on xs, side-by-side from sm.
- ui_release_card: metric strip flex-col sm:flex-row.
- ui_audit_row: timestamp stacks below body on xs, indented to align.
- ui_card_title: rewritten to flex-wrap with ml-auto for the meta
  badge — the old flex-1 spacer cramped both title and meta when the
  card cell was narrow.
- ui_chart_card + line_chart: added min-w-0 + overflow-hidden + a
  CSS-important w-full override on the canvas, plus a window.resize
  fallback in the hook. Chart.js was failing to shrink because the
  canvas's intrinsic width pushed the grid cell open, creating a
  circular ResizeObserver dependency.

New dev-only showcase at /dev/ui:
- DevUiLive renders every ui_* component grouped by family.
- Viewport-width toggle: 375 / 414 / 768 / 1024 / 1440 / full.
- Compiled out in :prod via Mix.env() guard in the router.

Tests:
- responsive_test.exs: 13 assertions for class hooks on each component.
- dev_ui_live_test.exs: smoke test for /dev/ui route + width toggle.
- 277/277 passing.

Closes #19
@pachev pachev merged commit cca1da2 into main May 23, 2026
1 check passed
pachev added a commit that referenced this pull request May 23, 2026
Closes #26.

Adopt ui_table where raw <table> still lived in page code (settings
SSH keys, application top-processes), add sm breakpoints to
page-level 4-stat grids that were stuck at grid-cols-2 (dashboard,
server overview, app detail), and adopt ui_kv_table for the
server-settings Connection block so labels stack on xs instead of
cramping at grid-cols-2.

Rework two anti-patterns that still lived in page code after the
ui_* library was fixed in #25: the flex-1 spacer in the System
snapshot header (now ml-auto on the badge) and the w-44 shrink-0
label column in snapshot_row (now flex-col sm:flex-row).

Also flatten the Updates tab so it matches the pen design (PFTPt)
and the Releases tab pattern: drop the outer ui_card wrapper, lift
'Available Updates' into a page-level section header, let ui_table
be the only card surface. Empty states stay wrapped in ui_card so
ui_empty has a frame.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make custom UI components mobile-friendly

1 participant