Show right widget sidebar at iPad-landscape width

The right sidebar previously required xl (1280px) to appear, so horizontal
iPad (1024px) viewports saw only the left sidebar + main column. Use the
existing lg breakpoint (1024px) to control right-sidebar visibility, and
let the sidebars scale fluidly with the viewport by setting them to w-1/4
max-w-[300px] instead of fixed pixel widths. The center column (flex-1)
absorbs whatever space remains, so the layout fills the viewport smoothly
from 1024px up through the 1200px wrapper cap instead of leaving dead
space at intermediate widths. Below the lg breakpoint, the left sidebar
keeps its fixed 300px width.
This commit is contained in:
Alex Gleason
2026-05-01 21:08:42 -05:00
parent 5d99337cd2
commit 74a2522af1
10 changed files with 26 additions and 28 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "ditto",
"version": "2.11.2",
"version": "2.12.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ditto",
"version": "2.11.2",
"version": "2.12.0",
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.2.0",
"@capacitor/app": "^8.0.0",
+1 -1
View File
@@ -85,7 +85,7 @@ export function LeftSidebar() {
};
return (
<aside className="flex flex-col h-screen sticky top-0 py-3 px-4 w-[300px] shrink-0">
<aside className="hidden sidebar:flex flex-col h-screen sticky top-0 py-3 px-4 w-[300px] lg:w-1/4 lg:max-w-[300px] shrink-0">
{/* Logo */}
<div className="flex items-center px-3 mb-1">
<Link to="/" onClick={scrollToTopIfCurrent('/')}>
+11 -11
View File
@@ -121,7 +121,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
}, []);
const chatSidebar = (
<aside className="hidden xl:flex xl:flex-col xl:w-[340px] xl:shrink-0 h-screen sticky top-0">
<aside className="hidden lg:flex lg:flex-col lg:w-[340px] lg:shrink-0 h-screen sticky top-0">
<LiveStreamChat aTag={aTag} className="h-full" />
</aside>
);
@@ -137,7 +137,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
const detailsBlock = (
<div className="space-y-4">
{/* Author — mobile only (desktop shows it above) */}
<div className="xl:hidden">
<div className="lg:hidden">
<StreamAuthorRow event={event} participants={participants} />
</div>
@@ -179,7 +179,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
return (
<>
<main className="xl:max-sidebar:flex max-sidebar:flex max-sidebar:flex-col max-sidebar:livestream-height max-sidebar:overflow-hidden">
<main className="lg:max-sidebar:flex max-sidebar:flex max-sidebar:flex-col max-sidebar:livestream-height max-sidebar:overflow-hidden">
{/* Header */}
<PageHeader
title="Live Stream"
@@ -194,7 +194,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
</PageHeader>
{/* Video Player */}
<div className="xl:px-4 shrink-0">
<div className="lg:px-4 shrink-0">
{playUrl ? (
<LiveStreamPlayer
src={playUrl}
@@ -203,7 +203,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
title={title}
/>
) : (
<div className="aspect-video xl:rounded-2xl bg-muted flex items-center justify-center border-y xl:border border-border">
<div className="aspect-video lg:rounded-2xl bg-muted flex items-center justify-center border-y lg:border border-border">
<div className="text-center space-y-2">
<Radio className="size-8 text-muted-foreground/40 mx-auto" />
<p className="text-sm text-muted-foreground">
@@ -241,7 +241,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
</div>
{/* Author / Host — desktop only (on mobile it's inside the expandable details) */}
<div className="hidden xl:block">
<div className="hidden lg:block">
<StreamAuthorRow event={event} participants={participants} />
</div>
@@ -249,7 +249,7 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
{hasExpandable && (
<button
onClick={() => setDescExpanded((v) => !v)}
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors xl:hidden"
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors lg:hidden"
>
{descExpanded ? <ChevronUp className="size-3.5" /> : <ChevronDown className="size-3.5" />}
{descExpanded ? 'Hide details' : 'Show details'}
@@ -258,24 +258,24 @@ export function LiveStreamPage({ event }: LiveStreamPageProps) {
{/* Mobile: collapsible details */}
{descExpanded && (
<div className="xl:hidden">
<div className="lg:hidden">
{detailsBlock}
</div>
)}
{/* Desktop: always show details */}
<div className="hidden xl:block">
<div className="hidden lg:block">
{detailsBlock}
</div>
</div>
{/* Mobile chat — fills remaining viewport, scrollbox sits above bottom nav */}
<div className="xl:hidden mt-2 border-t border-border flex-1 min-h-0 overflow-hidden">
<div className="lg:hidden mt-2 border-t border-border flex-1 min-h-0 overflow-hidden">
<LiveStreamChat aTag={aTag} className="h-full" />
</div>
{/* Bottom spacer (desktop only) */}
<div className="hidden xl:block h-8" />
<div className="hidden lg:block h-8" />
</main>
</>
);
+2 -2
View File
@@ -188,7 +188,7 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live
if (hasError) {
return (
<div className={cn('relative xl:rounded-2xl overflow-hidden bg-black aspect-video flex items-center justify-center', className)}>
<div className={cn('relative lg:rounded-2xl overflow-hidden bg-black aspect-video flex items-center justify-center', className)}>
<div className="text-center space-y-2 px-4">
<p className="text-white/80 text-sm font-medium">Stream unavailable</p>
<p className="text-white/50 text-xs">The live stream could not be loaded. It may have ended or the URL is unreachable.</p>
@@ -201,7 +201,7 @@ export function LiveStreamPlayer({ src, poster, className, title, artist }: Live
<div
ref={containerRef}
className={cn(
'relative xl:rounded-2xl overflow-hidden bg-black group aspect-video',
'relative lg:rounded-2xl overflow-hidden bg-black group aspect-video',
className,
)}
onMouseMove={revealControls}
+3 -5
View File
@@ -44,7 +44,7 @@ function PageSkeleton() {
</div>
</main>
{/* Right sidebar placeholder — preserves layout width */}
<div className="w-[300px] shrink-0 hidden xl:block" />
<div className="w-1/4 max-w-[300px] shrink-0 hidden lg:block" />
</>
);
}
@@ -74,9 +74,7 @@ function MainLayoutInner() {
{/* Main layout - three column on desktop */}
<div className={cn("flex justify-center mx-auto max-w-[1200px]", wrapperClassName)}>
{/* Desktop left sidebar - hidden below sidebar breakpoint */}
<div className="hidden sidebar:block">
<LeftSidebar />
</div>
<LeftSidebar />
{/* Main content + right sidebar: inside Suspense so the left sidebar persists while lazy pages load */}
<Suspense fallback={<PageSkeleton />}>
@@ -107,7 +105,7 @@ function MainLayoutInner() {
)}
</div>
{/* Right sidebar — render page-provided sidebar, or the widget sidebar */}
{rightSidebar ?? <Suspense fallback={<div className="w-[300px] shrink-0 hidden xl:block" />}><WidgetSidebar /></Suspense>}
{rightSidebar ?? <Suspense fallback={<div className="w-1/4 max-w-[300px] shrink-0 hidden lg:block" />}><WidgetSidebar /></Suspense>}
</Suspense>
</div>
+1 -1
View File
@@ -535,7 +535,7 @@ export function ProfileRightSidebar({ fields, pubkey, onMediaClick, className }:
const sidebarRows = useMemo(() => sidebarJustifiedLayout(media), [media]);
return (
<aside className={cn("w-[300px] shrink-0 hidden xl:flex flex-col sticky top-0 h-screen overflow-y-auto pt-2 pb-3 px-3", className)}>
<aside className={cn("w-1/4 max-w-[300px] shrink-0 hidden lg:flex flex-col sticky top-0 h-screen overflow-y-auto pt-2 pb-3 px-3", className)}>
{/* Media Section — only shown when pubkey prop is provided */}
{pubkey !== undefined && <section className="mb-6 bg-background/85 rounded-xl p-3 -mx-1">
<h2 className="text-xl font-bold mb-3" style={{ fontFamily: 'var(--title-font-family, inherit)' }}>Media</h2>
+1 -1
View File
@@ -71,7 +71,7 @@ export function RightSidebar() {
const { data: sparklineData, isLoading: sparklinesLoading } = useTagSparklines(visibleTags, labelCreatedAt, isXl && visibleTags.length > 0);
return (
<aside className="w-[300px] shrink-0 hidden xl:flex flex-col sticky top-0 h-screen overflow-y-auto pt-2 pb-3 px-3">
<aside className="w-1/4 max-w-[300px] shrink-0 hidden lg:flex flex-col sticky top-0 h-screen overflow-y-auto pt-2 pb-3 px-3">
{/* Trending Tags */}
<section className="mb-6 bg-background/85 rounded-xl p-3 -mx-1">
<div className="flex items-center justify-between mb-3">
+1 -1
View File
@@ -201,7 +201,7 @@ export function WidgetSidebar() {
}, [updateWidgets]);
return (
<aside className="w-[300px] shrink-0 hidden xl:flex flex-col sticky top-0 h-screen overflow-y-auto pt-2 pb-3 px-2">
<aside className="w-1/4 max-w-[300px] shrink-0 hidden lg:flex flex-col sticky top-0 h-screen overflow-y-auto pt-2 pb-3 px-2">
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>
<div className="space-y-2 flex-1">
+2 -2
View File
@@ -2291,9 +2291,9 @@ type EditableTab = { label: string; isCore: boolean; tab?: ProfileTab };
</div>
)}
{/* Profile fields shown inline on mobile (sidebar is hidden below xl) */}
{/* Profile fields shown inline on mobile (sidebar is hidden below widgets) */}
{fields.length > 0 && (
<div className="mt-4 space-y-3 xl:hidden">
<div className="mt-4 space-y-3 lg:hidden">
{fields.map((field, i) => (
<ProfileFieldInline key={i} field={field} />
))}
+2 -2
View File
@@ -839,8 +839,8 @@ export function ProfileSettings() {
</div>
</div>
{/* Mobile sidebar preview — visible only below xl where the real sidebar is hidden */}
<div className="xl:hidden">
{/* Mobile sidebar preview — visible only below widgets where the real sidebar is hidden */}
<div className="lg:hidden">
<Collapsible open={showMobilePreview} onOpenChange={setShowMobilePreview}>
<CollapsibleTrigger asChild>
<Button type="button" variant="ghost" className="w-full justify-between px-0 h-auto hover:bg-transparent hover:text-foreground">