Add ViewModeSelector component with width controls
- Create ViewModeSelector component with masonry view mode button - Add width toggle controls (normal 700px / wide 900px) - Create width-normal and width-wide SVG icons - Integrate component into photos route with smooth transitions - Use SCSS variables throughout for consistent styling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a69f1098de
commit
b2488bd301
4 changed files with 136 additions and 1 deletions
8
src/assets/icons/width-normal.svg
Normal file
8
src/assets/icons/width-normal.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Normal width container -->
|
||||
<rect x="5" y="4" width="10" height="12" rx="1" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
||||
<!-- Content lines -->
|
||||
<line x1="7" y1="7" x2="13" y2="7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="7" y1="10" x2="11" y2="10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="7" y1="13" x2="13" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 579 B |
8
src/assets/icons/width-wide.svg
Normal file
8
src/assets/icons/width-wide.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Wide width container -->
|
||||
<rect x="2" y="4" width="16" height="12" rx="1" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
||||
<!-- Content lines -->
|
||||
<line x1="4" y1="7" x2="16" y2="7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="4" y1="10" x2="12" y2="10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<line x1="4" y1="13" x2="16" y2="13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 577 B |
101
src/lib/components/ViewModeSelector.svelte
Normal file
101
src/lib/components/ViewModeSelector.svelte
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import PhotosIcon from '$icons/photos.svg?component'
|
||||
import WidthNormalIcon from '$icons/width-normal.svg?component'
|
||||
import WidthWideIcon from '$icons/width-wide.svg?component'
|
||||
|
||||
interface Props {
|
||||
mode?: 'masonry'
|
||||
width?: 'normal' | 'wide'
|
||||
onWidthChange?: (width: 'normal' | 'wide') => void
|
||||
}
|
||||
|
||||
let {
|
||||
mode = 'masonry',
|
||||
width = 'normal',
|
||||
onWidthChange
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="view-mode-selector">
|
||||
<div class="mode-section">
|
||||
<button class="mode-button selected" aria-label="Masonry view">
|
||||
<PhotosIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="width-section">
|
||||
<button
|
||||
class="mode-button"
|
||||
class:selected={width === 'normal'}
|
||||
aria-label="Normal width"
|
||||
onclick={() => onWidthChange?.('normal')}
|
||||
>
|
||||
<WidthNormalIcon />
|
||||
</button>
|
||||
<button
|
||||
class="mode-button"
|
||||
class:selected={width === 'wide'}
|
||||
aria-label="Wide width"
|
||||
onclick={() => onWidthChange?.('wide')}
|
||||
>
|
||||
<WidthWideIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.view-mode-selector {
|
||||
width: 100%;
|
||||
background: $grey-100;
|
||||
border-radius: $corner-radius-lg;
|
||||
box-sizing: border-box;
|
||||
padding: $unit;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: $unit-2x;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.mode-section,
|
||||
.width-section {
|
||||
display: flex;
|
||||
gap: $unit-half;
|
||||
}
|
||||
|
||||
.separator {
|
||||
flex: 1;
|
||||
min-width: $unit-2x;
|
||||
}
|
||||
|
||||
.mode-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: $corner-radius-sm;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: $grey-60;
|
||||
|
||||
&:hover {
|
||||
background: $grey-95;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
color: $red-60;
|
||||
background: $salmon-pink;
|
||||
}
|
||||
|
||||
:global(svg) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import MasonryPhotoGrid from '$components/MasonryPhotoGrid.svelte'
|
||||
import LoadingSpinner from '$components/admin/LoadingSpinner.svelte'
|
||||
import ViewModeSelector from '$components/ViewModeSelector.svelte'
|
||||
import { InfiniteLoader, LoaderState } from 'svelte-infinite'
|
||||
import { generateMetaTags } from '$lib/utils/metadata'
|
||||
import { page } from '$app/stores'
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
// Initialize state with server-side data
|
||||
let allPhotoItems = $state<PhotoItem[]>(data.photoItems || [])
|
||||
let currentOffset = $state(data.pagination?.limit || 20)
|
||||
let containerWidth = $state<'normal' | 'wide'>('normal')
|
||||
|
||||
// Track loaded photo IDs to prevent duplicates
|
||||
let loadedPhotoIds = $state(new Set(data.photoItems?.map((item) => item.id) || []))
|
||||
|
|
@ -100,7 +102,7 @@
|
|||
<link rel="canonical" href={metaTags.other.canonical} />
|
||||
</svelte:head>
|
||||
|
||||
<div class="photos-container">
|
||||
<div class="photos-container" class:wide={containerWidth === 'wide'}>
|
||||
{#if error}
|
||||
<div class="error-container">
|
||||
<div class="error-message">
|
||||
|
|
@ -116,6 +118,10 @@
|
|||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<ViewModeSelector
|
||||
width={containerWidth}
|
||||
onWidthChange={(width) => containerWidth = width}
|
||||
/>
|
||||
<MasonryPhotoGrid photoItems={allPhotoItems} />
|
||||
|
||||
<InfiniteLoader
|
||||
|
|
@ -163,6 +169,18 @@
|
|||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
padding: 0 $unit-3x;
|
||||
transition: max-width 0.3s ease;
|
||||
|
||||
&.wide {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
:global(.view-mode-selector) {
|
||||
margin-bottom: $unit-3x;
|
||||
position: sticky;
|
||||
top: $unit-2x;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@include breakpoint('phone') {
|
||||
padding: 0 $unit-2x;
|
||||
|
|
|
|||
Loading…
Reference in a new issue