greatly improve handling of modals
This commit is contained in:
parent
dc8414c050
commit
72c9650f71
11 changed files with 262 additions and 297 deletions
|
@ -1,8 +1,22 @@
|
|||
import { fade, fly, scale } from "svelte/transition";
|
||||
import { cubicInOut } from "svelte/easing";
|
||||
import { cubicInOut, linear } from "svelte/easing";
|
||||
import { getItem } from "./storage";
|
||||
import { smallViewport } from "./stores";
|
||||
|
||||
|
||||
// Function specific for the Login and CreateAccount modals, where the transition duration is relied upon
|
||||
export function maybeModalFadeIf(...e) {
|
||||
if (e[1] && e[1]._condition)
|
||||
return maybeModalFade(e[0]);
|
||||
else
|
||||
return {
|
||||
delay: 0,
|
||||
duration: e[1].duration,
|
||||
easing: e[1].easing,
|
||||
css: (_t) => ""
|
||||
};
|
||||
}
|
||||
|
||||
export function maybeModalFade(node) {
|
||||
return maybeFade(node, { duration: 175, easing: cubicInOut });
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { overlayStore } from "../../stores";
|
||||
import { methods, remoteSignal } from "../../request";
|
||||
import { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
let communityName = "";
|
||||
let createButtonEnabled = true;
|
||||
|
@ -15,36 +15,20 @@
|
|||
}
|
||||
close();
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await create();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<Modal {close} enter={create}>
|
||||
<span class="h4" slot="header">Create Community</span>
|
||||
|
||||
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
|
||||
<div class="modal-header">
|
||||
<span class="h4">Create Community</span>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<label class="input-label">
|
||||
Community Name
|
||||
<input class="input full-width" minlength="1" maxlength="32" bind:value={ communityName } />
|
||||
</label>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !createButtonEnabled }">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { overlayStore, OverlayType } from "../../stores";
|
||||
import { methods, remoteCall } from "../../request";
|
||||
import { maybeModalScale } from "../../animations";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
let username = "";
|
||||
let password = "";
|
||||
|
@ -30,31 +30,18 @@
|
|||
overlayStore.push(OverlayType.Login);
|
||||
}
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await create();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal-backdrop modal-backdrop-opaque" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation on:outroend="{ outroEnd }">
|
||||
<div class="modal-header">
|
||||
<span class="h4">Create an Account</span>
|
||||
</div>
|
||||
<Modal opaque close={loginInstead} {outroEnd} enter={create}>
|
||||
<span class="h4" slot="header">Create an Account</span>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<label class="input-label">
|
||||
Username
|
||||
<input class="input full-width" minlength="1" maxlength="32" bind:value={ username } />
|
||||
|
@ -66,11 +53,10 @@
|
|||
Password
|
||||
<input class="input full-width" minlength="8" type="password" bind:value={ password } />
|
||||
</label>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ loginInstead }">Log in instead</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !buttonsEnabled }">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { overlayStore } from "../../stores";
|
||||
import { methods, remoteCall, remoteSignal } from "../../request";
|
||||
import { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import { methods, remoteSignal } from "../../request";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
let channelName = "";
|
||||
let createButtonEnabled = true;
|
||||
|
@ -16,39 +16,25 @@
|
|||
}
|
||||
close();
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await create();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
|
||||
<div class="modal-header">
|
||||
<Modal {close} enter={create}>
|
||||
<svelte:fragment slot="header">
|
||||
<span class="h4">Create Channel</span>
|
||||
{#if community.id !== -1}
|
||||
<span class="text-fg-3 text-small">in <span class="text-fg-2">{ community.name }</span></span>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<label class="input-label">
|
||||
Channel Name
|
||||
<input class="input full-width" minlength="1" maxlength="32" bind:value={ channelName } />
|
||||
</label>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ create }" disabled="{ !createButtonEnabled }">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import { overlayStore } from "../../stores";
|
||||
import { methods, remoteCall, remoteSignal } from "../../request";
|
||||
import { methods, remoteSignal } from "../../request";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
export let channel;
|
||||
|
||||
|
@ -25,41 +25,27 @@
|
|||
}
|
||||
close();
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await save();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
color: var(--red-2);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
|
||||
<div class="modal-header">
|
||||
<span class="h4">Edit Channel</span>
|
||||
</div>
|
||||
<Modal {close} enter={save}>
|
||||
<span class="h4" slot="header">Edit Channel</span>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<label class="input-label">
|
||||
Channel Name
|
||||
<input class="input full-width" minlength="1" maxlength="32" bind:value={ channelName } />
|
||||
</label>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
|
||||
<button class="button modal-secondary-action delete-button" on:click="{ deleteChannel }" disabled="{ !buttonsEnabled }">Delete</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { overlayStore } from "../../stores";
|
||||
import { methods, remoteCall, remoteSignal } from "../../request";
|
||||
import { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import { methods, remoteSignal } from "../../request";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
export let message;
|
||||
|
||||
|
@ -25,41 +25,27 @@
|
|||
}
|
||||
close();
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await save();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
color: var(--red-2);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
|
||||
<div class="modal-header">
|
||||
<span class="h4">Edit Message</span>
|
||||
</div>
|
||||
<Modal {close} enter={save}>
|
||||
<span class="h4" slot="header">Edit Message</span>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<label class="input-label">
|
||||
Content
|
||||
<input class="input full-width" minlength="1" bind:value={ messageContent } />
|
||||
</label>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ close }">Cancel</button>
|
||||
<button class="button modal-secondary-action delete-button" on:click="{ deleteMessage }" disabled="{ !buttonsEnabled }">Delete</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { overlayStore, OverlayType } from "../../stores";
|
||||
import { remoteCall } from "../../request";
|
||||
import { authWithToken } from "../../auth";
|
||||
import { maybeModalScale } from "../../animations";
|
||||
import { methods } from "../../request";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
let username = "";
|
||||
let password = "";
|
||||
|
@ -35,31 +35,18 @@
|
|||
overlayStore.push(OverlayType.CreateAccount);
|
||||
}
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await login();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal-backdrop modal-backdrop-opaque" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation on:outroend="{ outroEnd }">
|
||||
<div class="modal-header">
|
||||
<span class="h4">Welcome back!</span>
|
||||
</div>
|
||||
<Modal opaque close={createAccountInstead} {outroEnd} enter={login}>
|
||||
<span class="h4" slot="header">Welcome back!</span>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<label class="input-label">
|
||||
Username
|
||||
<input class="input full-width" minlength="1" maxlength="32" bind:value={ username } />
|
||||
|
@ -71,11 +58,10 @@
|
|||
Password
|
||||
<input class="input full-width" minlength="8" type="password" bind:value={ password } />
|
||||
</label>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ createAccountInstead }">Create an account instead</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ login }" disabled="{ !buttonsEnabled }">Log In</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
57
frontend/src/components/overlays/Modal.svelte
Normal file
57
frontend/src/components/overlays/Modal.svelte
Normal file
|
@ -0,0 +1,57 @@
|
|||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { maybeModalFade, maybeModalFadeIf, maybeModalScale } from "../../animations";
|
||||
|
||||
export let close = () => {};
|
||||
export let enter = () => {};
|
||||
export let outroEnd = () => {};
|
||||
export let className = "";
|
||||
export let opaque = false;
|
||||
let modal;
|
||||
let blur = false;
|
||||
|
||||
const onKeydown = ({ code }) => {
|
||||
if (code === "Enter") {
|
||||
enter();
|
||||
} else if (code === "Escape") {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const backdropIntroEnd = () => {
|
||||
blur = true;
|
||||
};
|
||||
|
||||
const backdropOutroStart = () => {
|
||||
blur = false;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
modal.focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||
<div class="modal-backdrop" class:modal-backdrop-opaque={opaque} class:blur={blur} transition:maybeModalFadeIf="{{ _condition: !opaque }}" on:click="{ close }" on:keydown="{ onKeydown }" on:introend={backdropIntroEnd} on:outrostart={backdropOutroStart}>
|
||||
<div bind:this={modal} role="alertdialog" tabindex="-1" aria-modal="true" class={className + " modal"} transition:maybeModalScale on:click|stopPropagation on:outroend={outroEnd}>
|
||||
{#if $$slots.header}
|
||||
<div class="modal-header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $$slots.content}
|
||||
<div class="modal-content">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $$slots.footer}
|
||||
<div class="modal-footer">
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
export let onSubmit = async () => {};
|
||||
export let onClose = async () => {};
|
||||
|
@ -19,36 +19,18 @@
|
|||
await onSubmit(userInput);
|
||||
closePrompt();
|
||||
};
|
||||
const onKeydown = async (e) => {
|
||||
if (e.code !== "Enter")
|
||||
return;
|
||||
|
||||
await save();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<Modal {closePrompt} enter={save}>
|
||||
<span class="h4" slot="header">{ heading }</span>
|
||||
|
||||
<div class="modal-backdrop" transition:maybeModalFade on:click="{ closePrompt }" on:keydown="{ onKeydown }">
|
||||
<div class="modal" transition:maybeModalScale on:click|stopPropagation>
|
||||
<div class="modal-header">
|
||||
<span class="h4">{ heading }</span>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<label class="input-label">
|
||||
<label class="input-label" slot="content">
|
||||
{ valueName }
|
||||
<input class="input full-width" bind:value={ userInput } />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="button modal-secondary-action" on:click="{ closePrompt }">Cancel</button>
|
||||
<button class="button button-accent modal-primary-action" on:click="{ save }" disabled="{ !buttonsEnabled }">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import { overlayStore, userInfoStore, smallViewport, theme, doAnimations, OverlayType, sendTypingUpdatesItemStore } from "../../stores";
|
||||
import { logOut } from "../../auth";
|
||||
import { maybeModalFade, maybeModalScale } from "../../animations";
|
||||
import request, { methods, remoteBlobUpload, remoteCall } from "../../request";
|
||||
import request, { methods, remoteBlobUpload } from "../../request";
|
||||
import { apiRoute, getItem } from "../../storage";
|
||||
import UserView from "../UserView.svelte";
|
||||
import ChipBar from "../ChipBar.svelte";
|
||||
import Switch from "../Switch.svelte";
|
||||
import StoredSwitch from "../StoredSwitch.svelte";
|
||||
import Modal from "./Modal.svelte";
|
||||
|
||||
export let close = () => {};
|
||||
let avatarFileInput;
|
||||
|
@ -76,12 +76,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<style>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
@ -128,7 +123,7 @@
|
|||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.large-settings {
|
||||
:global(.large-settings) {
|
||||
width: 600px;
|
||||
min-height: 425px;
|
||||
padding-bottom: var(--space-xs);
|
||||
|
@ -138,23 +133,19 @@
|
|||
margin-left: auto;
|
||||
}
|
||||
|
||||
.settings-modal {
|
||||
background-color: var(--background-color-1);
|
||||
:global(.settings-modal) {
|
||||
background-color: var(--background-color-1) !important;
|
||||
}
|
||||
|
||||
.settings-modal .modal-header {
|
||||
:global(.settings-modal .modal-header) {
|
||||
padding-bottom: var(--space-xxs);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="modal-backdrop" transition:maybeModalFade on:click="{ close }">
|
||||
<div class="modal settings-modal" class:large-settings="{ !$smallViewport }" transition:maybeModalScale on:click|stopPropagation>
|
||||
<div class="modal-header">
|
||||
<span class="h4">Settings</span>
|
||||
</div>
|
||||
<Modal {close} className={`settings-modal ${$smallViewport ? "" : "large-settings"}`}>
|
||||
<span class="h4" slot="header">Settings</span>
|
||||
|
||||
<div class="modal-content">
|
||||
<svelte:fragment slot="content">
|
||||
<ChipBar selectedOptionId="ACCOUNT" onSelect={ (tab) => selectedTab = tab } options={[
|
||||
{ id: "ACCOUNT", text: "Account", icon: "person" },
|
||||
{ id: "PRIVACY", text: "Privacy", icon: "lock" },
|
||||
|
@ -209,6 +200,5 @@
|
|||
{:else}
|
||||
<span>Page not found: { selectedTab }</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
|
|
@ -222,10 +222,13 @@ body {
|
|||
bottom: 0;
|
||||
z-index: 15;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(1.5px);
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.modal-backdrop.blur {
|
||||
backdrop-filter: blur(1.5px);
|
||||
}
|
||||
|
||||
.modal-backdrop-opaque {
|
||||
background-color: var(--background-color-1);
|
||||
backdrop-filter: unset;
|
||||
|
@ -284,7 +287,6 @@ body {
|
|||
.modal-backdrop {
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
backdrop-filter: unset;
|
||||
}
|
||||
|
||||
.modal-backdrop-opaque {
|
||||
|
@ -327,8 +329,10 @@ body {
|
|||
background: none;
|
||||
text-align: center;
|
||||
border: none;
|
||||
padding: 0.7em;
|
||||
border-radius: 1em;
|
||||
padding: 0.85em;
|
||||
padding-top: 0.65em;
|
||||
padding-bottom: 0.65em;
|
||||
border-radius: 9999px;
|
||||
font: inherit;
|
||||
user-select: none;
|
||||
font-weight: 550;
|
||||
|
@ -588,6 +592,10 @@ body {
|
|||
border-bottom-left-radius: var(--radius-mdplus);
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/*! the tweaks below are heavily based on modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
||||
|
||||
|
|
Loading…
Reference in a new issue