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:
@@ -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;
|
||||
|
||||
@@ -42,7 +42,7 @@ export function AuthGuard({ children }: { children: React.ReactNode }) {
|
||||
|
||||
// Authenticated or guest browsing: render the full app shell
|
||||
return (
|
||||
<div className="flex h-dvh bg-background">
|
||||
<div className="fixed inset-0 flex overflow-hidden bg-background">
|
||||
{/* Desktop sidebar — hidden on mobile */}
|
||||
<div className="hidden md:flex">
|
||||
<Sidebar />
|
||||
@@ -59,7 +59,7 @@ export function AuthGuard({ children }: { children: React.ReactNode }) {
|
||||
)}
|
||||
|
||||
{/* Content column */}
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
<div className="flex-1 flex flex-col min-h-0 min-w-0">
|
||||
<MobileHeader onMenuClick={() => setDrawerOpen(true)} />
|
||||
<main className="flex-1 overflow-auto">
|
||||
<div className="container mx-auto px-4 md:px-6 py-6 max-w-7xl">
|
||||
|
||||
Reference in New Issue
Block a user