mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
chore(web): migration svelte 5 syntax (#13883)
This commit is contained in:
parent
9203a61709
commit
0b3742cf13
310 changed files with 6435 additions and 4176 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue