fix: mobile layout — prevent horizontal overflow on notifications page

- AuthGuard: switch outer shell from h-dvh to fixed inset-0 so the
  container always matches the visual viewport regardless of Android
  Chrome address-bar state; add min-w-0 to content column so flex
  children (e.g. long ntfy URL input) cannot force the column wider
  than the viewport
- Notifications page: add overflow-x-auto + shrink-0 to both tab bars
  so button overflow scrolls within the bar instead of escaping to the
  page; add min-w-0 to all inline label/hint div pairs in
  ModeFilterSection and Discovery so they shrink correctly in flex
  layout; add break-all to bill title line-clamp paragraph

Authored by: Jack Levy
This commit is contained in:
Jack Levy
2026-03-15 21:09:23 -04:00
parent 761ffa85d9
commit 7ff75f9a00
2 changed files with 14 additions and 14 deletions

View File

@@ -126,21 +126,21 @@ function ModeFilterSection({
<input type="checkbox" checked={!!filters["new_document"]}
onChange={(e) => onChange({ ...filters, new_document: e.target.checked })}
className="mt-0.5 rounded" />
<div><span className="text-sm">New bill text</span><span className="text-xs text-muted-foreground ml-2">The full text of the bill is published</span></div>
<div className="min-w-0"><span className="text-sm">New bill text</span><span className="text-xs text-muted-foreground ml-2">The full text of the bill is published</span></div>
</label>
<label className="flex items-start gap-3 py-1.5 cursor-pointer">
<input type="checkbox" checked={!!filters["new_amendment"]}
onChange={(e) => onChange({ ...filters, new_amendment: e.target.checked })}
className="mt-0.5 rounded" />
<div><span className="text-sm">Amendment filed</span><span className="text-xs text-muted-foreground ml-2">An amendment is filed against the bill</span></div>
<div className="min-w-0"><span className="text-sm">Amendment filed</span><span className="text-xs text-muted-foreground ml-2">An amendment is filed against the bill</span></div>
</label>
<div className="py-1.5">
<label className="flex items-start gap-3 cursor-pointer">
<input ref={milestoneCheckRef} type="checkbox" checked={milestoneState === "on"}
onChange={toggleMilestones} className="mt-0.5 rounded" />
<div><span className="text-sm font-medium">Milestones</span><span className="text-xs text-muted-foreground ml-2">Select all milestone types</span></div>
<div className="min-w-0"><span className="text-sm font-medium">Milestones</span><span className="text-xs text-muted-foreground ml-2">Select all milestone types</span></div>
</label>
<div className="ml-6 mt-1 space-y-0.5">
{(["vote", "presidential", "committee_report", "calendar", "procedural"] as const).map((k) => {
@@ -150,7 +150,7 @@ function ModeFilterSection({
<input type="checkbox" checked={!!filters[k]}
onChange={(e) => onChange({ ...filters, [k]: e.target.checked })}
className="mt-0.5 rounded" />
<div><span className="text-sm">{row.label}</span><span className="text-xs text-muted-foreground ml-2">{row.hint}</span></div>
<div className="min-w-0"><span className="text-sm">{row.label}</span><span className="text-xs text-muted-foreground ml-2">{row.hint}</span></div>
</label>
);
})}
@@ -484,7 +484,7 @@ export default function NotificationsPage() {
</div>
{/* Channel tab bar */}
<div className="flex gap-0 border-b border-border -mx-6 px-6">
<div className="flex gap-0 border-b border-border -mx-6 px-6 overflow-x-auto">
{([
{ key: "ntfy", label: "ntfy", icon: Bell, active: settings?.ntfy_enabled },
{ key: "email", label: "Email", icon: Mail, active: settings?.email_enabled },
@@ -494,7 +494,7 @@ export default function NotificationsPage() {
<button
key={key}
onClick={() => setActiveChannelTab(key)}
className={`flex items-center gap-1.5 px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
className={`shrink-0 flex items-center gap-1.5 px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
activeChannelTab === key
? "border-primary text-foreground"
: "border-transparent text-muted-foreground hover:text-foreground"
@@ -688,12 +688,12 @@ export default function NotificationsPage() {
</div>
{/* Tab bar */}
<div className="flex gap-0 border-b border-border">
<div className="flex gap-0 border-b border-border overflow-x-auto">
{MODES.map((mode) => (
<button
key={mode.key}
onClick={() => setActiveFilterTab(mode.key as ModeKey)}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
className={`shrink-0 px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
activeFilterTab === mode.key
? "border-primary text-foreground"
: "border-transparent text-muted-foreground hover:text-foreground"
@@ -704,7 +704,7 @@ export default function NotificationsPage() {
))}
<button
onClick={() => setActiveFilterTab("discovery")}
className={`px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
className={`shrink-0 px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px ${
activeFilterTab === "discovery"
? "border-primary text-foreground"
: "border-transparent text-muted-foreground hover:text-foreground"
@@ -749,7 +749,7 @@ export default function NotificationsPage() {
<input type="checkbox" checked={!!enabled}
onChange={(e) => setDiscoveryFilters((prev) => ({ ...prev, [src.key]: { ...prev[src.key], enabled: e.target.checked } }))}
className="rounded" />
<div>
<div className="min-w-0">
<span className="text-sm font-medium">Notify me about bills from member follows</span>
<span className="text-xs text-muted-foreground ml-2">{src.description}</span>
</div>
@@ -807,7 +807,7 @@ export default function NotificationsPage() {
<input type="checkbox" checked={!!enabled}
onChange={(e) => setDiscoveryFilters((prev) => ({ ...prev, [src.key]: { ...prev[src.key], enabled: e.target.checked } }))}
className="rounded" />
<div>
<div className="min-w-0">
<span className="text-sm font-medium">Notify me about bills from topic follows</span>
<span className="text-xs text-muted-foreground ml-2">{src.description}</span>
</div>
@@ -1062,7 +1062,7 @@ export default function NotificationsPage() {
<span className="text-xs text-muted-foreground ml-auto">{timeAgo(event.created_at)}</span>
</div>
{billTitle && (
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-1">{billTitle}</p>
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-1 break-all">{billTitle}</p>
)}
{(() => {
const src = p.source as string | undefined;