UX+Refactoring: use side-drawer for menu, reorder menu structure

This commit is contained in:
Pieter Vander Vennet 2024-08-29 02:46:51 +02:00
parent 8465b59c7f
commit 124e816abe
25 changed files with 645 additions and 1059 deletions

View file

@ -1,48 +0,0 @@
<script lang="ts">
import { Store } from "../../Logic/UIEventSource"
import { onDestroy, onMount } from "svelte"
let elem: HTMLElement
let targetOuter: HTMLElement
export let isOpened: Store<boolean>
export let moveTo: Store<HTMLElement>
export let debug: string
function copySizeOf(htmlElem: HTMLElement) {
const target = htmlElem.getBoundingClientRect()
elem.style.left = target.x + "px"
elem.style.top = target.y + "px"
elem.style.width = target.width + "px"
elem.style.height = target.height + "px"
}
function animate(opened: boolean) {
const moveToElem = moveTo.data
if (opened) {
copySizeOf(targetOuter)
elem.style.background = "var(--background-color)"
} else if (moveToElem !== undefined) {
copySizeOf(moveToElem)
elem.style.background = "#ffffff00"
} else {
elem.style.left = "0px"
elem.style.top = "0px"
elem.style.background = "#ffffff00"
}
}
onDestroy(isOpened.addCallback((opened) => animate(opened)))
onMount(() => requestAnimationFrame(() => animate(isOpened.data)))
</script>
<div class={"pointer-events-none invisible absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"}>
<div class="content h-full" bind:this={targetOuter} style="background: red" />
</div>
<div
bind:this={elem}
class="low-interaction pointer-events-none absolute bottom-0 right-0 rounded-2xl"
style="transition: all 0.5s ease-out, background-color 1.4s ease-out; background: var(--background-color);"
>
<!-- Classes should be the same as the 'floatoaver' -->
</div>

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { Drawer } from "flowbite-svelte"
import { sineIn } from "svelte/easing"
import { UIEventSource } from "../../Logic/UIEventSource.js"
export let shown: UIEventSource<boolean>;
let transitionParams = {
x: -320,
duration: 200,
easing: sineIn
};
</script>
<Drawer placement="left" transitionType="fly" transitionParams={transitionParams} hidden={!$shown} on:close={() => shown.set(false)}>
<slot>
CONTENTS
</slot>
</Drawer>

View file

@ -14,15 +14,10 @@
export let arialabel: Translation = undefined
export let arialabelDynamic: Store<Translation> = new ImmutableStore(arialabel)
let arialabelString = arialabelDynamic.bind((tr) => tr?.current)
export let htmlElem: UIEventSource<HTMLElement> = undefined
let _htmlElem: HTMLElement
$: {
htmlElem?.setData(_htmlElem)
}
</script>
<button
bind:this={_htmlElem}
on:click={(e) => dispatch("click", e)}
on:keydown
use:ariaLabelStore={arialabelString}

38
src/UI/Base/Page.svelte Normal file
View file

@ -0,0 +1,38 @@
<script lang="ts">
// A fake 'page' which can be shown; kind of a modal
import { UIEventSource } from "../../Logic/UIEventSource"
import { Modal } from "flowbite-svelte"
export let shown: UIEventSource<boolean>
let _shown = false
export let onlyLink: boolean = false
shown.addCallbackAndRun(sh => {
_shown = sh
})
export let fullscreen: boolean = false
const shared = "defaultClass normal-background dark:bg-gray-800 rounded-lg border-gray-200 dark:border-gray-700 border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md"
let defaultClass = "relative flex flex-col mx-auto w-full divide-y " + shared
if (fullscreen) {
defaultClass = shared
}
let dialogClass = "fixed top-0 start-0 end-0 h-modal inset-0 z-50 w-full p-4 flex";
if(fullscreen){
dialogClass += " h-full-child"
}
let bodyClass = "h-full p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain"
</script>
{#if !onlyLink}
<Modal open={_shown} on:close={() => shown.set(false)} size="xl" {defaultClass} {bodyClass} {dialogClass} color="none">
<slot name="header" slot="header" />
<slot />
{#if $$slots.footer}
<slot name="footer" />
{/if}
</Modal>
{:else}
<button class="as-link" on:click={() => shown.setData(true)}>
<slot name="header" />
</button>
{/if}

View file

@ -1,197 +0,0 @@
<script lang="ts">
/**
* Thin wrapper around 'TabGroup' which binds the state
*/
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import { twJoin } from "tailwind-merge"
/**
* If a condition is given for a certain tab, it will only be shown if this condition is true.
* E.g.
* condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
*/
const tr = new ImmutableStore(true)
export let condition0: Store<boolean> = tr
export let condition1: Store<boolean> = tr
export let condition2: Store<boolean> = tr
export let condition3: Store<boolean> = tr
export let condition4: Store<boolean> = tr
export let condition5: Store<boolean> = tr
export let condition6: Store<boolean> = tr
export let tab: UIEventSource<number> = new UIEventSource<number>(0)
let tabElements: HTMLElement[] = []
$: tabElements[$tab]?.click()
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
}
}
export function getTab() {
return tab
}
</script>
<div class="tabbedgroup flex h-full w-full">
<TabGroup
class="flex h-full w-full flex-col"
defaultIndex={1}
on:change={(e) => {
if (e.detail >= 0) {
tab.setData(e.detail)
}
}}
>
<div class="interactive sticky top-0 flex items-center justify-between">
<TabList class="flex flex-wrap">
{#if $$slots.title0}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}
>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">Tab 0</slot>
</div>
</Tab>
{/if}
{#if $$slots.title1}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}
>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1" />
</div>
</Tab>
{/if}
{#if $$slots.title2}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}
>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2" />
</div>
</Tab>
{/if}
{#if $$slots.title3}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}
>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3" />
</div>
</Tab>
{/if}
{#if $$slots.title4}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}
>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4" />
</div>
</Tab>
{/if}
{#if $$slots.title5}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition5 && "hidden")}
>
<div bind:this={tabElements[5]} class="flex">
<slot name="title5" />
</div>
</Tab>
{/if}
{#if $$slots.title6}
<Tab
class={({ selected }) => twJoin("tab", selected && "primary", !$condition6 && "hidden")}
>
<div bind:this={tabElements[6]} class="flex">
<slot name="title6" />
</div>
</Tab>
{/if}
</TabList>
<slot name="post-tablist" />
</div>
<div class="normal-background h-full overflow-y-auto">
<TabPanels class="tabpanels" defaultIndex={$tab}>
<TabPanel class="tabpanel">
<slot name="content0">
<div>Empty</div>
</slot>
</TabPanel>
<TabPanel class="tabpanel">
<slot name="content1">
<div />
</slot>
</TabPanel>
<TabPanel class="tabpanel">
<slot name="content2">
<div />
</slot>
</TabPanel>
<TabPanel class="tabpanel">
<slot name="content3">
<div />
</slot>
</TabPanel>
<TabPanel class="tabpanel">
<slot name="content4">
<div />
</slot>
</TabPanel>
<TabPanel class="tabpanel">
<slot name="content5">
<div />
</slot>
</TabPanel>
<TabPanel class="tabpanel">
<slot name="content6">
<div />
</slot>
</TabPanel>
</TabPanels>
</div>
</TabGroup>
</div>
<style>
.tabbedgroup {
max-height: 100vh;
height: 100%;
}
:global(.tabpanel) {
height: 100%;
}
:global(.tabpanels) {
height: calc(100% - 2rem);
}
:global(.tab) {
margin: 0.25rem;
padding: 0.25rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border-radius: 1rem;
}
:global(.tab .flex) {
align-items: center;
gap: 0.25rem;
}
:global(.tab span|div) {
align-items: center;
gap: 0.25rem;
display: flex;
}
:global(.tab-selected svg) {
fill: var(--catch-detail-color-contrast);
}
:global(.tab-unselected) {
background-color: var(--background-color) !important;
color: var(--foreground-color) !important;
}
</style>

View file

@ -6,4 +6,7 @@
<div class="flex h-full flex-col overflow-auto border-b-2 p-4">
<slot />
</div>
<slot class="border-t-gray-300 mt-1" name="footer" />
</div>