Styling: style menu- and theme-menu tabbed interface

This commit is contained in:
Pieter Vander Vennet 2023-05-07 23:19:30 +02:00
parent a4a3b8a5ad
commit 14927497bd
6 changed files with 181 additions and 125 deletions

View file

@ -1,20 +1,33 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import {createEventDispatcher} from "svelte";
import {XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
/**
* The slotted element will be shown on top, with a lower-opacity border
*/
const dispatch = createEventDispatcher<{ close }>();
/**
* The slotted element will be shown on top, with a lower-opacity border
*/
const dispatch = createEventDispatcher<{ close }>();
</script>
<div class="absolute top-0 right-0 w-screen h-screen overflow-auto" style="background-color: #00000088">
<div class="flex flex-col m-4 sm:m-6 p-4 sm:p-6 md:m-8 rounded normal-background">
<slot name="close-button">
<div class="w-8 h-8 absolute right-10 top-10 cursor-pointer" on:click={() => dispatch("close")}>
<XCircleIcon />
</div>
</slot>
<slot></slot>
</div>
<div class="absolute top-0 right-0 w-screen h-screen p-4 md:p-6" style="background-color: #00000088">
<div class="content normal-background">
<div class="rounded-xl">
<slot></slot>
</div>
<slot name="close-button">
<!-- The close button is placed _after_ the default slot in order to always paint it on top -->
<div class="w-8 h-8 absolute right-10 top-10 cursor-pointer" on:click={() => dispatch("close")}>
<XCircleIcon/>
</div>
</slot>
</div>
</div>
<style>
.content {
height: calc( 100vh - 2rem );
border-radius: 0.5rem;
overflow-x: auto;
box-shadow: 0 0 1rem #00000088;
}
</style>

View file

@ -1,79 +1,121 @@
<script lang="ts">
/**
* Thin wrapper around 'TabGroup' which binds the state
*/
/**
* Thin wrapper around 'TabGroup' which binds the state
*/
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
import { UIEventSource } from "../../Logic/UIEventSource";
import {Tab, TabGroup, TabList, TabPanel, TabPanels} from "@rgossiaux/svelte-headlessui";
import {UIEventSource} from "../../Logic/UIEventSource";
export let tab: UIEventSource<number>;
let tabElements: HTMLElement[] = [];
$: tabElements[$tab]?.click();
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
export let tab: UIEventSource<number>;
let tabElements: HTMLElement[] = [];
$: tabElements[$tab]?.click();
$: {
if (tabElements[tab.data]) {
window.setTimeout(() => tabElements[tab.data].click(), 50)
}
}
}
</script>
<TabGroup defaultIndex={1} on:change={(e) =>{if(e.detail >= 0){tab.setData( e.detail); }} }>
<TabList>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">
Tab 0
</slot>
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1" />
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2" />
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3" />
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4" />
</div>
</Tab>
</TabList>
<TabPanels defaultIndex={$tab}>
<TabPanel>
<slot name="content0">
<div>Empty</div>
</slot>
</TabPanel>
<TabPanel>
<slot name="content1" />
</TabPanel>
<TabPanel>
<slot name="content2" />
</TabPanel>
<TabPanel>
<slot name="content3" />
</TabPanel>
<TabPanel>
<slot name="content4" />
</TabPanel>
</TabPanels>
</TabGroup>
<div class="tabbedgroup w-full h-full">
<TabGroup class="h-full w-full flex flex-col" defaultIndex={1}
on:change={(e) =>{if(e.detail >= 0){tab.setData( e.detail); }} }>
<div class="flex bg-gray-300 items-center justify-between sticky top-0">
<TabList class="flex flex-wrap">
{#if $$slots.title1}
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
<div bind:this={tabElements[0]} class="flex">
<slot name="title0">
Tab 0
</slot>
</div>
</Tab>
{/if}
{#if $$slots.title1}
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
<div bind:this={tabElements[1]} class="flex">
<slot name="title1"/>
</div>
</Tab>
{/if}
{#if $$slots.title2}
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
<div bind:this={tabElements[2]} class="flex">
<slot name="title2"/>
</div>
</Tab>
{/if}
{#if $$slots.title3}
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
<div bind:this={tabElements[3]} class="flex">
<slot name="title3"/>
</div>
</Tab>
{/if}
{#if $$slots.title4}
<Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}>
<div bind:this={tabElements[4]} class="flex">
<slot name="title4"/>
</div>
</Tab>
{/if}
</TabList>
<slot name="post-tablist"/>
</div>
<div class="overflow-y-auto normal-background">
<TabPanels defaultIndex={$tab}>
<TabPanel>
<slot name="content0">
<div>Empty</div>
</slot>
</TabPanel>
<TabPanel>
<slot name="content1"/>
</TabPanel>
<TabPanel>
<slot name="content2"/>
</TabPanel>
<TabPanel>
<slot name="content3"/>
</TabPanel>
<TabPanel>
<slot name="content4"/>
</TabPanel>
</TabPanels>
</div>
</TabGroup>
</div>
<style>
.tab-selected {
.tabbedgroup {
max-height: 100vh;
height: 100%;
}
: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) {
background-color: rgb(59 130 246);
color: rgb(255 255 255);
}
.tab-unselected {
:global(.tab-unselected) {
background-color: rgb(255 255 255);
color: rgb(0 0 0);
}

View file

@ -29,13 +29,15 @@
{:else}
<UserCircleIcon class="w-12 h-12" />
{/if}
<div class="flex flex-col relative">
<div class="flex flex-col">
<h3>{$userdetails.name}</h3>
{#if description}
<FromHtml src={description} />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank">
<PencilAltIcon class="p-2 w-6 h-6 subtle-background rounded-full absolute right-1 bottom-1" />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="link-no-underline flex subtle-background items-center w-fit self-end">
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
<Tr slot="message" t={Translations.t.userinfo.editDescription} />
</a>
{:else}
<Tr t={Translations.t. userinfo.noDescription} />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="flex subtle-background items-center">

View file

@ -16,7 +16,7 @@
import type {MapProperties} from "../Models/MapProperties";
import Geosearch from "./BigComponents/Geosearch.svelte";
import Translations from "./i18n/Translations";
import {CogIcon, EyeIcon, MenuIcon} from "@rgossiaux/svelte-heroicons/solid";
import {CogIcon, EyeIcon, MenuIcon, XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte";
@ -149,9 +149,12 @@
<If condition={selectedElementView.map(v => v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}>
<ModalRight on:close={() => {selectedElement.setData(undefined)}}>
<div class="absolute flex flex-col h-full w-full normal-background">
<ToSvelte construct={new VariableUiElement(selectedElementTitle)}></ToSvelte>
<ToSvelte construct={new VariableUiElement(selectedElementView)}></ToSvelte>
<ToSvelte construct={new VariableUiElement(selectedElementTitle)}>
<!-- Title -->
</ToSvelte>
<ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}>
<!-- Main view -->
</ToSvelte>
</div>
</ModalRight>
</If>
@ -163,9 +166,14 @@
</If>
<If condition={state.guistate.themeIsOpened}>
<!-- Theme page -->
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<!-- Theme menu -->
<FloatOver>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
<div slot="post-tablist">
<XCircleIcon class="w-8 h-8 mr-2" on:click={() => state.guistate.themeIsOpened.setData(false)}/>
</div>
<Tr slot="title0" t={layout.title}/>
<div slot="content0">
@ -240,8 +248,12 @@
<If condition={state.guistate.menuIsOpened}>
<!-- Menu page -->
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
<FloatOver>
<span slot="close-button"><!-- Hide the default close button --></span>
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
<div slot="post-tablist">
<XCircleIcon class="w-8 h-8 mr-2" on:click={() => state.guistate.menuIsOpened.setData(false)}/>
</div>
<div class="flex" slot="title0">
<Tr t={Translations.t.general.menu.aboutMapComplete}/>
</div>
@ -276,16 +288,13 @@
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({theme: "MapComplete"})}/>
</a>
{Constants.vNumber}
<ToSvelte construct={Hotkeys.generateDocumentationDynamic}></ToSvelte>
</div>
<If condition={state.osmConnection.isLoggedIn} slot="title1">
<div class="flex">
<CogIcon class="w-6 h-6"/>
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})}/>
</div>
</If>
<div class="flex" slot="title1">
<CogIcon class="w-6 h-6"/>
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})}/>
</div>
<div slot="content1">
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
@ -316,6 +325,12 @@
<Tr t={Translations.t.privacy.title}></Tr>
</div>
<ToSvelte construct={() => new PrivacyPolicy()} slot="content3"></ToSvelte>
<Tr slot="title4" t={Translations.t.advanced.title}/>
<div class="flex flex-col" slot="content4">
<ToSvelte construct={Hotkeys.generateDocumentationDynamic}></ToSvelte>
</div>
</TabbedGroup>
</FloatOver>
</If>

View file

@ -735,14 +735,6 @@ video {
top: 1rem;
}
.right-1 {
right: 0.25rem;
}
.bottom-1 {
bottom: 0.25rem;
}
.left-1\/2 {
left: 50%;
}
@ -905,10 +897,6 @@ video {
margin-bottom: 2.5rem;
}
.mt-0 {
margin-top: 0px;
}
.mt-8 {
margin-top: 2rem;
}
@ -1464,6 +1452,11 @@ video {
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
}
.bg-gray-300 {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}
.bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
@ -1479,11 +1472,6 @@ video {
background-color: rgb(224 231 255 / var(--tw-bg-opacity));
}
.bg-gray-300 {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
@ -2324,10 +2312,6 @@ input {
margin: 0.5rem;
}
.sm\:m-6 {
margin: 1.5rem;
}
.sm\:mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
@ -2382,10 +2366,6 @@ input {
border-width: 4px;
}
.sm\:p-6 {
padding: 1.5rem;
}
.sm\:p-1 {
padding: 0.25rem;
}
@ -2418,10 +2398,6 @@ input {
margin: 0.25rem;
}
.md\:m-8 {
margin: 2rem;
}
.md\:m-2 {
margin: 0.5rem;
}
@ -2483,6 +2459,10 @@ input {
border-top-width: 2px;
}
.md\:p-6 {
padding: 1.5rem;
}
.md\:p-4 {
padding: 1rem;
}

View file

@ -1,4 +1,7 @@
{
"advanced": {
"title": "Advanced features"
},
"centerMessage": {
"loadingData": "Loading data…",
"ready": "Done!",
@ -961,6 +964,7 @@
"notImmediate": "Translations are not updated directly. This typically takes a few days"
},
"userinfo": {
"editDescription": "Edit your profile description",
"gotoInbox": "Open your inbox",
"gotoSettings": "Go to your settings on OpenStreetMap.org",
"noDescription": "You don't have a description on your profile yet",