refactor: Refactor Button and GroupedListItem components

Improve component typing with more specific HTML attribute types and
implement conditional rendering based on properties. Use Svelte snippets
to avoid code duplication and enhance component flexibility.
This commit is contained in:
Shibo Lyu 2025-04-17 02:28:08 +08:00
parent 9e79b29c69
commit c379933787
3 changed files with 67 additions and 47 deletions

9
.zed/settings.json Normal file
View file

@ -0,0 +1,9 @@
{
"lsp": {
"tailwindcss-language-server": {
"settings": {
"classFunctions": ["tw"]
}
}
}
}

View file

@ -1,28 +1,17 @@
<script lang="ts">
import { tw } from '$lib/tw';
import type { HTMLAttributes } from 'svelte/elements';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
type Props = {
variant?: 'primary' | 'secondary';
class?: string;
children?: import('svelte').Snippet;
[key: string]: unknown;
} & (
| ({
href: string;
} & HTMLAttributes<HTMLAnchorElement>)
| ({
href?: undefined;
} & HTMLAttributes<HTMLButtonElement>)
);
} & Omit<HTMLAnchorAttributes | HTMLButtonAttributes, 'class'>;
let { variant = 'secondary', class: className = '', href, children, ...rest }: Props = $props();
</script>
let { variant = 'secondary', class: externalClass, children, ...rest }: Props = $props();
<svelte:element
this={href ? 'a' : 'button'}
{href}
class={tw(
const className = $derived(
tw(
'text-sf-secondary bg-sb-primary ring-ss-secondary inline-flex cursor-default items-center justify-center rounded-md px-2.5 py-1 shadow-xs ring-1',
'hover:ring-ss-primary font-normal transition-shadow duration-200 active:shadow-inner',
variant === 'primary' && [
@ -31,15 +20,21 @@
'before:from-accent-400 before:to-accent-500 before:bg-linear-to-b before:from-40% before:ring-1 before:ring-black/10 before:ring-inset',
'dark:before:from-accent-500 dark:before:to-accent-600 before:transition-shadow active:before:shadow-inner'
],
className
)}
role="button"
tabindex="0"
{...rest}
>
externalClass
)
);
</script>
{#snippet content()}
{#if variant === 'primary'}
<div class="z-10 drop-shadow-[0_-1px_0_--theme(--color-black/0.2)]">{@render children?.()}</div>
{:else}
{@render children?.()}
{/if}
</svelte:element>
{/snippet}
{#if 'href' in rest}
<a class={className} {...rest}>{@render content()}</a>
{:else}
<button class={className} {...rest}>{@render content()}</button>
{/if}

View file

@ -1,29 +1,37 @@
<script lang="ts">
import { tw } from '$lib/tw';
import { Icon, type IconSource } from 'svelte-hero-icons';
import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
interface Props {
href?: string | undefined;
type Props = {
icon?: IconSource | undefined;
selected?: boolean;
children?: import('svelte').Snippet;
onclick?: (e: MouseEvent) => void;
}
class?: string;
} & (
| ({ href: string } & Omit<HTMLAnchorAttributes, 'class' | 'href'>)
| ({ onclick: Required<HTMLButtonAttributes['onclick']> } & Omit<HTMLButtonAttributes, 'class'>)
| Omit<HTMLAttributes<HTMLDivElement>, 'onclick'>
);
let { href = undefined, icon = undefined, selected = false, children, onclick }: Props = $props();
let {
icon = undefined,
selected = false,
children,
class: externalClass,
...rest
}: Props = $props();
const className = $derived(
tw(
'text-sf-primary flex w-full cursor-default items-center gap-2 px-4 py-3 font-medium first:rounded-t-lg last:rounded-b-lg',
selected && 'bg-accent-500 dark:bg-accent-900 dark:text-sf-primary text-white shadow-inner',
externalClass
)
);
</script>
<svelte:element
this={href ? 'a' : 'button'}
{href}
class={tw(
'text-sf-primary flex w-full cursor-default items-center gap-2 px-4 py-3 font-medium first:rounded-t-lg last:rounded-b-lg',
selected && 'bg-accent-500 dark:bg-accent-900 dark:text-sf-primary text-white shadow-inner'
)}
tabindex="0"
role="button"
{onclick}
>
{#snippet content()}
{#if icon}
<Icon
src={icon}
@ -34,4 +42,12 @@
<div class="min-w-0 truncate text-start">
{@render children?.()}
</div>
</svelte:element>
{/snippet}
{#if 'href' in rest}
<a class={className} {...rest}>{@render content()}</a>
{:else if 'onclick' in rest}
<button class={className} {...rest}>{@render content()}</button>
{:else}
<div class={className} {...rest}>{@render content()}</div>
{/if}