From 516d3175c56ce765e8d96811c68b320f4493287b Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Tue, 30 Jun 2026 19:46:06 +0530 Subject: [PATCH 1/6] Show sidebar Add-photos button only with photos, pin it to bottom With the empty-state hero now carrying the add action, the left rail's button was redundant before any photo existed. Hide it until there's at least one photo and move it below the strip, pinned to the bottom. --- apps/web/src/components/photo-sidebar.tsx | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/photo-sidebar.tsx b/apps/web/src/components/photo-sidebar.tsx index 379aaf4..4772bed 100644 --- a/apps/web/src/components/photo-sidebar.tsx +++ b/apps/web/src/components/photo-sidebar.tsx @@ -10,12 +10,15 @@ const ACCEPT = 'image/jpeg,image/png,image/webp,image/heic,image/heif,.jpg,.jpeg,.png,.webp,.heic,.heif' /** - * Left rail: an "Add photos" button on top, the reorderable strip below. + * Left rail: the reorderable photo strip, with an "Add photos" button pinned + * to the bottom. The button only appears once there's at least one photo — + * before that, the centre empty-state hero carries the add action. * Dragging a file anywhere over the app reveals a square drop pad here, with * the strip blurred behind it — and a drop is accepted wherever it lands. */ export function PhotoSidebar() { const addFiles = usePhotoStore((state) => state.addFiles) + const hasPhotos = usePhotoStore((state) => state.photos.length > 0) const inputRef = useRef(null) const onFiles = useCallback((files: File[]) => addFiles(files), [addFiles]) @@ -36,17 +39,19 @@ export function PhotoSidebar() { }} /> - - + {hasPhotos && ( + + )} + {dragging && (
From 07a85d3bf5681c376e81368ad6cdee4ec96178c9 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Tue, 30 Jun 2026 19:46:06 +0530 Subject: [PATCH 2/6] Give Export PDF a download icon to match Add photos --- apps/web/src/components/options-panel.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/options-panel.tsx b/apps/web/src/components/options-panel.tsx index ef9c6d3..7e28c84 100644 --- a/apps/web/src/components/options-panel.tsx +++ b/apps/web/src/components/options-panel.tsx @@ -1,5 +1,5 @@ import { type ReactNode, useState } from "react"; -import { Minus, Plus } from "lucide-react"; +import { FileDown, Minus, Plus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; @@ -335,8 +335,9 @@ export function OptionsPanel() {
From 7a326d851d387d1df693e6fcbd507765f9b5da18 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Tue, 30 Jun 2026 20:18:56 +0530 Subject: [PATCH 3/6] Float page controls onto the sheet to align its top with sidebars The shape toggle and page label sat in a row above the sheet, dropping the sheet's top edge below where both sidebars begin. Overlay them on the sheet's top margin instead (screen-only, never in the PDF) so the sheet top lines up with the photo strip and the options panel. --- apps/web/src/components/a4-preview.tsx | 40 +++++++++++++++++--------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/web/src/components/a4-preview.tsx b/apps/web/src/components/a4-preview.tsx index fcbfdca..00f8b85 100644 --- a/apps/web/src/components/a4-preview.tsx +++ b/apps/web/src/components/a4-preview.tsx @@ -92,11 +92,8 @@ export function A4Preview() { ? stripPages.map((stripPhotos, page) => (
- - {pageLabel(page)} - + {pageLabel(page) && ( +
+ +
+ )}
)) : gridPages.map((slice, page) => (
-
- - {pageLabel(page)} - - setPageShape(page, shape)} - /> -
+ {/* Screen-only controls float over the sheet's top margin so the + sheet's top edge stays level with both sidebars. */} +
+ {pageLabel(page) ? : } +
+ setPageShape(page, shape)} + /> +
+
))}
@@ -145,6 +149,14 @@ export function A4Preview() { ); } +function PageBadge({ label }: { label: string }) { + return ( + + {label} + + ); +} + function PageShapeToggle({ value, onChange, From ce58faabe34e5167f42997e997faa39f420060f6 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Tue, 30 Jun 2026 20:18:56 +0530 Subject: [PATCH 4/6] Keep the sidebar Add-photos button in view with a sticky bottom Pinning it with mt-auto alone parked it at the bottom of the full-height pane, off-screen until you scrolled. Stick it to the viewport bottom so it stays reachable. --- apps/web/src/components/photo-sidebar.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/web/src/components/photo-sidebar.tsx b/apps/web/src/components/photo-sidebar.tsx index 4772bed..0902ec6 100644 --- a/apps/web/src/components/photo-sidebar.tsx +++ b/apps/web/src/components/photo-sidebar.tsx @@ -42,14 +42,16 @@ export function PhotoSidebar() { {hasPhotos && ( - +
+ +
)} {dragging && ( From 8d83d9fe9a0eb086f36cd34cfb311dd07369ba8c Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Tue, 30 Jun 2026 20:25:52 +0530 Subject: [PATCH 5/6] Make the floated page controls glassy and smaller The overlay read like a raised button. Switch to a translucent white glass with a hairline border, drop the shadow, and shrink the toggle so it sits quietly on the sheet. Styled for white paper so it stays legible in dark mode too. --- apps/web/src/components/a4-preview.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/a4-preview.tsx b/apps/web/src/components/a4-preview.tsx index 00f8b85..8e72ccb 100644 --- a/apps/web/src/components/a4-preview.tsx +++ b/apps/web/src/components/a4-preview.tsx @@ -135,7 +135,7 @@ export function A4Preview() { sheet's top edge stays level with both sidebars. */}
{pageLabel(page) ? : } -
+
setPageShape(page, shape)} @@ -151,7 +151,7 @@ export function A4Preview() { function PageBadge({ label }: { label: string }) { return ( - + {label} ); @@ -178,11 +178,11 @@ function PageShapeToggle({ aria-pressed={active} onClick={() => onChange(id)} className={cn( - "text-muted-foreground hover:bg-accent hover:text-foreground flex size-6 items-center justify-center rounded", - active && "bg-accent text-foreground", + "flex size-5 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-black/5 hover:text-neutral-900", + active && "bg-black/10 text-neutral-900", )} > - + {label} frames on this page From 1f804597c3d847a3e1b7d6e3fea4d94f539e2982 Mon Sep 17 00:00:00 2001 From: ankitsejwal Date: Tue, 30 Jun 2026 20:33:14 +0530 Subject: [PATCH 6/6] Collapse the page-shape control to one icon until hovered It overlapped the sheet's margin guide. Show just the current shape as a small glassy icon at rest, tucked into the corner, and reveal the full three-way selector on hover or focus (focus so touch works too). --- apps/web/src/components/a4-preview.tsx | 77 +++++++++++++++++--------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/apps/web/src/components/a4-preview.tsx b/apps/web/src/components/a4-preview.tsx index 8e72ccb..38b1b5b 100644 --- a/apps/web/src/components/a4-preview.tsx +++ b/apps/web/src/components/a4-preview.tsx @@ -133,9 +133,9 @@ export function A4Preview() { /> {/* Screen-only controls float over the sheet's top margin so the sheet's top edge stays level with both sidebars. */} -
+
{pageLabel(page) ? : } -
+
setPageShape(page, shape)} @@ -157,6 +157,11 @@ function PageBadge({ label }: { label: string }) { ); } +/** + * Collapses to a single glassy icon of the current shape so it stays out of the + * way of the sheet's margin guide; reveals the full selector on hover or focus + * (focus covers touch, where there's no hover). + */ function PageShapeToggle({ value, onChange, @@ -164,31 +169,51 @@ function PageShapeToggle({ value: Orientation; onChange: (shape: Orientation) => void; }) { + const CurrentIcon = SHAPE_ICONS[value]; + const glass = + "rounded-md bg-white/40 ring-1 ring-black/5 backdrop-blur-md"; return ( -
- {FRAME_SHAPES.map(({ id, label }) => { - const Icon = SHAPE_ICONS[id]; - const active = value === id; - return ( - - - - - {label} frames on this page - - ); - })} +
+ +
+ {FRAME_SHAPES.map(({ id, label }) => { + const Icon = SHAPE_ICONS[id]; + const active = value === id; + return ( + + + + + {label} frames on this page + + ); + })} +
); }