diff --git a/components/Button.js b/components/Button.js
index 6376cfd2394..537adeee16d 100644
--- a/components/Button.js
+++ b/components/Button.js
@@ -1,48 +1,59 @@
+import React from "react";
import { classNames } from "@services/utils/classNames";
import Link from "./Link";
+import { cva } from "class-variance-authority";
-export default function Button({
- primary = false,
- disabled = false,
- className,
- overrideClassNames = false,
- children,
- ...restProps
-}) {
- let defaultClassName = classNames(
- "w-full inline-flex items-center flex-1 justify-center rounded-md border-2 border-primary-high dark:border-white hover:border-transparent px-5 py-3 text-base font-medium first-letter:bg-white transition duration-400 ease-in-out",
- !disabled
- ? primary
- ? " text-primary-medium bg-secondary-medium hover:bg-tertiary-medium"
- : " text-secondary-high dark:text-secondary-high-high hover:text-white dark:hover:text-white dark:bg-primary-low hover:bg-secondary-medium dark:hover:bg-secondary-medium"
- : disabled
- ? " border-2 border-red border shadow-sm bg-primary-low text-primary-medium cursor-not-allowed "
- : " cursor-pointer",
- );
+const buttonVariants = cva(
+ "w-full inline-flex items-center flex-1 justify-center cursor-pointer rounded-md border-2 border-primary-high dark:border-white hover:border-transparent px-5 py-3 text-base font-medium first-letter:bg-white transition duration-400 ease-in-out",
+ {
+ variants: {
+ variant: {
+ primary:
+ " text-primary-medium bg-secondary-medium hover:bg-tertiary-medium",
+ default:
+ "text-secondary-high dark:text-secondary-high-high hover:text-white dark:hover:text-white dark:bg-primary-low hover:bg-secondary-medium dark:hover:bg-secondary-medium",
+ disabled:
+ "border-2 border-red border shadow-sm bg-primary-low text-primary-medium cursor-not-allowed",
+ },
+ },
- const link = (
-
- {children}
-
- );
+ defaultVariants: {
+ variant: "default",
+ },
+ },
+);
- const button = (
-
- );
+/**
+ * @typedef {Object} ButtonProps
+ * @property {boolean} [disabled]
+ * @property {"primary" | "secondary" | "disabled"} [variant]
+ * @property {string} [className]
+ * @property {string} [href]
+ * @property {string} [ref]
+ * @property {React.ReactNode} [children]
+ */
- return restProps.href ? link : button;
-}
+/**
+ * @type {React.ForwardRefExoticComponent>}
+ */
+const Button = React.forwardRef(function (
+ { disabled = false, className, variant, href, children, ...restProps },
+ ref,
+) {
+ const Component = href ? Link : "button";
+ const componentProps = {
+ ref,
+ className: classNames(
+ buttonVariants({ variant: disabled ? "disabled" : variant, className }),
+ ),
+ disabled,
+ ...restProps,
+ };
+ if (href) {
+ componentProps["href"] = href;
+ }
+ return {children};
+});
+
+Button.displayName = "Button";
+export default Button;
diff --git a/package-lock.json b/package-lock.json
index 6c5dfb5bdd8..524a62e0900 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,8 @@
"@tailwindcss/typography": "^0.5.10",
"@vercel/analytics": "^1.1.1",
"autoprefixer": "^10.4.16",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.0",
"dotenv": "^16.3.1",
"file-saver": "^2.0.5",
"husky": "^8.0.3",
@@ -52,6 +54,7 @@
"strip-markdown": "^6.0.0",
"stripe": "^12.18.0",
"supercluster": "^8.0.1",
+ "tailwind-merge": "^2.2.0",
"tailwindcss": "^3.3.7",
"use-supercluster": "^1.1.0",
"zod": "^3.22.4"
@@ -9879,6 +9882,25 @@
"consola": "^3.2.3"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz",
+ "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==",
+ "dependencies": {
+ "clsx": "2.0.0"
+ },
+ "funding": {
+ "url": "https://joebell.co.uk"
+ }
+ },
+ "node_modules/class-variance-authority/node_modules/clsx": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz",
+ "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
@@ -10008,9 +10030,9 @@
}
},
"node_modules/clsx": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.1.tgz",
- "integrity": "sha512-PrYqtepbSWwrFmtHX0IHv3zUEbWn1psIqaIZyjB3b9wEGpRAkpyeKbCCCkCXJYiHZPjK7jckJvf2sEB/8VKPgQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
+ "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
"engines": {
"node": ">=6"
}
@@ -22308,6 +22330,18 @@
"integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==",
"dev": true
},
+ "node_modules/tailwind-merge": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.0.tgz",
+ "integrity": "sha512-SqqhhaL0T06SW59+JVNfAqKdqLs0497esifRrZ7jOaefP3o64fdFNDMrAQWZFMxTLJPiHVjRLUywT8uFz1xNWQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.5"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.7.tgz",
diff --git a/package.json b/package.json
index e3ab11b350c..bfe3382e0f0 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,8 @@
"@tailwindcss/typography": "^0.5.10",
"@vercel/analytics": "^1.1.1",
"autoprefixer": "^10.4.16",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.0",
"dotenv": "^16.3.1",
"file-saver": "^2.0.5",
"husky": "^8.0.3",
@@ -49,6 +51,7 @@
"strip-markdown": "^6.0.0",
"stripe": "^12.18.0",
"supercluster": "^8.0.1",
+ "tailwind-merge": "^2.2.0",
"tailwindcss": "^3.3.7",
"use-supercluster": "^1.1.0",
"zod": "^3.22.4"
diff --git a/services/utils/classNames.js b/services/utils/classNames.js
index 28cc7a750a1..3719091bb60 100644
--- a/services/utils/classNames.js
+++ b/services/utils/classNames.js
@@ -1,3 +1,6 @@
-export const classNames = (...classes) => {
- return classes.filter(Boolean).join(" ");
-};
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function classNames(...inputs) {
+ return twMerge(clsx(inputs));
+}