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'
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 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
@@ -56,7 +56,7 @@
<button
class="grid aspect-square w-14 place-items-center text-neutral-400 transition-colors hover:text-lazuli-primary"
on:click={() => {
calculateDirection(tabData.pathname, currentPathname)
direction = calculateDirection(tabData.pathname, currentPathname)
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 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
export const backgroundImage: Writable<string> = writable(youtubeMusicBackground)