Photo Library
A photo library app with virtualized scrolling, view transitions, adjustable waterfall layout, folder tree, search, multi-selection, and accessible drag and drop.
App.css
App.tsx
PhotoDetail.css
PhotoDetail.tsx
PhotoGrid.css
PhotoGrid.tsx
Sidebar.css
Sidebar.tsx
albums.json
photos.json
App.css
App.tsx
PhotoDetail.css
PhotoDetail.tsx
PhotoGrid.css
PhotoGrid.tsx
Sidebar.css
Sidebar.tsx
albums.json
photos.json
App.css
App.tsx
PhotoDetail.css
PhotoDetail.tsx
PhotoGrid.css
PhotoGrid.tsx
Sidebar.css
Sidebar.tsx
albums.json
photos.json
import './App.css';
import photos from './photos.json';
import {useLayoutEffect, useMemo, useState} from 'react';
import {Sidebar} from './Sidebar';
import {PhotoGrid} from './PhotoGrid';
import {PhotoDetail} from './PhotoDetail';
type Photo = typeof photos[0];
export function App() {
let [album, setAlbum] = useState('library');
let [library, setLibrary] = useState(photos);
let [isMobile, setMobile] = useState(false);
let [sidebarVisible, setSidebarVisible] = useState(false);
let [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);
let visiblePhotos = useMemo(() => album === 'library' ? library : library.filter(p => p.album === album), [album, library]);
useLayoutEffect(() => {
let media = matchMedia('(width >= 500px)');
setMobile(media.matches);
media.onchange = () => {
setMobile(media.matches);
};
}, []);
return (
<div className="app">
<Sidebar
// Sidebar is hidden by default on mobile
isVisible={isMobile ? true : sidebarVisible}
selectedAlbum={album}
onSelectionChange={album => {
setAlbum(album);
setSidebarVisible(false);
}}
onDrop={(album, photoIds) => {
setLibrary(library.map(photo => photoIds.includes(photo.id) ? { ...photo, album } : photo));
setAlbum(album);
}} />
<div style={{flex: 1, minWidth: 0}} inert={sidebarVisible}>
<PhotoGrid
key={album}
photos={visiblePhotos}
onAction={setSelectedPhoto}
toggleSidebar={() => setSidebarVisible(!sidebarVisible)}
hidden={!!selectedPhoto} />
{selectedPhoto && <PhotoDetail photo={selectedPhoto} onBack={() => setSelectedPhoto(null)} />}
</div>
</div>
);
}