diff --git a/apps/web/src/components/a4-preview.tsx b/apps/web/src/components/a4-preview.tsx index fcbfdca..38b1b5b 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,19 @@ export function A4Preview() { ); } +function PageBadge({ label }: { label: string }) { + return ( + + {label} + + ); +} + +/** + * 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, @@ -152,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 + + ); + })} +
); } 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() {
diff --git a/apps/web/src/components/photo-sidebar.tsx b/apps/web/src/components/photo-sidebar.tsx index 379aaf4..0902ec6 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,21 @@ export function PhotoSidebar() { }} /> - - + {hasPhotos && ( +
+ +
+ )} + {dragging && (