diff --git a/apps/web/src/components/a4-preview.tsx b/apps/web/src/components/a4-preview.tsx index 7ee52f7..627837d 100644 --- a/apps/web/src/components/a4-preview.tsx +++ b/apps/web/src/components/a4-preview.tsx @@ -42,12 +42,8 @@ export function A4Preview() { return (
-

- Print sheet ({paper.label}) - {pageCount > 1 ? ` — ${pageCount} pages` : ''} -

- {/* Floats over the page so the selected frame's tools follow you. */} -
+ {/* Pinned to the screen, so the selected frame's tools never shift the page. */} +
- - setPerRow(Number(event.target.value))} - className="accent-primary w-24" - /> - - {perRow} - - + void +}) { + return ( + + + {value} + + + ) +} + function Section({ title, children }: { title: string; children: ReactNode }) { return (
diff --git a/apps/web/src/components/photo-dropzone.tsx b/apps/web/src/components/photo-dropzone.tsx deleted file mode 100644 index 8961b6c..0000000 --- a/apps/web/src/components/photo-dropzone.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { useCallback, useState } from 'react' -import { type FileRejection, useDropzone } from 'react-dropzone' -import { ImagePlus } from 'lucide-react' - -import { cn } from '@/lib/utils' -import { usePhotoStore } from '@/stores/photo-store' - -export function PhotoDropzone({ compact = false }: { compact?: boolean }) { - const addFiles = usePhotoStore((state) => state.addFiles) - const [rejectedCount, setRejectedCount] = useState(0) - - const onDrop = useCallback( - (accepted: File[], rejections: FileRejection[]) => { - if (accepted.length > 0) addFiles(accepted) - setRejectedCount(rejections.length) - }, - [addFiles], - ) - - const { getRootProps, getInputProps, isDragActive } = useDropzone({ - onDrop, - accept: { - 'image/jpeg': ['.jpg', '.jpeg'], - 'image/png': ['.png'], - 'image/webp': ['.webp'], - 'image/heic': ['.heic'], - 'image/heif': ['.heif'], - }, - }) - - return ( -
-
- - -
-

- {isDragActive - ? 'Drop your photos…' - : 'Drag photos here, or click to browse'} -

- {!compact && ( -

- JPEG, PNG, WebP, HEIC — processed on your device, never uploaded -

- )} -
-
- {rejectedCount > 0 && ( -

- {rejectedCount} file{rejectedCount > 1 ? 's' : ''} skipped — only - images are supported. -

- )} -
- ) -} diff --git a/apps/web/src/components/photo-sidebar.tsx b/apps/web/src/components/photo-sidebar.tsx new file mode 100644 index 0000000..670fb63 --- /dev/null +++ b/apps/web/src/components/photo-sidebar.tsx @@ -0,0 +1,69 @@ +import { useCallback, useState } from 'react' +import { type FileRejection, useDropzone } from 'react-dropzone' +import { ImagePlus } from 'lucide-react' + +import { PhotoStrip } from '@/components/photo-strip' +import { Button } from '@/components/ui/button' +import { usePhotoStore } from '@/stores/photo-store' + +/** + * Left rail: an "Add photos" button on top, the reorderable strip below. + * The whole rail is a drop target — but stays a clean button until you drag, + * when a square drop pad fades in over a blurred strip. + */ +export function PhotoSidebar() { + const addFiles = usePhotoStore((state) => state.addFiles) + const [rejectedCount, setRejectedCount] = useState(0) + + const onDrop = useCallback( + (accepted: File[], rejections: FileRejection[]) => { + if (accepted.length > 0) addFiles(accepted) + setRejectedCount(rejections.length) + }, + [addFiles], + ) + + const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ + onDrop, + noClick: true, + noKeyboard: true, + accept: { + 'image/jpeg': ['.jpg', '.jpeg'], + 'image/png': ['.png'], + 'image/webp': ['.webp'], + 'image/heic': ['.heic'], + 'image/heif': ['.heif'], + }, + }) + + return ( +
+ + + + + {rejectedCount > 0 && ( +

+ {rejectedCount} file{rejectedCount > 1 ? 's' : ''} skipped — only + images are supported. +

+ )} + + + + {isDragActive && ( +
+
+ +

+ Drop your photos… +

+
+
+ )} +
+ ) +} diff --git a/apps/web/src/components/sheet-inspector.tsx b/apps/web/src/components/sheet-inspector.tsx index 9544776..e2eb21c 100644 --- a/apps/web/src/components/sheet-inspector.tsx +++ b/apps/web/src/components/sheet-inspector.tsx @@ -1,4 +1,5 @@ import { + Check, RectangleHorizontal, RectangleVertical, Square, @@ -35,9 +36,9 @@ export function SheetInspector() { if (!photo) return null return ( -
- - Drag the photo to reposition +
+ + Drag to reposition
{ORIENTATIONS.map((orientation) => { @@ -80,21 +81,22 @@ export function SheetInspector() {
) diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 661f66d..b4b71a9 100644 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -2,35 +2,29 @@ import { createFileRoute } from '@tanstack/react-router' import { A4Preview } from '@/components/a4-preview' import { OptionsPanel } from '@/components/options-panel' -import { PhotoDropzone } from '@/components/photo-dropzone' -import { PhotoStrip } from '@/components/photo-strip' +import { PhotoSidebar } from '@/components/photo-sidebar' import { ProjectControls } from '@/components/project-controls' -import { usePhotoStore } from '@/stores/photo-store' export const Route = createFileRoute('/')({ component: Home, }) function Home() { - const hasPhotos = usePhotoStore((state) => state.photos.length > 0) - return (

Polaroid

- Build polaroid-style print sheets — edit each frame on the page. - Everything stays on your device. + Edit each frame on the page — everything stays on your device.

-