diff --git a/web/i18n/en/a11y/dialog.json b/web/i18n/en/a11y/dialog.json index 84df736b..478a1587 100644 --- a/web/i18n/en/a11y/dialog.json +++ b/web/i18n/en/a11y/dialog.json @@ -1,5 +1,7 @@ { "picker.item.photo": "photo thumbnail", "picker.item.video": "video thumbnail", - "picker.item.gif": "gif thumbnail" + "picker.item.gif": "gif thumbnail", + + "saving.copied": "link copied" } diff --git a/web/src/components/buttons/VerticalActionButton.svelte b/web/src/components/buttons/VerticalActionButton.svelte index a9d37689..14602500 100644 --- a/web/src/components/buttons/VerticalActionButton.svelte +++ b/web/src/components/buttons/VerticalActionButton.svelte @@ -5,9 +5,17 @@ }; export let fill = false; export let elevated = false; + export let ariaLabel = ""; </script> -<button id="button-{id}" class="button vertical" class:fill class:elevated on:click={click}> +<button + id="button-{id}" + class="button vertical" + class:fill + class:elevated + on:click={click} + aria-label={ariaLabel} +> <slot></slot> </button> @@ -19,6 +27,11 @@ gap: calc(var(--padding) / 2); } + .button.vertical :global(svg) { + width: 24px; + height: 24px; + } + .button.vertical.fill { width: 100%; padding: var(--padding) 0; diff --git a/web/src/components/dialog/SavingDialog.svelte b/web/src/components/dialog/SavingDialog.svelte index bba591f1..ee0a5ee4 100644 --- a/web/src/components/dialog/SavingDialog.svelte +++ b/web/src/components/dialog/SavingDialog.svelte @@ -10,16 +10,25 @@ import DialogButtons from "$components/dialog/DialogButtons.svelte"; import VerticalActionButton from "$components/buttons/VerticalActionButton.svelte"; - import IconCopy from "@tabler/icons-svelte/IconCopy.svelte"; import IconShare2 from "@tabler/icons-svelte/IconShare2.svelte"; import IconDownload from "@tabler/icons-svelte/IconDownload.svelte"; import IconFileDownload from "@tabler/icons-svelte/IconFileDownload.svelte"; + import CopyIcon from "$components/misc/CopyIcon.svelte"; + export let id: string; export let url: string; export let bodyText: string = ""; let close: () => void; + + let copied = false; + + $: if (copied) { + setTimeout(() => { + copied = false; + }, 1500); + } </script> <DialogContainer {id} bind:close> @@ -63,18 +72,24 @@ id="save-copy" fill elevated - click={async () => copyURL(url)} + click={async () => { + copyURL(url); + copied = true; + }} + ariaLabel={copied ? $t("a11y.dialog.saving.copied") : ""} > - <IconCopy /> + <CopyIcon check={copied} /> {$t("dialog.button.copy")} </VerticalActionButton> </div> </div> + {#if bodyText} <div class="body-text"> {bodyText} </div> {/if} + <DialogButtons buttons={[ { diff --git a/web/src/components/misc/CopyIcon.svelte b/web/src/components/misc/CopyIcon.svelte new file mode 100644 index 00000000..2bb60b01 --- /dev/null +++ b/web/src/components/misc/CopyIcon.svelte @@ -0,0 +1,54 @@ +<script lang="ts"> + import IconCopy from "@tabler/icons-svelte/IconCopy.svelte"; + import IconCheck from "@tabler/icons-svelte/IconCheck.svelte"; + + export let check = false; +</script> + +<div id="copy-animation" class:check> + <div class="icon-copy"> + <IconCopy /> + </div> + <div class="icon-check"> + <IconCheck /> + </div> +</div> + +<style> + #copy-animation { + position: relative; + height: 24px; + width: 24px; + } + + #copy-animation :global(svg) { + will-change: transform; + } + + .icon-copy, + .icon-check { + display: flex; + position: absolute; + transition: transform 0.25s, opacity 0.25s; + } + + .icon-copy { + transform: none; + opacity: 1; + } + + .icon-check { + transform: scale(0.4); + opacity: 0; + } + + .check .icon-copy { + transform: scale(0.4); + opacity: 0; + } + + .check .icon-check { + transform: none; + opacity: 1; + } +</style>