Navbars are still messed up

This commit is contained in:
Eclypsed
2024-01-27 01:38:04 -05:00
parent 1209b6ac25
commit bee4c903ec
15 changed files with 380 additions and 103 deletions

View File

@@ -0,0 +1,38 @@
<script lang="ts">
export let disabled = false
export let halo = false
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
</script>
<button class:disabled class:halo class="relative grid aspect-square h-full place-items-center transition-transform duration-75 active:scale-90" on:click|preventDefault={() => dispatch('click')} {disabled}>
<slot name="icon" />
</button>
<style>
button.disabled {
color: rgb(82, 82, 82);
}
button:not(.disabled).halo::before {
content: '';
width: 0;
height: 0;
background-color: color-mix(in srgb, var(--lazuli-primary) 20%, transparent);
border-radius: 100%;
transition-property: width height;
transition-duration: 200ms;
position: absolute;
}
button:not(.disabled).halo:hover::before {
width: 130%;
height: 130%;
}
button :global(> :first-child) {
transition: color 200ms;
}
button:not(.disabled):hover :global(> :first-child) {
color: var(--lazuli-primary);
}
</style>

View File

@@ -0,0 +1,75 @@
<script context="module" lang="ts">
export interface NavTab {
pathname: string
name: string
icon: string
}
</script>
<script lang="ts">
export let navTabs: NavTab[]
export let currentPathname: string
export let transitionTime: number = 200
import { fade } from 'svelte/transition'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
type PageTransitionDirection = 'left' | 'right'
let direction: PageTransitionDirection = 'right'
const calculateDirection = (newPage: string, currentPage: string): PageTransitionDirection => {
const newPageIndex = navTabs.findIndex((tab) => tab.pathname === newPage)
const currentPageIndex = navTabs.findIndex((tab) => tab.pathname === currentPage)
return newPageIndex > currentPageIndex ? 'right' : 'left'
}
let activeTab: HTMLButtonElement, indicatorBar: HTMLDivElement, tabList: HTMLDivElement
$: calculateBar(activeTab)
const calculateBar = (activeTab: HTMLButtonElement) => {
if (activeTab && indicatorBar && tabList) {
const listRect = tabList.getBoundingClientRect()
const tabRec = activeTab.getBoundingClientRect()
if (direction === 'right') {
indicatorBar.style.right = `${listRect.right - tabRec.right}px`
setTimeout(() => (indicatorBar.style.left = `${tabRec.left - listRect.left}px`), transitionTime)
} else {
indicatorBar.style.left = `${tabRec.left - listRect.left}px`
setTimeout(() => (indicatorBar.style.right = `${listRect.right - tabRec.right}px`), transitionTime)
}
}
}
</script>
<div bind:this={tabList} id="bottom-tab-list" class="relative flex w-full items-center justify-around bg-black">
{#each navTabs as tabData}
{#if currentPathname === tabData.pathname}
<button bind:this={activeTab} class="pointer-events-none text-white transition-colors" disabled>
<i class={tabData.icon} />
</button>
{:else}
<button
class="text-neutral-400 transition-colors hover:text-lazuli-primary"
on:click={() => {
direction = calculateDirection(tabData.pathname, currentPathname)
dispatch('navigate', { direction, pathname: tabData.pathname })
}}
>
<i class={tabData.icon} />
</button>
{/if}
{/each}
{#if navTabs.some((tab) => tab.pathname === currentPathname)}
<div bind:this={indicatorBar} transition:fade class="absolute bottom-0 h-1 rounded-full bg-white transition-all duration-300 ease-in-out" />
{/if}
</div>
<style>
#bottom-tab-list {
padding: 16px 0px;
font-size: 20px;
line-height: 28px;
}
</style>

View File

@@ -19,10 +19,10 @@
type PageTransitionDirection = 'up' | 'down' type PageTransitionDirection = 'up' | 'down'
let direction: PageTransitionDirection = 'down' let direction: PageTransitionDirection = 'down'
const calculateDirection = (newPage: string, currentPage: string): void => { const calculateDirection = (newPage: string, currentPage: string): PageTransitionDirection => {
const newPageIndex = navTabs.findIndex((tab) => tab.pathname === newPage) const newPageIndex = navTabs.findIndex((tab) => tab.pathname === newPage)
const currentPageIndex = navTabs.findIndex((tab) => tab.pathname === currentPage) const currentPageIndex = navTabs.findIndex((tab) => tab.pathname === currentPage)
newPageIndex > currentPageIndex ? (direction = 'down') : (direction = 'up') return newPageIndex > currentPageIndex ? 'down' : 'up'
} }
let activeTab: HTMLButtonElement, indicatorBar: HTMLDivElement, tabList: HTMLDivElement let activeTab: HTMLButtonElement, indicatorBar: HTMLDivElement, tabList: HTMLDivElement
@@ -56,7 +56,7 @@
<button <button
class="grid aspect-square w-14 place-items-center text-neutral-400 transition-colors hover:text-lazuli-primary" class="grid aspect-square w-14 place-items-center text-neutral-400 transition-colors hover:text-lazuli-primary"
on:click={() => { on:click={() => {
calculateDirection(tabData.pathname, currentPathname) direction = calculateDirection(tabData.pathname, currentPathname)
dispatch('navigate', { direction, pathname: tabData.pathname }) dispatch('navigate', { direction, pathname: tabData.pathname })
}} }}
> >

View File

@@ -0,0 +1,52 @@
<script lang="ts">
export let value = 0
let sliderThumb: HTMLSpanElement, sliderTrail: HTMLSpanElement
const trackThumb = (sliderPos: number): void => {
if (sliderThumb) sliderThumb.style.left = `${sliderPos}%`
if (sliderTrail) sliderTrail.style.right = `${100 - sliderPos}%`
}
$: trackThumb(value)
const handleKeyPress = (key: string) => {
if ((key === 'ArrowRight' || key === 'ArrowUp') && value < 100) return (value += 1)
if ((key === 'ArrowLeft' || key === 'ArrowDown') && value > 0) return (value -= 1)
}
</script>
<div
id="slider-track"
class="relative isolate h-1 w-full rounded-full bg-neutral-600"
role="slider"
tabindex="0"
aria-valuenow={value}
aria-valuemin="0"
aria-valuemax="100"
on:keydown={(event) => handleKeyPress(event.key)}
>
<input type="range" class="absolute z-10 h-1 w-full" step="any" min="0" max="100" bind:value tabindex="-1" aria-hidden="true" aria-disabled="true" />
<span bind:this={sliderTrail} id="slider-trail" class="absolute left-0 h-1 rounded-full bg-white transition-colors" />
<span bind:this={sliderThumb} id="slider-thumb" class="absolute top-1/2 aspect-square h-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 transition-opacity duration-300" />
</div>
<style>
input[type='range'] {
appearance: none;
cursor: pointer;
opacity: 0;
}
#slider-track:hover > #slider-trail {
background-color: var(--lazuli-primary);
}
#slider-track:focus > #slider-trail {
background-color: var(--lazuli-primary);
}
#slider-track:hover > #slider-thumb {
opacity: 1;
}
#slider-track:focus > #slider-thumb {
opacity: 1;
}
</style>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
export let toggled = false
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
const handleToggle = (): void => {
toggled = !toggled
dispatch('toggled', { toggled })
}
</script>
<button class:toggled aria-checked={toggled} role="checkbox" class="relative flex h-6 w-10 items-center rounded-full bg-neutral-500 transition-colors" on:click={handleToggle}>
<div class:toggled class="absolute left-0 aspect-square h-full p-1 transition-all">
<div class="grid h-full w-full place-items-center rounded-full bg-white text-xs">
<i class={toggled ? 'fa-solid fa-check text-lazuli-primary' : 'fa-solid fa-xmark text-neutral-500'} />
</div>
</div>
</button>
<style>
button.toggled {
background-color: var(--lazuli-primary);
}
div.toggled {
left: 100%;
transform: translateX(-100%);
}
</style>

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import Slider from '$lib/components/util/slider.svelte'
import IconButton from '$lib/components/util/iconButton.svelte'
import { onMount } from 'svelte'
export let volume = 0
let muted = false
let storedVolume: number
const getVolume = (): number => {
const currentVolume = localStorage.getItem('volume')
if (currentVolume) return Number(currentVolume)
const defaultVolume = 100
localStorage.setItem('volume', defaultVolume.toString())
return defaultVolume
}
const setVolume = (volume: number): void => {
if (Number.isFinite(volume)) localStorage.setItem('volume', Math.round(volume).toString())
}
onMount(() => (storedVolume = getVolume()))
$: changeVolume(storedVolume)
const changeVolume = (newVolume: number) => {
if (typeof newVolume === 'number' && !isNaN(newVolume)) setVolume(newVolume)
}
$: volume = muted ? 0 : storedVolume
</script>
<div id="volume-slider" class="flex h-10 w-fit flex-shrink-0 flex-row-reverse items-center gap-2">
<IconButton halo={false} on:click={() => (muted = !muted)}>
<i slot="icon" class="fa-solid {volume > 50 ? 'fa-volume-high' : volume > 0 ? 'fa-volume-low' : 'fa-volume-xmark'} w-full text-center text-base" />
</IconButton>
<div id="slider-wrapper" class="w-0 transition-all duration-500">
<Slider bind:value={storedVolume} />
</div>
</div>
<style>
#volume-slider:hover > #slider-wrapper {
width: 6rem;
}
#slider-wrapper:focus-within {
width: 6rem;
}
</style>

Binary file not shown.

View File

@@ -1,11 +1,11 @@
import { writable, type Writable } from 'svelte/store' import { writable, type Writable } from 'svelte/store'
import type { AlertType } from '$lib/components/util/alert.svelte' import type { AlertType } from '$lib/components/util/alert.svelte'
export const pageWidth: Writable<number> = writable(0) export const pageWidth: Writable<number> = writable()
export const newestAlert: Writable<[AlertType, string] | null> = writable(null) export const newestAlert: Writable<[AlertType, string]> = writable()
export const currentlyPlaying = writable(null) export const currentlyPlaying = writable()
const youtubeMusicBackground: string = 'https://www.gstatic.com/youtube/media/ytm/images/sbg/wsbg@4000x2250.png' // Default Youtube music background const youtubeMusicBackground: string = 'https://www.gstatic.com/youtube/media/ytm/images/sbg/wsbg@4000x2250.png' // Default Youtube music background
export const backgroundImage: Writable<string> = writable(youtubeMusicBackground) export const backgroundImage: Writable<string> = writable(youtubeMusicBackground)

View File

@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { fly, fade } from 'svelte/transition' import { fly } from 'svelte/transition'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import { pageWidth } from '$lib/stores' import { pageWidth } from '$lib/stores'
import type { LayoutServerData } from '../$types'
import NavbarSide, { type NavTab } from '$lib/components/util/navbarSide.svelte' import NavbarSide, { type NavTab } from '$lib/components/util/navbarSide.svelte'
import NavbarFoot from '$lib/components/util/navbarFoot.svelte'
import { page } from '$app/stores'
export let data: LayoutServerData let currentPathname = $page.url.pathname
const contentTabs: NavTab[] = [ const contentTabs: NavTab[] = [
{ {
@@ -33,60 +34,28 @@
const pageTransitionTime: number = 200 const pageTransitionTime: number = 200
type PageTransitionDirection = 1 | -1 type PageTransitionDirection = 1 | -1
let direction: PageTransitionDirection = 1 let directionMultiplier: PageTransitionDirection = 1
// $: calculateDirection(data.urlPathname)
// const calculateDirection = (newPage: string): void => {
// const newPageIndex = contentTabs.findIndex((tab) => tab.pathname === newPage)
// const previousPageIndex = contentTabs.findIndex((tab) => tab.pathname === previousPage)
// newPageIndex > previousPageIndex ? (direction = 1) : (direction = -1)
// previousPage = data.urlPathname
// }
// let activeTab: HTMLButtonElement, indicatorBar: HTMLDivElement, tabList: HTMLDivElement
// $: calculateBar(activeTab)
// const calculateBar = (activeTab: HTMLButtonElement) => {
// if (activeTab && indicatorBar && tabList) {
// if ($pageWidth >= 768) {
// const listRect = tabList.getBoundingClientRect()
// const tabRec = activeTab.getBoundingClientRect()
// if (direction === 1) {
// indicatorBar.style.bottom = `${listRect.bottom - tabRec.bottom}px`
// setTimeout(() => (indicatorBar.style.top = `${tabRec.top - listRect.top}px`), pageTransitionTime)
// } else {
// indicatorBar.style.top = `${tabRec.top - listRect.top}px`
// setTimeout(() => (indicatorBar.style.bottom = `${listRect.bottom - tabRec.bottom}px`), pageTransitionTime)
// }
// } else {
// const listRect = tabList.getBoundingClientRect()
// const tabRec = activeTab.getBoundingClientRect()
// if (direction === 1) {
// indicatorBar.style.right = `${listRect.right - tabRec.right}px`
// setTimeout(() => (indicatorBar.style.left = `${tabRec.left - listRect.left}px`), pageTransitionTime)
// } else {
// indicatorBar.style.left = `${tabRec.left - listRect.left}px`
// setTimeout(() => (indicatorBar.style.right = `${listRect.right - tabRec.right}px`), pageTransitionTime)
// }
// }
// }
// }
</script> </script>
<!-- {#if $pageWidth >= 768} --> {#if $pageWidth >= 768}
<div id="content-grid" class="h-full overflow-hidden"> <div id="content-grid" class="grid h-full grid-rows-1 gap-8 overflow-hidden">
<NavbarSide <NavbarSide
navTabs={contentTabs} navTabs={contentTabs}
currentPathname={data.urlPathname} {currentPathname}
transitionTime={pageTransitionTime} transitionTime={pageTransitionTime}
on:navigate={(event) => { on:navigate={(event) => {
event.detail.direction === 'up' ? (direction = 1) : (direction = -1) event.detail.direction === 'up' ? (directionMultiplier = 1) : (directionMultiplier = -1)
goto(event.detail.pathname) currentPathname = event.detail.pathname
goto(currentPathname)
}} }}
/> />
<section class="no-scrollbar relative overflow-y-scroll"> <section class="no-scrollbar relative overflow-y-scroll">
{#key data.urlPathname} {#key currentPathname}
<div in:fly={{ y: -50 * direction, duration: pageTransitionTime, delay: pageTransitionTime }} out:fly={{ y: 50 * direction, duration: pageTransitionTime }} class="absolute w-full pr-[5vw] pt-16"> <div
in:fly={{ y: -50 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
out:fly={{ y: 50 * directionMultiplier, duration: pageTransitionTime }}
class="absolute w-full pr-[5vw] pt-16"
>
<slot /> <slot />
</div> </div>
{/key} {/key}
@@ -94,50 +63,36 @@
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center"> <footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
<!-- <MiniPlayer displayMode={'horizontal'} /> --> <!-- <MiniPlayer displayMode={'horizontal'} /> -->
</footer> </footer>
</div> </div>
{:else}
<!-- {:else}
<div class="h-full overflow-hidden"> <div class="h-full overflow-hidden">
{#key data.urlPathname} {#key currentPathname}
<section <section
in:fly={{ x: 200 * direction, duration: pageTransitionTime, delay: pageTransitionTime }} in:fly={{ x: 200 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
out:fly={{ x: -200 * direction, duration: pageTransitionTime }} out:fly={{ x: -200 * directionMultiplier, duration: pageTransitionTime }}
class="no-scrollbar h-full overflow-y-scroll px-[5vw] pt-16" class="no-scrollbar h-full overflow-y-scroll px-[5vw] pt-16"
> >
<slot /> <slot />
</section> </section>
{/key} {/key}
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center"> <footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
<MiniPlayer displayMode={'vertical'} /> <!-- <MiniPlayer displayMode={'vertical'} /> -->
<div bind:this={tabList} id="bottom-tab-list" class="relative flex w-full items-center justify-around bg-black"> <NavbarFoot
{#each contentTabs as tabData} navTabs={contentTabs}
{#if data.urlPathname === tabData.pathname} {currentPathname}
<button bind:this={activeTab} class="pointer-events-none text-white transition-colors" disabled> transitionTime={pageTransitionTime}
<i class={tabData.icon} /> on:navigate={(event) => {
</button> event.detail.direction === 'right' ? (directionMultiplier = 1) : (directionMultiplier = -1)
{:else} currentPathname = event.detail.pathname
<button class="text-neutral-400 transition-colors hover:text-lazuli-primary" on:click={() => goto(tabData.pathname)}> goto(currentPathname)
<i class={tabData.icon} /> }}
</button> />
{/if}
{/each}
{#if contentTabs.some((tab) => tab.pathname === data.urlPathname)}
<div bind:this={indicatorBar} transition:fade class="absolute bottom-0 h-1 rounded-full bg-white transition-all duration-300 ease-in-out" />
{/if}
</div>
</footer> </footer>
</div> </div>
{/if} --> {/if}
<style> <style>
#content-grid { #content-grid {
display: grid;
grid-template-columns: max-content auto; grid-template-columns: max-content auto;
grid-template-rows: 100%;
}
#bottom-tab-list {
padding: 16px 0px;
font-size: 20px;
line-height: 28px;
} }
</style> </style>

View File

@@ -1 +1,32 @@
<h1>Welcome to the User Page!</h1> <script lang="ts">
import IconButton from '$lib/components/util/iconButton.svelte'
import type { LayoutData } from '../$types'
import { goto } from '$app/navigation'
export let data: LayoutData
</script>
<main class="flex flex-col gap-8">
<section class="flex items-center justify-between text-xl">
<div class="flex items-center gap-4">
<div class="grid aspect-square h-36 place-items-center rounded-full bg-neutral-800">
<i class="fa-solid fa-user text-6xl text-neutral-400" />
</div>
<div>
<div>{data.user.username}</div>
<div class="text-base text-neutral-400">Other info about the user</div>
</div>
</div>
<div class="flex h-10 gap-6">
<IconButton on:click={() => goto('/settings')} halo={true}>
<i slot="icon" class="fa-solid fa-gear" />
</IconButton>
<IconButton halo={true}>
<i slot="icon" class="fa-solid fa-right-from-bracket" />
</IconButton>
</div>
</section>
<section>
<div>This is where things like history would go</div>
</section>
</main>

View File

@@ -1,5 +1,5 @@
import type { LayoutServerLoad } from './$types' import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = ({ url, locals }) => { export const load: LayoutServerLoad = ({ locals }) => {
return { urlPathname: url.pathname, user: locals.user } return { user: locals.user }
} }

View File

@@ -6,7 +6,7 @@
import { fade } from 'svelte/transition' import { fade } from 'svelte/transition'
let alertBox: AlertBox let alertBox: AlertBox
$: if ($newestAlert !== null && alertBox) alertBox.addAlert(...$newestAlert) $: if ($newestAlert && alertBox) alertBox.addAlert(...$newestAlert)
</script> </script>
<svelte:window bind:innerWidth={$pageWidth} /> <svelte:window bind:innerWidth={$pageWidth} />

View File

@@ -0,0 +1,22 @@
<script>
import IconButton from '$lib/components/util/iconButton.svelte'
</script>
<main class="h-full">
<h1 class="sticky top-0 grid grid-cols-[1fr_auto_1fr] grid-rows-1 items-center">
<IconButton on:click={() => history.back()}>
<i slot="icon" class="fa-solid fa-arrow-left text-2xl" />
</IconButton>
<span class="text-3xl">Account</span>
</h1>
<section class="px-[5vw]">
<slot />
</section>
</main>
<style>
h1 {
height: 80px;
padding: 16px 5vw;
}
</style>

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import NavMenu from './navMenu.svelte'
</script>
<NavMenu />

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import IconButton from '$lib/components/util/iconButton.svelte'
</script>
<nav class="flex w-full flex-col divide-y-2 divide-neutral-700 rounded-lg bg-neutral-800 px-4 text-xl">
<button class="flex items-center justify-between px-4 py-6">
<span>
<i class="fa-solid fa-circle-nodes mr-2" />
Connections
</span>
<i class="fa-solid fa-arrow-right" />
</button>
<button class="flex items-center justify-between px-4 py-6">
<span>
<i class="fa-solid fa-mobile-screen mr-2" />
Devices
</span>
<i class="fa-solid fa-arrow-right" />
</button>
</nav>