mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
fix(web): shared link expiration date accessibility (#12060)
- use native select - shows focus, automatically has keyboard navigation, accessible for screen readers - remove DropdownButton component - fix dropdown styling in Safari
This commit is contained in:
parent
6b6d2a6621
commit
9894b9513b
3 changed files with 46 additions and 113 deletions
|
@ -8,7 +8,6 @@
|
|||
import { SharedLinkType, createSharedLink, updateSharedLink, type SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { mdiContentCopy, mdiLink } from '@mdi/js';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import DropdownButton, { type DropDownOption } from '../dropdown-button.svelte';
|
||||
import { NotificationType, notificationController } from '../notification/notification';
|
||||
import SettingInputField, { SettingInputFieldType } from '../settings/setting-input-field.svelte';
|
||||
import SettingSwitch from '../settings/setting-switch.svelte';
|
||||
|
@ -16,6 +15,7 @@
|
|||
import { t } from 'svelte-i18n';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
|
||||
|
||||
export let onClose: () => void;
|
||||
export let albumId: string | undefined = undefined;
|
||||
|
@ -27,7 +27,7 @@
|
|||
let allowDownload = true;
|
||||
let allowUpload = false;
|
||||
let showMetadata = true;
|
||||
let expirationOption: DropDownOption<number> | undefined;
|
||||
let expirationOption: number = 0;
|
||||
let password = '';
|
||||
let shouldChangeExpirationTime = false;
|
||||
let enablePassword = false;
|
||||
|
@ -48,14 +48,12 @@
|
|||
];
|
||||
|
||||
$: relativeTime = new Intl.RelativeTimeFormat($locale);
|
||||
$: expiredDateOption = [
|
||||
{ label: $t('never'), value: 0 },
|
||||
...expirationOptions.map(
|
||||
([value, unit]): DropDownOption<number> => ({
|
||||
label: relativeTime.format(value, unit),
|
||||
value: Duration.fromObject({ [unit]: value }).toMillis(),
|
||||
}),
|
||||
),
|
||||
$: expiredDateOptions = [
|
||||
{ text: $t('never'), value: 0 },
|
||||
...expirationOptions.map(([value, unit]) => ({
|
||||
text: relativeTime.format(value, unit),
|
||||
value: Duration.fromObject({ [unit]: value }).toMillis(),
|
||||
})),
|
||||
];
|
||||
|
||||
$: shareType = albumId ? SharedLinkType.Album : SharedLinkType.Individual;
|
||||
|
@ -82,8 +80,7 @@
|
|||
}
|
||||
|
||||
const handleCreateSharedLink = async () => {
|
||||
const expirationDate =
|
||||
expirationOption && expirationOption.value > 0 ? DateTime.now().plus(expirationOption.value).toISO() : undefined;
|
||||
const expirationDate = expirationOption > 0 ? DateTime.now().plus(expirationOption).toISO() : undefined;
|
||||
|
||||
try {
|
||||
const data = await createSharedLink({
|
||||
|
@ -112,8 +109,7 @@
|
|||
}
|
||||
|
||||
try {
|
||||
const expirationDate =
|
||||
expirationOption && expirationOption.value > 0 ? DateTime.now().plus(expirationOption.value).toISO() : null;
|
||||
const expirationDate = expirationOption > 0 ? DateTime.now().plus(expirationOption).toISO() : null;
|
||||
|
||||
await updateSharedLink({
|
||||
id: editingLink.id,
|
||||
|
@ -212,19 +208,18 @@
|
|||
<SettingSwitch bind:checked={allowUpload} title={$t('allow_public_user_to_upload')} />
|
||||
</div>
|
||||
|
||||
<div class="text-sm">
|
||||
{#if editingLink}
|
||||
<p class="immich-form-label my-2">
|
||||
<SettingSwitch bind:checked={shouldChangeExpirationTime} title={$t('change_expiration_time')} />
|
||||
</p>
|
||||
{:else}
|
||||
<p class="immich-form-label my-2">{$t('expire_after')}</p>
|
||||
{/if}
|
||||
|
||||
<DropdownButton
|
||||
options={expiredDateOption}
|
||||
bind:selected={expirationOption}
|
||||
{#if editingLink}
|
||||
<div class="my-3">
|
||||
<SettingSwitch bind:checked={shouldChangeExpirationTime} title={$t('change_expiration_time')} />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-3">
|
||||
<SettingSelect
|
||||
bind:value={expirationOption}
|
||||
options={expiredDateOptions}
|
||||
label={$t('expire_after')}
|
||||
disabled={editingLink && !shouldChangeExpirationTime}
|
||||
number={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
<script lang="ts" context="module">
|
||||
export type DropDownOption<T = unknown> = {
|
||||
label: string;
|
||||
value: T;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export let options: DropDownOption[];
|
||||
export let selected = options.at(0);
|
||||
export let disabled = false;
|
||||
|
||||
export let isOpen = false;
|
||||
const toggle = () => (isOpen = !isOpen);
|
||||
</script>
|
||||
|
||||
<div id="immich-dropdown" class="relative">
|
||||
<button
|
||||
type="button"
|
||||
{disabled}
|
||||
on:click={toggle}
|
||||
aria-expanded={isOpen}
|
||||
class="flex w-full place-items-center justify-between rounded-lg bg-gray-200 p-2 disabled:cursor-not-allowed disabled:bg-gray-600 dark:bg-gray-600 dark:disabled:bg-gray-300"
|
||||
>
|
||||
{#if selected}
|
||||
<div>
|
||||
{selected.label}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<svg
|
||||
style="tran"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="absolute mt-2 flex w-full flex-col">
|
||||
{#each options as option}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
selected = option;
|
||||
isOpen = false;
|
||||
}}
|
||||
class="flex w-full bg-gray-200 p-2 transition-all hover:bg-gray-300 dark:bg-gray-500 dark:hover:bg-gray-700"
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
transition: transform 0.2s ease-in;
|
||||
}
|
||||
|
||||
[aria-expanded='true'] svg {
|
||||
transform: rotate(0.5turn);
|
||||
}
|
||||
</style>
|
|
@ -3,6 +3,8 @@
|
|||
import { fly } from 'svelte/transition';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { mdiChevronDown } from '@mdi/js';
|
||||
|
||||
export let value: string | number;
|
||||
export let options: { value: string | number; text: string }[];
|
||||
|
@ -46,17 +48,27 @@
|
|||
</p>
|
||||
{/if}
|
||||
|
||||
<select
|
||||
class="immich-form-input w-full pb-2"
|
||||
{disabled}
|
||||
aria-describedby={desc ? `${name}-desc` : undefined}
|
||||
{name}
|
||||
id="{name}-select"
|
||||
bind:value
|
||||
on:change={handleChange}
|
||||
>
|
||||
{#each options as option}
|
||||
<option value={option.value}>{option.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class="grid">
|
||||
<Icon
|
||||
path={mdiChevronDown}
|
||||
size={'1.2em'}
|
||||
ariaHidden={true}
|
||||
class="pointer-events-none right-1 relative col-start-1 row-start-1 self-center justify-self-end {disabled
|
||||
? 'text-immich-bg'
|
||||
: 'text-immich-fg dark:text-immich-bg'}"
|
||||
/>
|
||||
<select
|
||||
class="immich-form-input w-full appearance-none row-start-1 col-start-1 !pr-6"
|
||||
{disabled}
|
||||
aria-describedby={desc ? `${name}-desc` : undefined}
|
||||
{name}
|
||||
id="{name}-select"
|
||||
bind:value
|
||||
on:change={handleChange}
|
||||
>
|
||||
{#each options as option}
|
||||
<option value={option.value}>{option.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue