button.svelte 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. <script lang="ts" module>
  2. import { cn, type WithElementRef } from "$lib/utils.js";
  3. import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
  4. import { type VariantProps, tv } from "tailwind-variants";
  5. export const buttonVariants = tv({
  6. 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 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  7. variants: {
  8. variant: {
  9. default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-xs",
  10. destructive:
  11. "bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white shadow-xs",
  12. outline:
  13. "bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs",
  14. secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs",
  15. ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
  16. link: "text-primary underline-offset-4 hover:underline",
  17. },
  18. size: {
  19. default: "h-9 px-4 py-2 has-[>svg]:px-3",
  20. sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
  21. lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
  22. icon: "size-9",
  23. "icon-sm": "size-8",
  24. "icon-lg": "size-10",
  25. },
  26. },
  27. defaultVariants: {
  28. variant: "default",
  29. size: "default",
  30. },
  31. });
  32. export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
  33. export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
  34. export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
  35. WithElementRef<HTMLAnchorAttributes> & {
  36. variant?: ButtonVariant;
  37. size?: ButtonSize;
  38. };
  39. </script>
  40. <script lang="ts">
  41. let {
  42. class: className,
  43. variant = "default",
  44. size = "default",
  45. ref = $bindable(null),
  46. href = undefined,
  47. type = "button",
  48. disabled,
  49. children,
  50. ...restProps
  51. }: ButtonProps = $props();
  52. </script>
  53. {#if href}
  54. <a
  55. bind:this={ref}
  56. data-slot="button"
  57. class={cn(buttonVariants({ variant, size }), className)}
  58. href={disabled ? undefined : href}
  59. aria-disabled={disabled}
  60. role={disabled ? "link" : undefined}
  61. tabindex={disabled ? -1 : undefined}
  62. {...restProps}
  63. >
  64. {@render children?.()}
  65. </a>
  66. {:else}
  67. <button
  68. bind:this={ref}
  69. data-slot="button"
  70. class={cn(buttonVariants({ variant, size }), className)}
  71. {type}
  72. {disabled}
  73. {...restProps}
  74. >
  75. {@render children?.()}
  76. </button>
  77. {/if}