feat(web,a11y): standardize base modal (#8388)

* consistent headings
* remove escape key handler
* add aria attributes
This commit is contained in:
Ben Basten 2024-04-02 15:05:02 +00:00 committed by GitHub
parent 28e8e539f6
commit f7afc0334e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 53 additions and 86 deletions

View File

@ -66,13 +66,7 @@
</script> </script>
{#if !selectedRemoveUser} {#if !selectedRemoveUser}
<BaseModal on:close={() => dispatch('close')}> <BaseModal id="share-info-modal" title="Options" on:close>
<svelte:fragment slot="title">
<span class="flex place-items-center gap-2">
<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Options</p>
</span>
</svelte:fragment>
<section class="immich-scrollbar max-h-[400px] overflow-y-auto pb-4"> <section class="immich-scrollbar max-h-[400px] overflow-y-auto pb-4">
<div class="flex w-full place-items-center justify-between gap-4 p-5"> <div class="flex w-full place-items-center justify-between gap-4 p-5">
<div class="flex place-items-center gap-4"> <div class="flex place-items-center gap-4">

View File

@ -13,7 +13,6 @@
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import BaseModal from '../shared-components/base-modal.svelte'; import BaseModal from '../shared-components/base-modal.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
@ -55,14 +54,7 @@
}; };
</script> </script>
<BaseModal on:close={() => dispatch('close')}> <BaseModal id="user-selection-modal" title="Invite to album" showLogo on:close>
<svelte:fragment slot="title">
<span class="flex place-items-center gap-2">
<ImmichLogo noText={true} width={36} />
<p class="font-medium">Invite to album</p>
</span>
</svelte:fragment>
{#if selectedUsers.length > 0} {#if selectedUsers.length > 0}
<div class="mb-2 flex flex-wrap place-items-center gap-4 overflow-x-auto px-5 py-2 sticky"> <div class="mb-2 flex flex-wrap place-items-center gap-4 overflow-x-auto px-5 py-2 sticky">
<p class="font-medium">To</p> <p class="font-medium">To</p>

View File

@ -737,7 +737,6 @@
on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)} on:newAlbum={({ detail }) => handleAddToNewAlbum(detail)}
on:album={({ detail }) => handleAddToAlbum(detail)} on:album={({ detail }) => handleAddToAlbum(detail)}
on:close={() => (isShowAlbumPicker = false)} on:close={() => (isShowAlbumPicker = false)}
on:escape={() => (isShowAlbumPicker = false)}
/> />
{/if} {/if}
@ -751,19 +750,11 @@
{/if} {/if}
{#if isShowProfileImageCrop} {#if isShowProfileImageCrop}
<ProfileImageCropper <ProfileImageCropper {asset} on:close={() => (isShowProfileImageCrop = false)} />
{asset}
on:close={() => (isShowProfileImageCrop = false)}
on:escape={() => (isShowProfileImageCrop = false)}
/>
{/if} {/if}
{#if isShowShareModal} {#if isShowShareModal}
<CreateSharedLinkModal <CreateSharedLinkModal assetIds={[asset.id]} on:close={() => (isShowShareModal = false)} />
assetIds={[asset.id]}
on:close={() => (isShowShareModal = false)}
on:escape={() => (isShowShareModal = false)}
/>
{/if} {/if}
</section> </section>
</FocusTrap> </FocusTrap>

View File

@ -2,26 +2,14 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
import { mdiShareVariantOutline } from '@mdi/js'; import { mdiShareVariantOutline } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
let showModal = false; let showModal = false;
const dispatch = createEventDispatcher<{
escape: void;
}>();
const { getAssets } = getAssetControlContext(); const { getAssets } = getAssetControlContext();
const escape = () => {
dispatch('escape');
showModal = false;
};
</script> </script>
<CircleIconButton title="Share" icon={mdiShareVariantOutline} on:click={() => (showModal = true)} /> <CircleIconButton title="Share" icon={mdiShareVariantOutline} on:click={() => (showModal = true)} />
{#if showModal} {#if showModal}
<CreateSharedLinkModal <CreateSharedLinkModal assetIds={[...getAssets()].map(({ id }) => id)} on:close={() => (showModal = false)} />
assetIds={[...getAssets()].map(({ id }) => id)}
on:close={() => (showModal = false)}
on:escape={escape}
/>
{/if} {/if}

View File

@ -43,17 +43,16 @@
const handleNew = () => { const handleNew = () => {
dispatch('newAlbum', search.length > 0 ? search : ''); dispatch('newAlbum', search.length > 0 ? search : '');
}; };
const getTitle = () => {
if (shared) {
return 'Add to shared album';
}
return 'Add to album';
};
</script> </script>
<BaseModal on:close on:escape> <BaseModal id="album-selection-modal" title={getTitle()} on:close>
<svelte:fragment slot="title">
<span class="flex place-items-center gap-2">
<p class="font-medium">
Add to {shared ? 'Shared ' : ''}Album
</p>
</span>
</svelte:fragment>
<div class="mb-2 flex max-h-[400px] flex-col"> <div class="mb-2 flex max-h-[400px] flex-col">
{#if loading} {#if loading}
{#each { length: 3 } as _} {#each { length: 3 } as _}
@ -69,11 +68,9 @@
</div> </div>
{/each} {/each}
{:else} {:else}
<!-- svelte-ignore a11y-autofocus -->
<input <input
class="border-b-4 border-immich-bg bg-immich-bg px-6 py-2 text-2xl focus:border-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:focus:border-immich-dark-primary" class="border-b-4 border-immich-bg bg-immich-bg px-6 py-2 text-2xl focus:border-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:focus:border-immich-dark-primary"
placeholder="Search" placeholder="Search"
autofocus
bind:value={search} bind:value={search}
/> />
<div class="immich-scrollbar overflow-y-auto"> <div class="immich-scrollbar overflow-y-auto">

View File

@ -7,12 +7,26 @@
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
import { mdiClose } from '@mdi/js'; import { mdiClose } from '@mdi/js';
import FocusTrap from '$lib/components/shared-components/focus-trap.svelte'; import FocusTrap from '$lib/components/shared-components/focus-trap.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
import Icon from '$lib/components/elements/icon.svelte';
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
escape: void;
close: void; close: void;
}>(); }>();
/**
* Unique identifier for the modal.
*/
export let id: string;
export let title: string;
export let zIndex = 9999; export let zIndex = 9999;
/**
* If true, the logo will be displayed next to the modal title.
*/
export let showLogo = false;
/**
* Optional icon to display next to the modal title, if `showLogo` is false.
*/
export let icon: string | undefined = undefined;
onMount(() => { onMount(() => {
if (browser) { if (browser) {
@ -36,7 +50,8 @@
<FocusTrap> <FocusTrap>
<div <div
id="immich-modal" aria-modal="true"
aria-labelledby={`${id}-title`}
style:z-index={zIndex} style:z-index={zIndex}
transition:fade={{ duration: 100, easing: quintOut }} transition:fade={{ duration: 100, easing: quintOut }}
class="fixed left-0 top-0 flex h-full w-full place-content-center place-items-center overflow-hidden bg-black/50" class="fixed left-0 top-0 flex h-full w-full place-content-center place-items-center overflow-hidden bg-black/50"
@ -44,16 +59,23 @@
<div <div
use:clickOutside={{ use:clickOutside={{
onOutclick: () => dispatch('close'), onOutclick: () => dispatch('close'),
onEscape: () => dispatch('escape'), onEscape: () => dispatch('close'),
}} }}
class="max-h-[800px] min-h-[200px] w-[450px] overflow-y-auto rounded-lg bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar" class="max-h-[800px] min-h-[200px] w-[450px] overflow-y-auto rounded-lg bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg immich-scrollbar"
tabindex="-1" tabindex="-1"
> >
<div class="flex place-items-center justify-between px-5 py-3"> <div class="flex place-items-center justify-between px-5 py-3">
<div> <div class="flex items-center">
<slot name="title"> {#if showLogo}
<p>Modal Title</p> <ImmichLogo noText={true} width={24} />
</slot> <div class="w-2" />
{:else if icon}
<Icon path={icon} size={24} ariaHidden={true} class="text-immich-primary dark:text-immich-dark-primary" />
<div class="w-2" />
{/if}
<h1 id={`${id}-title`} class="text-xl font-medium text-immich-primary dark:text-immich-dark-primary mt-1">
{title}
</h1>
</div> </div>
<CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} size={'20'} title="Close" /> <CircleIconButton on:click={() => dispatch('close')} icon={mdiClose} size={'20'} title="Close" />

View File

@ -143,20 +143,16 @@
handleError(error, 'Failed to edit shared link'); handleError(error, 'Failed to edit shared link');
} }
}; };
const getTitle = () => {
if (editingLink) {
return 'Edit link';
}
return 'Create link to share';
};
</script> </script>
<BaseModal on:close={() => dispatch('close')} on:escape={() => dispatch('escape')}> <BaseModal id="create-shared-link-modal" title={getTitle()} icon={mdiLink} on:close>
<svelte:fragment slot="title">
<span class="flex place-items-center gap-2">
<Icon path={mdiLink} size={24} />
{#if editingLink}
<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Edit link</p>
{:else}
<p class="font-medium text-immich-fg dark:text-immich-dark-fg">Create link to share</p>
{/if}
</span>
</svelte:fragment>
<section class="mx-6 mb-6"> <section class="mx-6 mb-6">
{#if shareType === SharedLinkType.Album} {#if shareType === SharedLinkType.Album}
{#if !editingLink} {#if !editingLink}

View File

@ -71,12 +71,7 @@
}; };
</script> </script>
<BaseModal on:close on:escape> <BaseModal id="profile-image-cropper" title="Set profile picture" on:close>
<svelte:fragment slot="title">
<span class="flex place-items-center gap-2">
<p class="font-medium">Set profile picture</p>
</span>
</svelte:fragment>
<div class="flex place-items-center items-center justify-center"> <div class="flex place-items-center items-center justify-center">
<div <div
class="relative flex aspect-square w-1/2 overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary" class="relative flex aspect-square w-1/2 overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary"

View File

@ -3,7 +3,6 @@
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte'; import Button from '../elements/buttons/button.svelte';
import BaseModal from '../shared-components/base-modal.svelte'; import BaseModal from '../shared-components/base-modal.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import UserAvatar from '../shared-components/user-avatar.svelte'; import UserAvatar from '../shared-components/user-avatar.svelte';
export let user: UserResponseDto; export let user: UserResponseDto;
@ -33,14 +32,7 @@
}; };
</script> </script>
<BaseModal on:close={() => dispatch('close')}> <BaseModal id="partner-selection-modal" title="Add partner" showLogo on:close>
<svelte:fragment slot="title">
<span class="flex place-items-center gap-2">
<ImmichLogo noText width={36} />
<p class="font-medium">Add partner</p>
</span>
</svelte:fragment>
<div class="immich-scrollbar max-h-[300px] overflow-y-auto"> <div class="immich-scrollbar max-h-[300px] overflow-y-auto">
{#if availableUsers.length > 0} {#if availableUsers.length > 0}
{#each availableUsers as user} {#each availableUsers as user}

View File

@ -53,7 +53,7 @@
assets={$selectedAssets} assets={$selectedAssets}
clearSelect={() => assetInteractionStore.clearMultiselect()} clearSelect={() => assetInteractionStore.clearMultiselect()}
> >
<CreateSharedLink on:escape={() => (handleEscapeKey = true)} /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add to..."> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />