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"]}
|
<input type="checkbox" checked={!!filters["new_document"]}
|
||||||
onChange={(e) => onChange({ ...filters, new_document: e.target.checked })}
|
onChange={(e) => onChange({ ...filters, new_document: e.target.checked })}
|
||||||
className="mt-0.5 rounded" />
|
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>
|
||||||
|
|
||||||
<label className="flex items-start gap-3 py-1.5 cursor-pointer">
|
<label className="flex items-start gap-3 py-1.5 cursor-pointer">
|
||||||
<input type="checkbox" checked={!!filters["new_amendment"]}
|
<input type="checkbox" checked={!!filters["new_amendment"]}
|
||||||
onChange={(e) => onChange({ ...filters, new_amendment: e.target.checked })}
|
onChange={(e) => onChange({ ...filters, new_amendment: e.target.checked })}
|
||||||
className="mt-0.5 rounded" />
|
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>
|
</label>
|
||||||
|
|
||||||
<div className="py-1.5">
|
<div className="py-1.5">
|
||||||
<label className="flex items-start gap-3 cursor-pointer">
|
<label className="flex items-start gap-3 cursor-pointer">
|
||||||
<input ref={milestoneCheckRef} type="checkbox" checked={milestoneState === "on"}
|
<input ref={milestoneCheckRef} type="checkbox" checked={milestoneState === "on"}
|
||||||
onChange={toggleMilestones} className="mt-0.5 rounded" />
|
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>
|
</label>
|
||||||
<div className="ml-6 mt-1 space-y-0.5">
|
<div className="ml-6 mt-1 space-y-0.5">
|
||||||
{(["vote", "presidential", "committee_report", "calendar", "procedural"] as const).map((k) => {
|
{(["vote", "presidential", "committee_report", "calendar", "procedural"] as const).map((k) => {
|
||||||
@@ -150,7 +150,7 @@ function ModeFilterSection({
|
|||||||
<input type="checkbox" checked={!!filters[k]}
|
<input type="checkbox" checked={!!filters[k]}
|
||||||
onChange={(e) => onChange({ ...filters, [k]: e.target.checked })}
|
onChange={(e) => onChange({ ...filters, [k]: e.target.checked })}
|
||||||
className="mt-0.5 rounded" />
|
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>
|
</label>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -484,7 +484,7 @@ export default function NotificationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Channel tab bar */}
|
{/* 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: "ntfy", label: "ntfy", icon: Bell, active: settings?.ntfy_enabled },
|
||||||
{ key: "email", label: "Email", icon: Mail, active: settings?.email_enabled },
|
{ key: "email", label: "Email", icon: Mail, active: settings?.email_enabled },
|
||||||
@@ -494,7 +494,7 @@ export default function NotificationsPage() {
|
|||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
onClick={() => setActiveChannelTab(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
|
activeChannelTab === key
|
||||||
? "border-primary text-foreground"
|
? "border-primary text-foreground"
|
||||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||||
@@ -688,12 +688,12 @@ export default function NotificationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab bar */}
|
{/* 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) => (
|
{MODES.map((mode) => (
|
||||||
<button
|
<button
|
||||||
key={mode.key}
|
key={mode.key}
|
||||||
onClick={() => setActiveFilterTab(mode.key as ModeKey)}
|
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
|
activeFilterTab === mode.key
|
||||||
? "border-primary text-foreground"
|
? "border-primary text-foreground"
|
||||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||||
@@ -704,7 +704,7 @@ export default function NotificationsPage() {
|
|||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveFilterTab("discovery")}
|
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"
|
activeFilterTab === "discovery"
|
||||||
? "border-primary text-foreground"
|
? "border-primary text-foreground"
|
||||||
: "border-transparent text-muted-foreground hover:text-foreground"
|
: "border-transparent text-muted-foreground hover:text-foreground"
|
||||||
@@ -749,7 +749,7 @@ export default function NotificationsPage() {
|
|||||||
<input type="checkbox" checked={!!enabled}
|
<input type="checkbox" checked={!!enabled}
|
||||||
onChange={(e) => setDiscoveryFilters((prev) => ({ ...prev, [src.key]: { ...prev[src.key], enabled: e.target.checked } }))}
|
onChange={(e) => setDiscoveryFilters((prev) => ({ ...prev, [src.key]: { ...prev[src.key], enabled: e.target.checked } }))}
|
||||||
className="rounded" />
|
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-sm font-medium">Notify me about bills from member follows</span>
|
||||||
<span className="text-xs text-muted-foreground ml-2">{src.description}</span>
|
<span className="text-xs text-muted-foreground ml-2">{src.description}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -807,7 +807,7 @@ export default function NotificationsPage() {
|
|||||||
<input type="checkbox" checked={!!enabled}
|
<input type="checkbox" checked={!!enabled}
|
||||||
onChange={(e) => setDiscoveryFilters((prev) => ({ ...prev, [src.key]: { ...prev[src.key], enabled: e.target.checked } }))}
|
onChange={(e) => setDiscoveryFilters((prev) => ({ ...prev, [src.key]: { ...prev[src.key], enabled: e.target.checked } }))}
|
||||||
className="rounded" />
|
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-sm font-medium">Notify me about bills from topic follows</span>
|
||||||
<span className="text-xs text-muted-foreground ml-2">{src.description}</span>
|
<span className="text-xs text-muted-foreground ml-2">{src.description}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -1062,7 +1062,7 @@ export default function NotificationsPage() {
|
|||||||
<span className="text-xs text-muted-foreground ml-auto">{timeAgo(event.created_at)}</span>
|
<span className="text-xs text-muted-foreground ml-auto">{timeAgo(event.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
{billTitle && (
|
{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;
|
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
|
// Authenticated or guest browsing: render the full app shell
|
||||||
return (
|
return (
|
||||||
<div className="flex h-dvh bg-background">
|
<div className="fixed inset-0 flex overflow-hidden bg-background">
|
||||||
{/* Desktop sidebar — hidden on mobile */}
|
{/* Desktop sidebar — hidden on mobile */}
|
||||||
<div className="hidden md:flex">
|
<div className="hidden md:flex">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
@@ -59,7 +59,7 @@ export function AuthGuard({ children }: { children: React.ReactNode }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Content column */}
|
{/* 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)} />
|
<MobileHeader onMenuClick={() => setDrawerOpen(true)} />
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
<div className="container mx-auto px-4 md:px-6 py-6 max-w-7xl">
|
<div className="container mx-auto px-4 md:px-6 py-6 max-w-7xl">
|
||||||
|
|||||||
Reference in New Issue
Block a user