refactor(Button): migrate to svelte5 (#12079)

* refactor(Button): migrate to svelte5

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* fix: support legacy

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* fix: make aria-label optional

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* fix: should not use render for non-snippet

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

---------

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
This commit is contained in:
axel7083 2025-04-09 15:32:53 +02:00 committed by GitHub
parent b0ebc44707
commit c40ad4e0ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,101 +1,116 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { Snippet } from 'svelte';
import { createEventDispatcher } from 'svelte';
import Fa from 'svelte-fa';
import Spinner from '../progress/Spinner.svelte';
import { isFontAwesomeIcon } from '../utils/icon-utils';
import type { ButtonType } from './Button';
export let title: string | undefined = undefined;
export let inProgress = false;
export let disabled = false;
export let type: ButtonType = 'primary';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let icon: any = undefined;
export let selected: boolean | undefined = undefined;
$: if (selected !== undefined && type !== 'tab') {
console.error('property selected can be used with type=tab only');
interface Props {
title?: string;
inProgress?: boolean;
disabled?: boolean;
type?: ButtonType;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
icon?: any;
selected?: boolean;
padding?: string;
class?: string;
hidden?: boolean;
'aria-label'?: string;
onclick?: () => void;
children?: Snippet;
}
export let padding: string =
'px-4 ' + (type === 'tab' ? 'pb-1' : type === 'secondary' ? 'py-[4px]' : type === 'danger' ? 'py-[3px]' : 'py-[5px]');
let iconType: string | undefined = undefined;
// support legacy usage (on:click)
const dispatch = createEventDispatcher<{ click: undefined }>();
onMount(() => {
if (isFontAwesomeIcon(icon)) {
iconType = 'fa';
} else {
iconType = 'unknown';
}
});
let {
title,
inProgress = false,
disabled = false,
type = 'primary',
icon,
selected,
padding = 'px-4 ' +
(type === 'tab' ? 'pb-1' : type === 'secondary' ? 'py-[4px]' : type === 'danger' ? 'py-[3px]' : 'py-[5px]'),
class: classNames,
hidden,
'aria-label': ariaLabel,
onclick = dispatch.bind(undefined, 'click'),
children,
}: Props = $props();
let classes = '';
$: {
let iconType: string = $derived(isFontAwesomeIcon(icon) ? 'fa' : 'unknown');
let classes = $derived.by(() => {
let result: string = '';
if (disabled || inProgress) {
if (type === 'primary') {
classes = 'bg-[var(--pd-button-disabled)]';
result = 'bg-[var(--pd-button-disabled)]';
} else if (type === 'secondary') {
classes = 'border-[1px] border-[var(--pd-button-disabled)] bg-[var(--pd-button-disabled)]';
result = 'border-[1px] border-[var(--pd-button-disabled)] bg-[var(--pd-button-disabled)]';
} else if (type === 'danger') {
classes =
result =
'border-2 border-[var(--pd-button-danger-disabled-border)] text-[var(--pd-button-danger-disabled-text)] bg-[var(--pd-button-danger-disabled-bg)]';
}
if (type !== 'danger') {
classes += ' text-[var(--pd-button-disabled-text)]';
result += ' text-[var(--pd-button-disabled-text)]';
}
} else if (type === 'primary') {
classes =
result =
'bg-[var(--pd-button-primary-bg)] text-[var(--pd-button-text)] border-none hover:bg-[var(--pd-button-primary-hover-bg)]';
} else if (type === 'secondary') {
classes =
result =
'border-[1px] border-[var(--pd-button-secondary)] text-[var(--pd-button-secondary)] hover:bg-[var(--pd-button-secondary-hover)] hover:border-[var(--pd-button-secondary-hover)] hover:text-[var(--pd-button-text)]';
} else if (type === 'danger') {
classes =
result =
'border-2 border-[var(--pd-button-danger-border)] bg-[var(--pd-button-danger-bg)] text-[var(--pd-button-danger-text)] hover:bg-[var(--pd-button-danger-hover-bg)] hover:text-[var(--pd-button-danger-hover-text)]';
} else if (type === 'tab') {
classes = 'border-b-[3px] border-[var(--pd-button-tab-border)]';
result = 'border-b-[3px] border-[var(--pd-button-tab-border)]';
} else {
// link
classes = 'border-none text-[var(--pd-button-link-text)] hover:bg-[var(--pd-button-link-hover-bg)]';
result = 'border-none text-[var(--pd-button-link-text)] hover:bg-[var(--pd-button-link-hover-bg)]';
}
if (type !== 'tab') {
classes += ' rounded-[4px]';
result += ' rounded-[4px]';
}
}
return result;
});
</script>
<button
type="button"
class="relative {padding} box-border whitespace-nowrap select-none transition-all outline-transparent focus:outline-[var(--pd-button-primary-hover-bg)] {classes} {$$props.class ??
''}"
class="relative {padding} box-border whitespace-nowrap select-none transition-all outline-transparent focus:outline-[var(--pd-button-primary-hover-bg)] {classes} {classNames}"
class:border-[var(--pd-button-tab-border-selected)]={type === 'tab' && selected}
class:hover:border-[var(--pd-button-tab-hover-border)]={type === 'tab' && !selected}
class:text-[var(--pd-button-tab-text-selected)]={type === 'tab' && selected}
class:text-[var(--pd-button-tab-text)]={type === 'tab' && !selected}
hidden={$$props.hidden}
hidden={hidden}
title={title}
aria-label={$$props['aria-label']}
on:click
aria-label={ariaLabel}
onclick={onclick}
disabled={disabled || inProgress}>
{#if icon ?? inProgress}
<div
class="flex flex-row p-0 m-0 bg-transparent justify-center items-center space-x-[4px]"
class:py-[3px]={!$$slots.default}>
class:py-[3px]={!children}>
{#if inProgress}
<Spinner size="1em" />
{:else if isFontAwesomeIcon(icon)}
<Fa icon={icon} />
{:else if iconType === 'unknown'}
<svelte:component this={icon} />
{@const Icon = icon}
<Icon/>
{/if}
{#if $$slots.default}
<span><slot /></span>
{#if children}
<span>{@render children()}</span>
{/if}
</div>
{:else}
<slot />
{@render children?.()}
{/if}
</button>