This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / ui / Button.svelte
4.6 kB 110 lines
1<script module lang="ts"> 2 import { tv, type VariantProps } from "tailwind-variants"; 3 4 export const button = tv({ 5 base: "inline-flex cursor-pointer select-none items-center justify-center overflow-hidden rounded no-underline transition-[background-color,border-color,color] duration-150 ease-[cubic-bezier(0.4,0,0.2,1)] hover:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50", 6 variants: { 7 variant: { 8 default: 9 "border border-border-default bg-background-default text-foreground-default hover:bg-background-subtle focus-visible:outline-border-focus", 10 primary: 11 "border border-border-success bg-background-success-emphasis text-foreground-on-emphasis hover:bg-background-success-emphasis-hover focus-visible:outline-background-success-emphasis", 12 ghost: 13 "bg-transparent text-foreground-muted hover:bg-background-inset focus-visible:outline-border-focus", 14 flat: "bg-gray-50 text-foreground-default hover:bg-gray-100 focus-visible:outline-border-focus dark:bg-gray-900 dark:hover:bg-gray-700", 15 // danger / success / warning are outline buttons in Figma: default surface + 16 // border, colored text only (bound to the fg-* tokens), no accent fill. 17 danger: 18 "border border-border-default bg-background-default text-foreground-danger hover:bg-background-subtle focus-visible:outline-foreground-danger", 19 success: 20 "border border-border-default bg-background-default text-foreground-success hover:bg-background-subtle focus-visible:outline-foreground-success", 21 warning: 22 "border border-border-default bg-background-default text-foreground-warning hover:bg-background-subtle focus-visible:outline-foreground-warning" 23 }, 24 size: { 25 sm: "min-h-8 gap-1.5 px-2 py-1.5 text-sm", 26 md: "min-h-9 gap-2 px-3 py-1.5 text-sm", 27 lg: "min-h-10 gap-2.5 px-6 py-2 text-base" 28 } 29 }, 30 compoundVariants: [ 31 // raised variants draw inset depth via a ::before layer. 32 { 33 variant: "default", 34 class: 35 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:content-[''] before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-subtle)] before:transition-all before:duration-150 hover:before:shadow-[inset_0_-2px_0_0_var(--color-shadow-inner-strong-hover)] active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_var(--color-shadow-inner-subtle)] disabled:active:translate-y-0" 36 }, 37 { 38 variant: "primary", 39 class: 40 "relative z-0 before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:content-[''] before:shadow-[inset_0_-2px_0_0_rgb(3_7_18/0.25)] before:transition-all before:duration-150 active:translate-y-0.5 active:before:shadow-[inset_0_2px_2px_0_rgb(3_7_18/0.5)] disabled:active:translate-y-0" 41 } 42 ], 43 defaultVariants: { 44 variant: "default", 45 size: "md" 46 } 47 }); 48 49 export type ButtonVariants = VariantProps<typeof button>; 50</script> 51 52<script lang="ts"> 53 import { resolve } from "$app/paths"; 54 import type { Snippet } from "svelte"; 55 import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; 56 import Spinner from "./Spinner.svelte"; 57 58 interface Props extends Omit<HTMLAnchorAttributes & HTMLButtonAttributes, "class" | "children"> { 59 variant?: ButtonVariants["variant"]; 60 size?: ButtonVariants["size"]; 61 href?: string; 62 type?: "button" | "submit" | "reset"; 63 disabled?: boolean; 64 class?: string; 65 loading?: boolean; 66 spinnerClass?: string; 67 children?: Snippet; 68 } 69 70 let { 71 variant = "default", 72 size = "md", 73 href, 74 type = "button", 75 disabled = false, 76 class: className, 77 loading = false, 78 spinnerClass, 79 children, 80 ...rest 81 }: Props = $props(); 82 83 const classes = $derived(button({ variant, size, class: className })); 84 const spinnerSize = $derived(spinnerClass ?? (size === "lg" ? "size-5" : "size-4")); 85</script> 86 87{#if href} 88 <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -- internal hrefs go through resolve(), external ones must pass through untouched --> 89 <a 90 href={href?.startsWith("/") ? resolve(href as "/") : href} 91 class={classes} 92 aria-disabled={disabled || loading ? "true" : undefined} 93 tabindex={disabled ? -1 : undefined} 94 {...rest} 95 > 96 {#if loading} 97 <Spinner class={spinnerSize} /> 98 {:else if children} 99 {@render children()} 100 {/if} 101 </a> 102{:else} 103 <button {type} disabled={disabled || loading} class={classes} aria-busy={loading} {...rest}> 104 {#if loading} 105 <Spinner class={spinnerSize} /> 106 {:else if children} 107 {@render children()} 108 {/if} 109 </button> 110{/if}