mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-18 19:22:58 +03:00
120 lines
3.7 KiB
Svelte
120 lines
3.7 KiB
Svelte
<script lang="ts" module>
|
|
import { cn, type WithElementRef } from '$lib/utils/style.js';
|
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
import { tv, type VariantProps } from 'tailwind-variants';
|
|
|
|
export const buttonVariants = tv({
|
|
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
variants: {
|
|
variant: {
|
|
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
|
destructive:
|
|
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
|
|
outline:
|
|
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
|
|
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
|
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
link: 'text-primary underline-offset-4 hover:underline'
|
|
},
|
|
size: {
|
|
default: 'h-10 px-4 py-2',
|
|
sm: 'h-9 rounded-md px-3',
|
|
lg: 'h-11 rounded-md px-8',
|
|
icon: 'h-10 w-10'
|
|
}
|
|
},
|
|
defaultVariants: {
|
|
variant: 'default',
|
|
size: 'default'
|
|
}
|
|
});
|
|
|
|
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
|
|
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
|
|
|
|
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
|
WithElementRef<HTMLAnchorAttributes> & {
|
|
variant?: ButtonVariant;
|
|
size?: ButtonSize;
|
|
isLoading?: boolean;
|
|
autofocus?: boolean;
|
|
};
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
import LoaderCircle from '@lucide/svelte/icons/loader-circle';
|
|
import { onMount } from 'svelte';
|
|
|
|
let {
|
|
class: className,
|
|
variant = 'default',
|
|
size = 'default',
|
|
ref = $bindable(null),
|
|
href = undefined,
|
|
type = 'button',
|
|
disabled,
|
|
isLoading = false,
|
|
autofocus = false,
|
|
onclick,
|
|
usePromiseLoading = false,
|
|
children,
|
|
...restProps
|
|
}: ButtonProps & {
|
|
usePromiseLoading?: boolean;
|
|
} = $props();
|
|
|
|
onMount(async () => {
|
|
// Using autofocus can be bad for a11y, but in the case of Pocket ID is only used responsibly in places where there are not many choices, and on buttons only where there's descriptive text
|
|
if (autofocus) {
|
|
// Use setTimeout to make sure the element is showing
|
|
setTimeout(() => ref?.focus(), 100);
|
|
}
|
|
});
|
|
|
|
async function handleOnClick(event: any) {
|
|
if (usePromiseLoading && onclick) {
|
|
isLoading = true;
|
|
try {
|
|
await onclick(event);
|
|
} finally {
|
|
isLoading = false;
|
|
}
|
|
} else {
|
|
onclick?.(event);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if href}
|
|
<a
|
|
bind:this={ref}
|
|
data-slot="button"
|
|
class={cn(buttonVariants({ variant, size }), className)}
|
|
href={disabled ? undefined : href}
|
|
aria-disabled={disabled}
|
|
role={disabled ? 'link' : undefined}
|
|
tabindex={disabled ? -1 : undefined}
|
|
{...restProps}
|
|
>
|
|
{#if isLoading}
|
|
<LoaderCircle class="size-4 animate-spin" />
|
|
{/if}
|
|
{@render children?.()}
|
|
</a>
|
|
{:else}
|
|
<button
|
|
bind:this={ref}
|
|
data-slot="button"
|
|
class={cn(buttonVariants({ variant, size }), className)}
|
|
{type}
|
|
disabled={disabled || isLoading}
|
|
onclick={handleOnClick}
|
|
{...restProps}
|
|
>
|
|
{#if isLoading}
|
|
<LoaderCircle class="size-4 animate-spin" />
|
|
{/if}
|
|
{@render children?.()}
|
|
</button>
|
|
{/if}
|