Gonna try to start using git properly
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<script>
|
||||
export let alertType
|
||||
export let alertMessage
|
||||
import { onMount } from 'svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { fly } from 'svelte/transition'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
let show = false
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const bgColors = {
|
||||
info: 'bg-neutral-500',
|
||||
success: 'bg-emerald-500',
|
||||
caution: 'bg-amber-500',
|
||||
warning: 'bg-red-500',
|
||||
}
|
||||
|
||||
export const triggerClose = () => {
|
||||
show = false
|
||||
dispatch('closeAlert')
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
show = true
|
||||
setTimeout(() => triggerClose(), 10000)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<div in:fly={{ duration: 300, x: 500 }} out:slide={{ duration: 300, axis: 'y' }} class="py-1">
|
||||
<div class="flex gap-1 overflow-hidden rounded-md">
|
||||
<div class="flex min-h-[3.5rem] w-full items-center px-4 py-2 {bgColors[alertType]}">
|
||||
{alertMessage}
|
||||
</div>
|
||||
<button class="w-16 {bgColors[alertType]}" on:click={() => triggerClose()}>
|
||||
<i class="fa-solid fa-x" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,30 @@
|
||||
<script>
|
||||
import Alert from './alert.svelte'
|
||||
|
||||
let alertBox
|
||||
let alertQueue = []
|
||||
|
||||
export const addAlert = (alertType, alertMessage) => {
|
||||
if (alertQueue.length > 5) {
|
||||
alertQueue[0].triggerClose()
|
||||
}
|
||||
|
||||
const alert = new Alert({
|
||||
target: alertBox,
|
||||
props: {
|
||||
alertType: alertType,
|
||||
alertMessage: alertMessage,
|
||||
},
|
||||
})
|
||||
|
||||
alert.$on('closeAlert', () => {
|
||||
const index = alertQueue.indexOf(alert)
|
||||
if (index > -1) alertQueue.splice(index, 1)
|
||||
setTimeout(() => alert.$destroy(), 300)
|
||||
})
|
||||
|
||||
alertQueue.push(alert)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={alertBox} class="fixed right-4 top-4 z-50 max-h-screen w-full max-w-sm overflow-hidden"></div>
|
||||
@@ -0,0 +1,68 @@
|
||||
<script>
|
||||
import { fade, slide } from 'svelte/transition'
|
||||
import { spin } from '$lib/utils/animations'
|
||||
import { page } from '$app/stores'
|
||||
|
||||
let button,
|
||||
icon,
|
||||
open = false
|
||||
|
||||
$: $page.url, closeMenu()
|
||||
const closeMenu = () => {
|
||||
if (button && open) {
|
||||
button.animate(spin(), 400)
|
||||
open = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative aspect-square h-full">
|
||||
<button
|
||||
bind:this={button}
|
||||
id="button"
|
||||
class="grid h-full w-full place-items-center transition-transform duration-75 active:scale-90"
|
||||
on:click={() => {
|
||||
button.animate(spin(), 400)
|
||||
open = !open
|
||||
}}
|
||||
>
|
||||
{#if open}
|
||||
<i id="menu-icon" transition:fade={{ duration: 300 }} bind:this={icon} class="fa-solid fa-xmark" />
|
||||
{:else}
|
||||
<i id="menu-icon" transition:fade={{ duration: 300 }} bind:this={icon} class="fa-solid fa-bars" />
|
||||
{/if}
|
||||
</button>
|
||||
{#if open}
|
||||
<section transition:slide={{ duration: 200, axis: 'y' }} id="dropdown" class="absolute w-screen max-w-sm">
|
||||
<slot name="menu-items" />
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#dropdown {
|
||||
top: calc(100% + 0.6rem);
|
||||
}
|
||||
#button::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;
|
||||
}
|
||||
#menu-icon {
|
||||
font-size: 1.5rem;
|
||||
position: absolute;
|
||||
transition: color 200ms;
|
||||
}
|
||||
#button:hover > i {
|
||||
color: var(--lazuli-primary);
|
||||
}
|
||||
#button:hover::before {
|
||||
width: 130%;
|
||||
height: 130%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<button class="relative grid aspect-square h-full place-items-center transition-transform duration-75 active:scale-90" on:click={() => dispatch('click')}>
|
||||
<slot name="icon" />
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button::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:hover::before {
|
||||
width: 130%;
|
||||
height: 130%;
|
||||
}
|
||||
button :global(> :first-child) {
|
||||
transition: color 200ms;
|
||||
}
|
||||
button:hover :global(> :first-child) {
|
||||
color: var(--lazuli-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script>
|
||||
import HamburgerMenu from './hamburgerMenu.svelte'
|
||||
import IconButton from './iconButton.svelte'
|
||||
import SearchBar from './searchBar.svelte'
|
||||
import { goto, afterNavigate } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
let previousPage = null
|
||||
afterNavigate(({ from }) => {
|
||||
if (from) previousPage = from.url
|
||||
})
|
||||
|
||||
let windowY = 0
|
||||
</script>
|
||||
|
||||
<svelte:window bind:scrollY={windowY} />
|
||||
<nav id="navbar" class="fixed left-0 top-0 isolate z-10 h-16 w-full">
|
||||
<div class="grid h-full grid-cols-3">
|
||||
<div class="flex h-full items-center gap-5 py-4 pl-6">
|
||||
<HamburgerMenu>
|
||||
<ol slot="menu-items" class="overflow-hidden rounded-lg border-2 border-neutral-800 bg-neutral-925 p-2">
|
||||
<li>
|
||||
<button class="w-full rounded-md px-3 py-2 text-left hover:bg-neutral-900" on:click={() => goto('/settings')}>
|
||||
<i class="fa-solid fa-gear mr-1" />
|
||||
Settings
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
</HamburgerMenu>
|
||||
{#if previousPage && $page.url.pathname !== '/'}
|
||||
<IconButton on:click={() => history.back()}>
|
||||
<i slot="icon" class="fa-solid fa-arrow-left text-xl" />
|
||||
</IconButton>
|
||||
{/if}
|
||||
{#if $page.url.pathname !== '/'}
|
||||
<IconButton on:click={() => goto('/')}>
|
||||
<i slot="icon" class="fa-solid fa-house text-xl" />
|
||||
</IconButton>
|
||||
{/if}
|
||||
</div>
|
||||
<SearchBar />
|
||||
</div>
|
||||
{#if windowY > 0}
|
||||
<div transition:fade={{ duration: 150 }} id="navbar-background" class="absolute left-0 top-0 -z-10 h-full w-full bg-neutral-925" />
|
||||
<!-- This would be a cool place for personalization -->
|
||||
{/if}
|
||||
</nav>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script>
|
||||
let searchBar, searchInput
|
||||
let searchOpen = false
|
||||
|
||||
let searchRecommendations = null
|
||||
const toggleSearchMenu = (open) => {
|
||||
searchOpen = open
|
||||
|
||||
if (open) {
|
||||
searchBar.style.borderColor = 'rgb(100, 100, 100)'
|
||||
searchRecommendations = ['Psycho Lily', 'Iceborn', 'HYPER4ID', 'Parousia', 'Ragnarok', 'Betwixt & Between']
|
||||
} else {
|
||||
searchBar.style.borderColor = 'transparent'
|
||||
searchRecommendations = null
|
||||
}
|
||||
}
|
||||
|
||||
const triggerSearch = (searchQuery) => {
|
||||
console.log(`Search for: ${searchQuery}`)
|
||||
// Redirect To '/search' route with query parameter '?query=searchQuery'
|
||||
}
|
||||
</script>
|
||||
|
||||
<search
|
||||
role="search"
|
||||
bind:this={searchBar}
|
||||
class="relative my-2.5 flex w-full items-center gap-2.5 justify-self-center rounded-full border-2 border-transparent px-4 py-1.5"
|
||||
on:focusout={() => {
|
||||
setTimeout(() => {
|
||||
// This is a completely stupid thing you have to do, if there is not timeout, the active element will be the body of the document and not the newly focused element
|
||||
if (!searchBar.contains(document.activeElement)) {
|
||||
toggleSearchMenu(false)
|
||||
}
|
||||
}, 1)
|
||||
}}
|
||||
>
|
||||
<button
|
||||
on:click|preventDefault={(event) => {
|
||||
if (searchInput.value.trim() === '') {
|
||||
if (event.pointerType === 'mouse') toggleSearchMenu(!searchOpen)
|
||||
if (searchOpen) searchInput.focus()
|
||||
} else {
|
||||
triggerSearch(searchInput.value)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i class="fa-solid fa-magnifying-glass transition-colors duration-200 hover:text-lazuli-primary" />
|
||||
</button>
|
||||
<input
|
||||
bind:this={searchInput}
|
||||
type="text"
|
||||
name="search"
|
||||
class="w-full bg-transparent outline-none"
|
||||
placeholder="Let's find some music"
|
||||
autocomplete="off"
|
||||
on:focus={() => toggleSearchMenu(true)}
|
||||
on:keypress={(event) => {
|
||||
if (event.key === 'Enter') triggerSearch(searchInput.value)
|
||||
}}
|
||||
/>
|
||||
{#if searchRecommendations}
|
||||
<div class="absolute left-0 top-full flex w-full flex-col bg-neutral-950">
|
||||
{#each searchRecommendations as recommendation}
|
||||
<button class="w-full p-4 text-left" on:click|preventDefault={() => triggerSearch(recommendation)}>
|
||||
{recommendation}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</search>
|
||||
|
||||
<style>
|
||||
search {
|
||||
background-color: rgba(100, 100, 100, 0.25);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<script>
|
||||
export let toggled = false
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let toggle, knob
|
||||
|
||||
const handleToggle = () => {
|
||||
toggled = !toggled
|
||||
if (toggled) {
|
||||
toggle.style.backgroundColor = 'var(--lazuli-primary)'
|
||||
knob.style.left = '100%'
|
||||
knob.style.transform = 'translateX(-100%)'
|
||||
} else {
|
||||
toggle.style.backgroundColor = 'rgb(115, 115, 115)'
|
||||
knob.style.left = 0
|
||||
knob.style.transform = ''
|
||||
}
|
||||
dispatch('toggled', {
|
||||
toggleState: toggled,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<button bind:this={toggle} aria-checked={toggle} role="checkbox" class="relative flex h-6 w-10 items-center rounded-full bg-neutral-500 transition-colors" on:click={handleToggle}>
|
||||
<div bind:this={knob} class="absolute left-0 aspect-square h-full p-1 transition-all">
|
||||
<div class="h-full w-full rounded-full bg-white"></div>
|
||||
</div>
|
||||
</button>
|
||||
Reference in New Issue
Block a user