Navbars are still messed up
This commit is contained in:
38
src/lib/components/util/iconButton.svelte
Normal file
38
src/lib/components/util/iconButton.svelte
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
52
src/lib/components/util/slider.svelte
Normal file
52
src/lib/components/util/slider.svelte
Normal 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>
|
||||||
29
src/lib/components/util/toggle.svelte
Normal file
29
src/lib/components/util/toggle.svelte
Normal 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>
|
||||||
50
src/lib/components/util/volumeSlider.svelte
Normal file
50
src/lib/components/util/volumeSlider.svelte
Normal 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.
@@ -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)
|
||||||
|
|||||||
@@ -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,111 +34,65 @@
|
|||||||
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">
|
/>
|
||||||
{#key data.urlPathname}
|
<section class="no-scrollbar relative overflow-y-scroll">
|
||||||
<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">
|
{#key currentPathname}
|
||||||
<slot />
|
<div
|
||||||
</div>
|
in:fly={{ y: -50 * directionMultiplier, duration: pageTransitionTime, delay: pageTransitionTime }}
|
||||||
{/key}
|
out:fly={{ y: 50 * directionMultiplier, duration: pageTransitionTime }}
|
||||||
</section>
|
class="absolute w-full pr-[5vw] pt-16"
|
||||||
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
|
>
|
||||||
<!-- <MiniPlayer displayMode={'horizontal'} /> -->
|
<slot />
|
||||||
</footer>
|
</div>
|
||||||
</div>
|
{/key}
|
||||||
|
</section>
|
||||||
<!-- {:else}
|
<footer class="fixed bottom-0 flex w-full flex-col items-center justify-center">
|
||||||
|
<!-- <MiniPlayer displayMode={'horizontal'} /> -->
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
{: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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
22
src/routes/settings/+layout.svelte
Normal file
22
src/routes/settings/+layout.svelte
Normal 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>
|
||||||
5
src/routes/settings/+page.svelte
Normal file
5
src/routes/settings/+page.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import NavMenu from './navMenu.svelte'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<NavMenu />
|
||||||
20
src/routes/settings/navMenu.svelte
Normal file
20
src/routes/settings/navMenu.svelte
Normal 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>
|
||||||
Reference in New Issue
Block a user