chore(web): migration svelte 5 syntax (#13883)

This commit is contained in:
Alex 2024-11-14 08:43:25 -06:00 committed by GitHub
parent 9203a61709
commit 0b3742cf13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
310 changed files with 6435 additions and 4176 deletions

View file

@ -1,11 +1,18 @@
<script lang="ts" context="module">
<script lang="ts" module>
export type Color = 'primary' | 'secondary';
export type Rounded = false | true | 'full';
</script>
<script lang="ts">
export let color: Color = 'primary';
export let rounded: Rounded = true;
import type { Snippet } from 'svelte';
interface Props {
color?: Color;
rounded?: Rounded;
children?: Snippet;
}
let { color = 'primary', rounded = true, children }: Props = $props();
const colorClasses: Record<Color, string> = {
primary: 'text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary',
@ -20,5 +27,5 @@
class:rounded-md={rounded === true}
class:rounded-full={rounded === 'full'}
>
<slot />
{@render children?.()}
</span>

View file

@ -1,6 +1,4 @@
<script lang="ts" context="module">
import type { HTMLButtonAttributes, HTMLLinkAttributes } from 'svelte/elements';
<script lang="ts" module>
export type Color =
| 'primary'
| 'primary-inversed'
@ -17,44 +15,47 @@
export type Size = 'tiny' | 'icon' | 'link' | 'sm' | 'base' | 'lg';
export type Rounded = 'lg' | '3xl' | 'full' | 'none';
export type Shadow = 'md' | false;
</script>
type BaseProps = {
class?: string;
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
type?: string;
href?: string;
color?: Color;
size?: Size;
rounded?: Rounded;
shadow?: Shadow;
fullwidth?: boolean;
border?: boolean;
};
class?: string;
children?: Snippet;
onclick?: (event: MouseEvent) => void;
onfocus?: () => void;
onblur?: () => void;
form?: string;
disabled?: boolean;
title?: string;
'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | undefined | null;
}
export type ButtonProps = HTMLButtonAttributes &
BaseProps & {
href?: never;
};
export type LinkProps = HTMLLinkAttributes &
BaseProps & {
type?: never;
};
export type Props = ButtonProps | LinkProps;
</script>
<script lang="ts">
type $$Props = Props;
export let type: $$Props['type'] = 'button';
export let href: $$Props['href'] = undefined;
export let color: Color = 'primary';
export let size: Size = 'base';
export let rounded: Rounded = '3xl';
export let shadow: Shadow = 'md';
export let fullwidth = false;
export let border = false;
let className = '';
export { className as class };
let {
type = 'button',
href = undefined,
color = 'primary',
size = 'base',
rounded = '3xl',
shadow = 'md',
fullwidth = false,
border = false,
class: className = '',
children,
onclick,
onfocus,
onblur,
...rest
}: Props = $props();
const colorClasses: Record<Color, string> = {
primary:
@ -93,29 +94,31 @@
full: 'rounded-full',
};
$: computedClass = [
className,
colorClasses[color],
sizeClasses[size],
roundedClasses[rounded],
shadow === 'md' && 'shadow-md',
fullwidth && 'w-full',
border && 'border',
]
.filter(Boolean)
.join(' ');
let computedClass = $derived(
[
className,
colorClasses[color],
sizeClasses[size],
roundedClasses[rounded],
shadow === 'md' && 'shadow-md',
fullwidth && 'w-full',
border && 'border',
]
.filter(Boolean)
.join(' '),
);
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<svelte:element
this={href ? 'a' : 'button'}
type={href ? undefined : type}
{href}
on:click
on:focus
on:blur
{onclick}
{onfocus}
{onblur}
class="inline-flex items-center justify-center transition-colors disabled:cursor-not-allowed disabled:opacity-60 disabled:pointer-events-none {computedClass}"
{...$$restProps}
{...rest}
>
<slot />
{@render children?.()}
</svelte:element>

View file

@ -1,64 +1,64 @@
<script lang="ts" context="module">
import type { HTMLButtonAttributes, HTMLLinkAttributes } from 'svelte/elements';
<script lang="ts" module>
export type Color = 'transparent' | 'light' | 'dark' | 'gray' | 'primary' | 'opaque' | 'alert';
export type Padding = '1' | '2' | '3';
type BaseProps = {
icon: string;
title: string;
class?: string;
color?: Color;
padding?: Padding;
size?: string;
hideMobile?: true;
buttonSize?: string;
viewBox?: string;
};
export type ButtonProps = HTMLButtonAttributes &
BaseProps & {
href?: never;
};
export type LinkProps = HTMLLinkAttributes &
BaseProps & {
type?: never;
};
export type Props = ButtonProps | LinkProps;
</script>
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
type $$Props = Props;
export let type: $$Props['type'] = 'button';
export let href: $$Props['href'] = undefined;
export let icon: string;
export let color: Color = 'transparent';
export let title: string;
/**
* The padding of the button, used by the `p-{padding}` Tailwind CSS class.
*/
export let padding: Padding = '3';
/**
* Size of the button, used for a CSS value.
*/
export let size = '24';
export let hideMobile = false;
export let buttonSize: string | undefined = undefined;
/**
* viewBox attribute for the SVG icon.
*/
export let viewBox: string | undefined = undefined;
/**
* Override the default styling of the button for specific use cases, such as the icon color.
*/
let className = '';
export { className as class };
interface Props {
id?: string;
type?: string;
href?: string;
icon: string;
color?: Color;
title: string;
/**
* The padding of the button, used by the `p-{padding}` Tailwind CSS class.
*/
padding?: Padding;
/**
* Size of the button, used for a CSS value.
*/
size?: string;
hideMobile?: boolean;
buttonSize?: string | undefined;
/**
* viewBox attribute for the SVG icon.
*/
viewBox?: string | undefined;
class?: string;
'aria-hidden'?: boolean | undefined | null;
'aria-checked'?: 'true' | 'false' | undefined | null;
'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | undefined | null;
'aria-controls'?: string | undefined | null;
'aria-expanded'?: boolean;
'aria-haspopup'?: boolean;
tabindex?: number | undefined | null;
role?: string | undefined | null;
onclick: (e: MouseEvent) => void;
disabled?: boolean;
}
let {
type = 'button',
href = undefined,
icon,
color = 'transparent',
title,
padding = '3',
size = '24',
hideMobile = false,
buttonSize = undefined,
viewBox = undefined,
class: className = '',
onclick,
...rest
}: Props = $props();
const colorClasses: Record<Color, string> = {
transparent: 'bg-transparent hover:bg-[#d3d3d3] dark:text-immich-dark-fg',
@ -77,12 +77,12 @@
'3': 'p-3',
};
$: colorClass = colorClasses[color];
$: mobileClass = hideMobile ? 'hidden sm:flex' : '';
$: paddingClass = paddingClasses[padding];
let colorClass = $derived(colorClasses[color]);
let mobileClass = $derived(hideMobile ? 'hidden sm:flex' : '');
let paddingClass = $derived(paddingClasses[padding]);
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<svelte:element
this={href ? 'a' : 'button'}
type={href ? undefined : type}
@ -91,8 +91,8 @@
style:width={buttonSize ? buttonSize + 'px' : ''}
style:height={buttonSize ? buttonSize + 'px' : ''}
class="flex place-content-center place-items-center rounded-full {colorClass} {paddingClass} transition-all disabled:cursor-default hover:dark:text-immich-dark-gray {className} {mobileClass}"
on:click
{...$$restProps}
{onclick}
{...rest}
>
<Icon path={icon} {size} ariaLabel={title} {viewBox} color="currentColor" />
</svelte:element>

View file

@ -1,22 +1,25 @@
<script lang="ts" context="module">
<script lang="ts" module>
export type Color = 'transparent-primary' | 'transparent-gray';
type BaseProps = {
color?: Color;
};
export type Props = (LinkProps & BaseProps) | (ButtonProps & BaseProps);
</script>
<script lang="ts">
import Button, { type ButtonProps, type LinkProps } from '$lib/components/elements/buttons/button.svelte';
import Button from '$lib/components/elements/buttons/button.svelte';
import type { Snippet } from 'svelte';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type $$Props = Props;
interface Props {
href?: string;
color?: Color;
children?: Snippet;
onclick?: (e: MouseEvent) => void;
title?: string;
disabled?: boolean;
fullwidth?: boolean;
class?: string;
}
export let color: Color = 'transparent-gray';
let { color = 'transparent-gray', children, ...rest }: Props = $props();
</script>
<Button size="link" {color} shadow={false} rounded="lg" on:click {...$$restProps}>
<slot />
<Button size="link" {color} shadow={false} rounded="lg" {...rest}>
{@render children?.()}
</Button>

View file

@ -2,13 +2,17 @@
import { t } from 'svelte-i18n';
import Button from './button.svelte';
/**
* Target for the skip link to move focus to.
*/
export let target: string = 'main';
export let text: string = $t('skip_to_content');
interface Props {
/**
* Target for the skip link to move focus to.
*/
target?: string;
text?: string;
}
let isFocused = false;
let { target = 'main', text = $t('skip_to_content') }: Props = $props();
let isFocused = $state(false);
const moveFocus = () => {
const targetEl = document.querySelector<HTMLElement>(target);
@ -20,9 +24,9 @@
<Button
size={'sm'}
rounded="none"
on:click={moveFocus}
on:focus={() => (isFocused = true)}
on:blur={() => (isFocused = false)}
onclick={moveFocus}
onfocus={() => (isFocused = true)}
onblur={() => (isFocused = false)}
>
{text}
</Button>

View file

@ -1,11 +1,25 @@
<script lang="ts">
export let id: string;
export let label: string;
export let checked: boolean | undefined = undefined;
export let disabled: boolean = false;
export let labelClass: string | undefined = undefined;
export let name: string | undefined = undefined;
export let value: string | undefined = undefined;
interface Props {
id: string;
label: string;
checked?: boolean | undefined;
disabled?: boolean;
labelClass?: string | undefined;
name?: string | undefined;
value?: string | undefined;
onchange?: () => void;
}
let {
id,
label,
checked = $bindable(),
disabled = false,
labelClass = undefined,
name = undefined,
value = undefined,
onchange = () => {},
}: Props = $props();
</script>
<div class="flex items-center space-x-2">
@ -17,7 +31,7 @@
{disabled}
class="size-5 flex-shrink-0 focus-visible:ring"
bind:checked
on:change
{onchange}
/>
<label class={labelClass} for={id}>{label}</label>
</div>

View file

@ -1,29 +1,35 @@
<script lang="ts">
import type { HTMLInputAttributes } from 'svelte/elements';
interface $$Props extends HTMLInputAttributes {
interface Props {
type: 'date' | 'datetime-local';
value?: string;
min?: string;
max?: string;
class?: string;
id?: string;
name?: string;
placeholder?: string;
}
export let type: $$Props['type'];
export let value: $$Props['value'] = undefined;
export let max: $$Props['max'] = undefined;
let { type, value = $bindable(), max = undefined, ...rest }: Props = $props();
$: fallbackMax = type === 'date' ? '9999-12-31' : '9999-12-31T23:59';
let fallbackMax = $derived(type === 'date' ? '9999-12-31' : '9999-12-31T23:59');
// Updating `value` directly causes the date input to reset itself or
// interfere with user changes.
$: updatedValue = value;
let updatedValue = $state<string>();
$effect(() => {
updatedValue = value;
});
</script>
<input
{...$$restProps}
{...rest}
{type}
{value}
max={max || fallbackMax}
on:input={(e) => (updatedValue = e.currentTarget.value)}
on:blur={() => (value = updatedValue)}
on:keydown={(e) => {
oninput={(e) => (updatedValue = e.currentTarget.value)}
onblur={() => (value = updatedValue)}
onkeydown={(e) => {
if (e.key === 'Enter') {
value = updatedValue;
}

View file

@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
// Necessary for eslint
/* eslint-disable @typescript-eslint/no-explicit-any */
type T = any;
@ -20,19 +20,31 @@
import { clickOutside } from '$lib/actions/click-outside';
import { fly } from 'svelte/transition';
let className = '';
export { className as class };
interface Props {
class?: string;
options: T[];
selectedOption?: any;
showMenu?: boolean;
controlable?: boolean;
hideTextOnSmallScreen?: boolean;
title?: string | undefined;
onSelect: (option: T) => void;
onClickOutside?: () => void;
render?: (item: T) => string | RenderedOption;
}
export let options: T[];
export let selectedOption = options[0];
export let showMenu = false;
export let controlable = false;
export let hideTextOnSmallScreen = true;
export let title: string | undefined = undefined;
export let onSelect: (option: T) => void;
export let onClickOutside: () => void = () => {};
export let render: (item: T) => string | RenderedOption = String;
let {
class: className = '',
options,
selectedOption = $bindable(options[0]),
showMenu = $bindable(false),
controlable = false,
hideTextOnSmallScreen = true,
title = undefined,
onSelect,
onClickOutside = () => {},
render = String,
}: Props = $props();
const handleClickOutside = () => {
if (!controlable) {
@ -65,12 +77,12 @@
}
};
$: renderedSelectedOption = renderOption(selectedOption);
let renderedSelectedOption = $derived(renderOption(selectedOption));
</script>
<div use:clickOutside={{ onOutclick: handleClickOutside, onEscape: handleClickOutside }}>
<!-- BUTTON TITLE -->
<LinkButton on:click={() => (showMenu = true)} fullwidth {title}>
<LinkButton onclick={() => (showMenu = true)} fullwidth {title}>
<div class="flex place-items-center gap-2 text-sm">
{#if renderedSelectedOption?.icon}
<Icon path={renderedSelectedOption.icon} size="18" />
@ -92,7 +104,7 @@
type="button"
class="grid grid-cols-[36px,1fr] place-items-center p-2 disabled:opacity-40 {buttonStyle}"
disabled={renderedOption.disabled}
on:click={() => !renderedOption.disabled && handleSelectOption(option)}
onclick={() => !renderedOption.disabled && handleSelectOption(option)}
>
{#if isEqual(selectedOption, option)}
<div class="text-immich-primary dark:text-immich-dark-primary">

View file

@ -1,10 +1,14 @@
<script lang="ts">
import { generateId } from '$lib/utils/generate-id';
export let filters: string[];
export let selected: string;
export let label: string;
export let onSelect: (selected: string) => void;
interface Props {
filters: string[];
selected: string;
label: string;
onSelect: (selected: string) => void;
}
let { filters, selected, label, onSelect }: Props = $props();
const id = `group-tab-${generateId()}`;
</script>
@ -22,7 +26,7 @@
class="peer sr-only"
value={filter}
checked={filter === selected}
on:change={() => onSelect(filter)}
onchange={() => onSelect(filter)}
/>
<label
for="{id}-{index}"

View file

@ -1,22 +1,41 @@
<script lang="ts">
import type { AriaRole } from 'svelte/elements';
export let size: string | number = '1em';
export let color = 'currentColor';
export let path: string;
export let title: string | null = null;
export let desc = '';
export let flipped = false;
let className = '';
export { className as class };
export let viewBox = '0 0 24 24';
export let role: AriaRole = 'img';
export let ariaHidden: boolean | undefined = undefined;
export let ariaLabel: string | undefined = undefined;
export let ariaLabelledby: string | undefined = undefined;
export let strokeWidth: number = 0;
export let strokeColor: string = 'currentColor';
export let spin = false;
interface Props {
size?: string | number;
color?: string;
path: string;
title?: string | null;
desc?: string;
flipped?: boolean;
class?: string;
viewBox?: string;
role?: AriaRole;
ariaHidden?: boolean | undefined;
ariaLabel?: string | undefined;
ariaLabelledby?: string | undefined;
strokeWidth?: number;
strokeColor?: string;
spin?: boolean;
}
let {
size = '1em',
color = 'currentColor',
path,
title = null,
desc = '',
flipped = false,
class: className = '',
viewBox = '0 0 24 24',
role = 'img',
ariaHidden = undefined,
ariaLabel = undefined,
ariaLabelledby = undefined,
strokeWidth = 0,
strokeColor = 'currentColor',
spin = false,
}: Props = $props();
</script>
<svg

View file

@ -1,9 +1,13 @@
<script lang="ts">
export let id: string;
export let label: string;
export let name: string;
export let value: string;
export let group: string | undefined = undefined;
interface Props {
id: string;
label: string;
name: string;
value: string;
group?: string | undefined;
}
let { id, label, name, value, group = $bindable(undefined) }: Props = $props();
</script>
<div class="flex items-center space-x-2">

View file

@ -5,14 +5,25 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { t } from 'svelte-i18n';
export let name: string;
export let roundedBottom = true;
export let showLoadingSpinner: boolean;
export let placeholder: string;
export let onSearch: (options: SearchOptions) => void = () => {};
export let onReset: () => void = () => {};
interface Props {
name: string;
roundedBottom?: boolean;
showLoadingSpinner: boolean;
placeholder: string;
onSearch?: (options: SearchOptions) => void;
onReset?: () => void;
}
let inputRef: HTMLElement;
let {
name = $bindable(),
roundedBottom = true,
showLoadingSpinner,
placeholder,
onSearch = () => {},
onReset = () => {},
}: Props = $props();
let inputRef = $state<HTMLElement>();
const resetSearch = () => {
name = '';
@ -37,7 +48,7 @@
title={$t('search')}
size="16"
padding="2"
on:click={() => onSearch({ force: true })}
onclick={() => onSearch({ force: true })}
/>
<input
class="w-full gap-2 bg-gray-200 dark:bg-immich-dark-gray dark:text-white"
@ -45,8 +56,8 @@
{placeholder}
bind:value={name}
bind:this={inputRef}
on:keydown={handleSearch}
on:input={() => onSearch({ force: false })}
onkeydown={handleSearch}
oninput={() => onSearch({ force: false })}
/>
{#if showLoadingSpinner}
<div class="flex place-items-center">
@ -54,6 +65,6 @@
</div>
{/if}
{#if name}
<CircleIconButton icon={mdiClose} title={$t('clear_value')} size="16" padding="2" on:click={resetSearch} />
<CircleIconButton icon={mdiClose} title={$t('clear_value')} size="16" padding="2" onclick={resetSearch} />
{/if}
</div>

View file

@ -1,15 +1,25 @@
<script lang="ts">
/**
* Unique identifier for the checkbox element, used to associate labels with the input element.
*/
export let id: string;
/**
* Optional aria-describedby attribute to associate the checkbox with a description.
*/
export let ariaDescribedBy: string | undefined = undefined;
export let checked = false;
export let disabled = false;
export let onToggle: ((checked: boolean) => void) | undefined = undefined;
interface Props {
/**
* Unique identifier for the checkbox element, used to associate labels with the input element.
*/
id: string;
/**
* Optional aria-describedby attribute to associate the checkbox with a description.
*/
ariaDescribedBy?: string | undefined;
checked?: boolean;
disabled?: boolean;
onToggle?: ((checked: boolean) => void) | undefined;
}
let {
id,
ariaDescribedBy = undefined,
checked = $bindable(false),
disabled = false,
onToggle = undefined,
}: Props = $props();
const handleToggle = (event: Event) => onToggle?.((event.target as HTMLInputElement).checked);
</script>
@ -20,7 +30,7 @@
class="disabled::cursor-not-allowed h-0 w-0 opacity-0 peer"
type="checkbox"
bind:checked
on:click={handleToggle}
onclick={handleToggle}
{disabled}
aria-describedby={ariaDescribedBy}
/>