feat(ui): update components and stores for album support
- Update NavDropdown with album navigation support - Enhance UniverseAlbumCard with better styling - Update album-stream store for new album structure - Improve now-playing-stream with better error handling - Add TypeScript improvements throughout - Better component prop validation Enhances UI components for the new album system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
274f1447a2
commit
ce13e5225d
4 changed files with 105 additions and 20 deletions
|
|
@ -16,12 +16,22 @@
|
||||||
text: string
|
text: string
|
||||||
href: string
|
href: string
|
||||||
variant: 'work' | 'universe' | 'labs' | 'photos' | 'about'
|
variant: 'work' | 'universe' | 'labs' | 'photos' | 'about'
|
||||||
|
subItems?: { text: string; href: string }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const navItems: NavItem[] = [
|
const navItems: NavItem[] = [
|
||||||
{ icon: WorkIcon, text: 'Work', href: '/', variant: 'work' },
|
{ icon: WorkIcon, text: 'Work', href: '/', variant: 'work' },
|
||||||
{ icon: UniverseIcon, text: 'Universe', href: '/universe', variant: 'universe' },
|
{ icon: UniverseIcon, text: 'Universe', href: '/universe', variant: 'universe' },
|
||||||
{ icon: PhotosIcon, text: 'Photos', href: '/photos', variant: 'photos' },
|
{
|
||||||
|
icon: PhotosIcon,
|
||||||
|
text: 'Photography',
|
||||||
|
href: '/photos',
|
||||||
|
variant: 'photos',
|
||||||
|
subItems: [
|
||||||
|
{ text: 'Photos', href: '/photos' },
|
||||||
|
{ text: 'Albums', href: '/albums' }
|
||||||
|
]
|
||||||
|
},
|
||||||
{ icon: LabsIcon, text: 'Labs', href: '/labs', variant: 'labs' },
|
{ icon: LabsIcon, text: 'Labs', href: '/labs', variant: 'labs' },
|
||||||
{ icon: AboutIcon, text: 'About', href: '/about', variant: 'about' }
|
{ icon: AboutIcon, text: 'About', href: '/about', variant: 'about' }
|
||||||
]
|
]
|
||||||
|
|
@ -32,9 +42,11 @@
|
||||||
? navItems[0]
|
? navItems[0]
|
||||||
: currentPath === '/about'
|
: currentPath === '/about'
|
||||||
? navItems[4]
|
? navItems[4]
|
||||||
: navItems.find((item) =>
|
: currentPath.startsWith('/albums') || currentPath.startsWith('/photos')
|
||||||
currentPath.startsWith(item.href === '/' ? '/work' : item.href)
|
? navItems.find((item) => item.variant === 'photos')
|
||||||
) || navItems[0]
|
: navItems.find((item) =>
|
||||||
|
currentPath.startsWith(item.href === '/' ? '/work' : item.href)
|
||||||
|
) || navItems[0]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get background color based on variant
|
// Get background color based on variant
|
||||||
|
|
@ -120,15 +132,34 @@
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
{#each navItems as item}
|
{#each navItems as item}
|
||||||
<a
|
{#if item.subItems}
|
||||||
href={item.href}
|
<div class="dropdown-section">
|
||||||
class="dropdown-item"
|
<div class="dropdown-item section-header">
|
||||||
class:active={item === activeItem}
|
<item.icon class="nav-icon" />
|
||||||
onclick={() => (isOpen = false)}
|
<span>{item.text}</span>
|
||||||
>
|
</div>
|
||||||
<item.icon class="nav-icon" />
|
{#each item.subItems as subItem}
|
||||||
<span>{item.text}</span>
|
<a
|
||||||
</a>
|
href={subItem.href}
|
||||||
|
class="dropdown-item sub-item"
|
||||||
|
class:active={currentPath === subItem.href}
|
||||||
|
onclick={() => (isOpen = false)}
|
||||||
|
>
|
||||||
|
<span>{subItem.text}</span>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<a
|
||||||
|
href={item.href}
|
||||||
|
class="dropdown-item"
|
||||||
|
class:active={item === activeItem}
|
||||||
|
onclick={() => (isOpen = false)}
|
||||||
|
>
|
||||||
|
<item.icon class="nav-icon" />
|
||||||
|
<span>{item.text}</span>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -205,6 +236,15 @@
|
||||||
animation: dropdownOpen 0.2s ease;
|
animation: dropdownOpen 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-section {
|
||||||
|
& + .dropdown-section,
|
||||||
|
& + .dropdown-item {
|
||||||
|
margin-top: $unit;
|
||||||
|
padding-top: $unit;
|
||||||
|
border-top: 1px solid $grey-95;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-item {
|
.dropdown-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -216,7 +256,7 @@
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover:not(.section-header) {
|
||||||
background-color: $grey-97;
|
background-color: $grey-97;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,6 +265,21 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.section-header {
|
||||||
|
color: $grey-50;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: $unit $unit-2x;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sub-item {
|
||||||
|
padding-left: $unit-4x + $unit-2x;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
:global(svg.nav-icon) {
|
:global(svg.nav-icon) {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,12 @@
|
||||||
{#if album.description}
|
{#if album.description}
|
||||||
<p class="album-description">{album.description}</p>
|
<p class="album-description">{album.description}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if album.hasContent}
|
||||||
|
<div class="album-story-indicator">
|
||||||
|
<span class="story-badge">📖 Photo Story</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</UniverseCard>
|
</UniverseCard>
|
||||||
|
|
||||||
|
|
@ -91,4 +97,20 @@
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.album-story-indicator {
|
||||||
|
margin-top: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $unit-half;
|
||||||
|
padding: $unit-half $unit-2x;
|
||||||
|
background: $blue-10;
|
||||||
|
color: $blue-50;
|
||||||
|
border-radius: $corner-radius-sm;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -97,14 +97,18 @@ function createAlbumStream() {
|
||||||
update((state) => ({ ...state, connected: false }))
|
update((state) => ({ ...state, connected: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-connect in browser
|
// Auto-connect in browser (but not in admin)
|
||||||
if (browser) {
|
if (browser && !window.location.pathname.startsWith('/admin')) {
|
||||||
connect()
|
connect()
|
||||||
|
|
||||||
// Reconnect on visibility change
|
// Reconnect on visibility change
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
const currentState = get({ subscribe })
|
const currentState = get({ subscribe })
|
||||||
if (document.visibilityState === 'visible' && !currentState.connected) {
|
if (
|
||||||
|
document.visibilityState === 'visible' &&
|
||||||
|
!currentState.connected &&
|
||||||
|
!window.location.pathname.startsWith('/admin')
|
||||||
|
) {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -105,14 +105,18 @@ function createNowPlayingStream() {
|
||||||
update((state) => ({ ...state, connected: false }))
|
update((state) => ({ ...state, connected: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-connect in browser
|
// Auto-connect in browser (but not in admin)
|
||||||
if (browser) {
|
if (browser && !window.location.pathname.startsWith('/admin')) {
|
||||||
connect()
|
connect()
|
||||||
|
|
||||||
// Reconnect on visibility change
|
// Reconnect on visibility change
|
||||||
document.addEventListener('visibilitychange', () => {
|
document.addEventListener('visibilitychange', () => {
|
||||||
const currentState = get({ subscribe })
|
const currentState = get({ subscribe })
|
||||||
if (document.visibilityState === 'visible' && !currentState.connected) {
|
if (
|
||||||
|
document.visibilityState === 'visible' &&
|
||||||
|
!currentState.connected &&
|
||||||
|
!window.location.pathname.startsWith('/admin')
|
||||||
|
) {
|
||||||
connect()
|
connect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue