add element theming and avatar to navigation
This commit is contained in:
parent
e6b95f7c12
commit
e2c71ad28d
2 changed files with 170 additions and 27 deletions
|
|
@ -9,21 +9,31 @@
|
||||||
import Icon from './Icon.svelte'
|
import Icon from './Icon.svelte'
|
||||||
import DropdownItem from './ui/dropdown/DropdownItem.svelte'
|
import DropdownItem from './ui/dropdown/DropdownItem.svelte'
|
||||||
import { DropdownMenu } from 'bits-ui'
|
import { DropdownMenu } from 'bits-ui'
|
||||||
|
import type { UserCookie } from '$lib/types/UserCookie'
|
||||||
|
import { getAvatarSrc, getAvatarSrcSet } from '$lib/utils/avatar'
|
||||||
|
|
||||||
// Props from layout data
|
// Props from layout data
|
||||||
const {
|
const {
|
||||||
username: usernameProp,
|
account,
|
||||||
isAuthenticated: isAuthProp,
|
currentUser,
|
||||||
role: roleProp
|
isAuthenticated: isAuthProp
|
||||||
} = $props<{
|
} = $props<{
|
||||||
username?: string | null
|
account?: {
|
||||||
|
userId: string
|
||||||
|
username: string
|
||||||
|
role: number
|
||||||
|
} | null
|
||||||
|
currentUser?: UserCookie | null
|
||||||
isAuthenticated?: boolean
|
isAuthenticated?: boolean
|
||||||
role?: number | null
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const username = $derived(usernameProp ?? '')
|
const username = $derived(account?.username ?? '')
|
||||||
const isAuth = $derived(Boolean(isAuthProp))
|
const isAuth = $derived(Boolean(isAuthProp))
|
||||||
const role = $derived(roleProp ?? null)
|
const role = $derived(account?.role ?? null)
|
||||||
|
// Element from UserCookie is already a string like "fire", "water", etc.
|
||||||
|
const userElement = $derived(
|
||||||
|
currentUser?.element as 'wind' | 'fire' | 'water' | 'earth' | 'dark' | 'light' | undefined
|
||||||
|
)
|
||||||
|
|
||||||
// Localized links
|
// Localized links
|
||||||
const galleryHref = $derived(localizeHref('/teams/explore'))
|
const galleryHref = $derived(localizeHref('/teams/explore'))
|
||||||
|
|
@ -35,6 +45,13 @@
|
||||||
const databaseHref = $derived(localizeHref('/database'))
|
const databaseHref = $derived(localizeHref('/database'))
|
||||||
const newTeamHref = $derived(localizeHref('/teams/new'))
|
const newTeamHref = $derived(localizeHref('/teams/new'))
|
||||||
|
|
||||||
|
// Get the element class for styling
|
||||||
|
const elementClass = $derived(userElement ? `element-${userElement}` : '')
|
||||||
|
|
||||||
|
// Get the user's avatar URLs
|
||||||
|
const avatarSrc = $derived(getAvatarSrc(currentUser?.picture))
|
||||||
|
const avatarSrcSet = $derived(getAvatarSrcSet(currentUser?.picture))
|
||||||
|
|
||||||
// Database-specific links
|
// Database-specific links
|
||||||
const databaseCharactersHref = $derived(localizeHref('/database/characters'))
|
const databaseCharactersHref = $derived(localizeHref('/database/characters'))
|
||||||
const databaseWeaponsHref = $derived(localizeHref('/database/weapons'))
|
const databaseWeaponsHref = $derived(localizeHref('/database/weapons'))
|
||||||
|
|
@ -43,11 +60,29 @@
|
||||||
// Database route detection
|
// Database route detection
|
||||||
const isDatabaseRoute = $derived($page.url.pathname.startsWith(localizeHref('/database')))
|
const isDatabaseRoute = $derived($page.url.pathname.startsWith(localizeHref('/database')))
|
||||||
|
|
||||||
|
// Function to check if a nav item is selected
|
||||||
|
function isNavSelected(href: string): boolean {
|
||||||
|
const path = $page.url.pathname
|
||||||
|
|
||||||
|
// For gallery/teams, we need to check for /teams paths
|
||||||
|
if (href === galleryHref) {
|
||||||
|
return path === href || path.startsWith(localizeHref('/teams'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exact match or starts with href + /
|
||||||
|
return path === href || path.startsWith(href + '/')
|
||||||
|
}
|
||||||
|
|
||||||
// Function to check if a database nav item is selected
|
// Function to check if a database nav item is selected
|
||||||
function isDatabaseNavSelected(href: string): boolean {
|
function isDatabaseNavSelected(href: string): boolean {
|
||||||
return $page.url.pathname === href || $page.url.pathname.startsWith(href + '/')
|
return $page.url.pathname === href || $page.url.pathname.startsWith(href + '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the user profile link is selected
|
||||||
|
const isProfileSelected = $derived(
|
||||||
|
isAuth && ($page.url.pathname === meHref || $page.url.pathname === localizeHref(`/${username}`))
|
||||||
|
)
|
||||||
|
|
||||||
// Handle logout
|
// Handle logout
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -66,7 +101,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav aria-label="Global">
|
<nav aria-label="Global" class={elementClass}>
|
||||||
{#if isDatabaseRoute}
|
{#if isDatabaseRoute}
|
||||||
<!-- Database navigation mode -->
|
<!-- Database navigation mode -->
|
||||||
<div class="database-nav">
|
<div class="database-nav">
|
||||||
|
|
@ -107,15 +142,40 @@
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Normal navigation mode -->
|
<!-- Normal navigation mode -->
|
||||||
<ul role="list">
|
<ul role="list">
|
||||||
<li><a href={galleryHref}>{m.nav_gallery()}</a></li>
|
<li>
|
||||||
<li><a href={guidesHref}>Guides</a></li>
|
<a href={galleryHref} class:selected={isNavSelected(galleryHref)}>{m.nav_gallery()}</a>
|
||||||
<li><a href={collectionHref}>{m.nav_collection()}</a></li>
|
</li>
|
||||||
|
<li><a href={guidesHref} class:selected={isNavSelected(guidesHref)}>Guides</a></li>
|
||||||
|
<li>
|
||||||
|
<a href={collectionHref} class:selected={isNavSelected(collectionHref)}
|
||||||
|
>{m.nav_collection()}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
{#if isAuth}
|
{#if isAuth}
|
||||||
<a href={meHref} aria-label="Your account">{username}</a>
|
<a
|
||||||
|
href={meHref}
|
||||||
|
class:selected={isProfileSelected}
|
||||||
|
aria-label="Your account"
|
||||||
|
class="profile-link"
|
||||||
|
>
|
||||||
|
{#if avatarSrc}
|
||||||
|
<img
|
||||||
|
src={avatarSrc}
|
||||||
|
srcset={avatarSrcSet}
|
||||||
|
alt={username}
|
||||||
|
class="user-avatar"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<span>{username}</span>
|
||||||
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a href={loginHref} aria-label="Login">{m.nav_login()}</a>
|
<a href={loginHref} class:selected={isNavSelected(loginHref)} aria-label="Login"
|
||||||
|
>{m.nav_login()}</a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
@ -145,15 +205,17 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
icon="plus"
|
icon="plus"
|
||||||
iconOnly
|
iconOnly
|
||||||
shape="circle"
|
shape="circle"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
class="new-team-button"
|
element={userElement}
|
||||||
aria-label="New team"
|
elementStyle={Boolean(userElement)}
|
||||||
href={newTeamHref}
|
class="new-team-button"
|
||||||
/>
|
aria-label="New team"
|
||||||
|
href={newTeamHref}
|
||||||
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -191,9 +253,15 @@
|
||||||
border-radius: layout.$full-corner;
|
border-radius: layout.$full-corner;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
gap: spacing.$unit-quarter;
|
||||||
padding: spacing.$unit-half;
|
padding: spacing.$unit-half;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
border-radius: layout.$full-corner;
|
border-radius: layout.$full-corner;
|
||||||
color: var(--menu-text);
|
color: var(--menu-text);
|
||||||
|
|
@ -274,7 +342,7 @@
|
||||||
background-color: var(--menu-bg);
|
background-color: var(--menu-bg);
|
||||||
border-radius: layout.$full-corner;
|
border-radius: layout.$full-corner;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: spacing.$unit-half;
|
gap: spacing.$unit-quarter;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: spacing.$unit-half;
|
padding: spacing.$unit-half;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
@ -307,6 +375,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Profile link with avatar
|
||||||
|
.profile-link {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
gap: spacing.$unit-half;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Style the nav buttons to match link dimensions
|
// Style the nav buttons to match link dimensions
|
||||||
:global(.nav-item-button) {
|
:global(.nav-item-button) {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -332,7 +414,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: spacing.$unit (spacing.$unit * 1.5);
|
align-self: stretch; // Make it stretch to full height
|
||||||
|
padding: spacing.$unit calc(spacing.$unit * 1.5 + 1px);
|
||||||
border-radius: layout.$full-corner;
|
border-radius: layout.$full-corner;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--menu-text);
|
color: var(--menu-text);
|
||||||
|
|
@ -357,6 +440,62 @@
|
||||||
// The Button component already handles size, shape, colors, etc.
|
// The Button component already handles size, shape, colors, etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Element-specific SELECTED states for navigation links
|
||||||
|
// Hover states remain the default grey (defined in base styles above)
|
||||||
|
nav.element-wind {
|
||||||
|
ul a.selected,
|
||||||
|
.database-nav a.selected {
|
||||||
|
background-color: var(--wind-nav-selected-bg);
|
||||||
|
color: var(--wind-nav-selected-text);
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.element-fire {
|
||||||
|
ul a.selected,
|
||||||
|
.database-nav a.selected {
|
||||||
|
background-color: var(--fire-nav-selected-bg);
|
||||||
|
color: var(--fire-nav-selected-text);
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.element-water {
|
||||||
|
ul a.selected,
|
||||||
|
.database-nav a.selected {
|
||||||
|
background-color: var(--water-nav-selected-bg);
|
||||||
|
color: var(--water-nav-selected-text);
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.element-earth {
|
||||||
|
ul a.selected,
|
||||||
|
.database-nav a.selected {
|
||||||
|
background-color: var(--earth-nav-selected-bg);
|
||||||
|
color: var(--earth-nav-selected-text);
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.element-dark {
|
||||||
|
ul a.selected,
|
||||||
|
.database-nav a.selected {
|
||||||
|
background-color: var(--dark-nav-selected-bg);
|
||||||
|
color: var(--dark-nav-selected-text);
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.element-light {
|
||||||
|
ul a.selected,
|
||||||
|
.database-nav a.selected {
|
||||||
|
background-color: var(--light-nav-selected-bg);
|
||||||
|
color: var(--light-nav-selected-text);
|
||||||
|
font-weight: typography.$bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dropdown menu styles
|
// Dropdown menu styles
|
||||||
:global(.dropdown-content) {
|
:global(.dropdown-content) {
|
||||||
background-color: var(--menu-bg);
|
background-color: var(--menu-bg);
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,11 @@
|
||||||
<div class="main-pane">
|
<div class="main-pane">
|
||||||
<div class="nav-blur-background"></div>
|
<div class="nav-blur-background"></div>
|
||||||
<div class="main-navigation">
|
<div class="main-navigation">
|
||||||
<Navigation isAuthenticated={data?.isAuthenticated} username={data?.account?.username} role={data?.account?.role} />
|
<Navigation
|
||||||
|
isAuthenticated={data?.isAuthenticated}
|
||||||
|
account={data?.account}
|
||||||
|
currentUser={data?.currentUser}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|
@ -103,7 +107,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 80px; // Taller to test the progressive effect
|
height: 81px; // Matches $nav-height
|
||||||
z-index: 1; // Lower z-index so scrollbar appears above
|
z-index: 1; // Lower z-index so scrollbar appears above
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: right $duration-slide ease-in-out;
|
transition: right $duration-slide ease-in-out;
|
||||||
|
|
@ -153,7 +157,7 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-top: 80px; // Space for fixed navigation (matching test height)
|
padding-top: 81px; // Space for fixed navigation (matches $nav-height)
|
||||||
z-index: 2; // Ensure scrollbar is above blur background
|
z-index: 2; // Ensure scrollbar is above blur background
|
||||||
|
|
||||||
// Smooth scrolling
|
// Smooth scrolling
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue