diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 0f8a64ee998..6a25adef893 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -2,32 +2,100 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; +import { VariantProps, cva } from "class-variance-authority"; import { FocusableElement } from "../shared/focusable-element"; -export type BadgeVariant = "primary" | "secondary" | "success" | "danger" | "warning" | "info"; +export type BadgeVariant = VariantProps["variant"]; -const styles: Record = { - primary: ["tw-bg-primary-100", "tw-border-primary-700", "!tw-text-primary-700"], - secondary: ["tw-bg-secondary-100", "tw-border-secondary-700", "!tw-text-secondary-700"], - success: ["tw-bg-success-100", "tw-border-success-700", "!tw-text-success-700"], - danger: ["tw-bg-danger-100", "tw-border-danger-700", "!tw-text-danger-700"], - warning: ["tw-bg-warning-100", "tw-border-warning-700", "!tw-text-warning-700"], - info: ["tw-bg-info-100", "tw-border-info-700", "!tw-text-info-700"], -}; - -const hoverStyles: Record = { - primary: ["hover:tw-bg-primary-600", "hover:tw-border-primary-600", "hover:!tw-text-contrast"], - secondary: [ - "hover:tw-bg-secondary-600", - "hover:tw-border-secondary-600", - "hover:!tw-text-contrast", +const badge = cva( + [ + "tw-inline-block", + "tw-py-1", + "tw-px-2", + "tw-font-medium", + "tw-text-center", + "tw-align-text-top", + "tw-rounded-full", + "tw-border-[0.5px]", + "tw-border-solid", + "tw-box-border", + "tw-whitespace-nowrap", + "tw-text-xs", + "hover:tw-no-underline", + "focus-visible:tw-outline-none", + "focus-visible:tw-ring-2", + "focus-visible:tw-ring-offset-2", + "focus-visible:tw-ring-primary-600", + "disabled:tw-bg-secondary-300", + "disabled:hover:tw-bg-secondary-300", + "disabled:tw-border-secondary-300", + "disabled:hover:tw-border-secondary-300", + "disabled:!tw-text-muted", + "disabled:hover:!tw-text-muted", + "disabled:tw-cursor-not-allowed", ], - success: ["hover:tw-bg-success-600", "hover:tw-border-success-600", "hover:!tw-text-contrast"], - danger: ["hover:tw-bg-danger-600", "hover:tw-border-danger-600", "hover:!tw-text-contrast"], - warning: ["hover:tw-bg-warning-600", "hover:tw-border-warning-600", "hover:!tw-text-black"], - info: ["hover:tw-bg-info-600", "hover:tw-border-info-600", "hover:!tw-text-black"], -}; + { + variants: { + variant: { + primary: ["tw-bg-primary-100", "tw-border-primary-700", "!tw-text-primary-700"], + secondary: ["tw-bg-secondary-100", "tw-border-secondary-700", "!tw-text-secondary-700"], + success: ["tw-bg-success-100", "tw-border-success-700", "!tw-text-success-700"], + danger: ["tw-bg-danger-100", "tw-border-danger-700", "!tw-text-danger-700"], + warning: ["tw-bg-warning-100", "tw-border-warning-700", "!tw-text-warning-700"], + info: ["tw-bg-info-100", "tw-border-info-700", "!tw-text-info-700"], + }, + hover: { + true: null, + false: null, + }, + }, + compoundVariants: [ + { + variant: "primary", + hover: true, + class: [ + "hover:tw-bg-primary-600", + "hover:tw-border-primary-600", + "hover:!tw-text-contrast", + ], + }, + { + variant: "secondary", + hover: true, + class: [ + "hover:tw-bg-secondary-600", + "hover:tw-border-secondary-600", + "hover:!tw-text-contrast", + ], + }, + { + variant: "success", + hover: true, + class: [ + "hover:tw-bg-success-600", + "hover:tw-border-success-600", + "hover:!tw-text-contrast", + ], + }, + { + variant: "danger", + hover: true, + class: ["hover:tw-bg-danger-600", "hover:tw-border-danger-600", "hover:!tw-text-contrast"], + }, + { + variant: "warning", + hover: true, + class: ["hover:tw-bg-warning-600", "hover:tw-border-warning-600", "hover:!tw-text-black"], + }, + { + variant: "info", + hover: true, + class: ["hover:tw-bg-info-600", "hover:tw-border-info-600", "hover:!tw-text-black"], + }, + ], + }, +); @Component({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", @@ -38,36 +106,13 @@ const hoverStyles: Record = { }) export class BadgeComponent implements FocusableElement { @HostBinding("class") get classList() { - return [ - "tw-inline-block", - "tw-py-1", - "tw-px-2", - "tw-font-medium", - "tw-text-center", - "tw-align-text-top", - "tw-rounded-full", - "tw-border-[0.5px]", - "tw-border-solid", - "tw-box-border", - "tw-whitespace-nowrap", - "tw-text-xs", - "hover:tw-no-underline", - "focus-visible:tw-outline-none", - "focus-visible:tw-ring-2", - "focus-visible:tw-ring-offset-2", - "focus-visible:tw-ring-primary-600", - "disabled:tw-bg-secondary-300", - "disabled:hover:tw-bg-secondary-300", - "disabled:tw-border-secondary-300", - "disabled:hover:tw-border-secondary-300", - "disabled:!tw-text-muted", - "disabled:hover:!tw-text-muted", - "disabled:tw-cursor-not-allowed", - ] - .concat(styles[this.variant]) - .concat(this.hasHoverEffects ? [...hoverStyles[this.variant], "tw-min-w-10"] : []) - .concat(this.truncate ? this.maxWidthClass : []); + return badge({ + variant: this.variant, + hover: this.hasHoverEffects, + class: this.truncate ? this.maxWidthClass : undefined, + }); } + @HostBinding("attr.title") get titleAttr() { if (this.title !== undefined) { return this.title; diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 0b4ce3073c3..fd172d57b1e 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -4,6 +4,7 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { NgClass } from "@angular/common"; import { Input, HostBinding, Component, model, computed } from "@angular/core"; import { toObservable, toSignal } from "@angular/core/rxjs-interop"; +import { cva } from "class-variance-authority"; import { debounce, interval } from "rxjs"; import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; @@ -14,36 +15,71 @@ const focusRing = [ "focus-visible:tw-ring-primary-600", "focus-visible:tw-z-10", ]; - -const buttonStyles: Record = { - primary: [ - "tw-border-primary-600", - "tw-bg-primary-600", - "!tw-text-contrast", - "hover:tw-bg-primary-700", - "hover:tw-border-primary-700", - ...focusRing, +const button = cva( + [ + "tw-font-semibold", + "tw-py-1.5", + "tw-px-3", + "tw-rounded-full", + "tw-transition", + "tw-border-2", + "tw-border-solid", + "tw-text-center", + "tw-no-underline", + "hover:tw-no-underline", + "focus:tw-outline-none", ], - secondary: [ - "tw-bg-transparent", - "tw-border-primary-600", - "!tw-text-primary-600", - "hover:tw-bg-primary-600", - "hover:tw-border-primary-600", - "hover:!tw-text-contrast", - ...focusRing, - ], - danger: [ - "tw-bg-transparent", - "tw-border-danger-600", - "!tw-text-danger", - "hover:tw-bg-danger-600", - "hover:tw-border-danger-600", - "hover:!tw-text-contrast", - ...focusRing, - ], - unstyled: [], -}; + { + variants: { + variant: { + primary: [ + "tw-border-primary-600", + "tw-bg-primary-600", + "!tw-text-contrast", + "hover:tw-bg-primary-700", + "hover:tw-border-primary-700", + ...focusRing, + ], + secondary: [ + "tw-bg-transparent", + "tw-border-primary-600", + "!tw-text-primary-600", + "hover:tw-bg-primary-600", + "hover:tw-border-primary-600", + "hover:!tw-text-contrast", + ...focusRing, + ], + danger: [ + "tw-bg-transparent", + "tw-border-danger-600", + "!tw-text-danger", + "hover:tw-bg-danger-600", + "hover:tw-border-danger-600", + "hover:!tw-text-contrast", + ...focusRing, + ], + unstyled: [], + }, + disabled: { + true: [ + "disabled:tw-bg-secondary-300", + "disabled:hover:tw-bg-secondary-300", + "disabled:tw-border-secondary-300", + "disabled:hover:tw-border-secondary-300", + "disabled:!tw-text-muted", + "disabled:hover:!tw-text-muted", + "disabled:tw-cursor-not-allowed", + "disabled:hover:tw-no-underline", + ], + false: null, + }, + block: { + true: ["tw-w-full", "tw-block"], + false: ["tw-inline-block"], + }, + }, + }, +); @Component({ selector: "button[bitButton], a[bitButton]", @@ -57,35 +93,11 @@ const buttonStyles: Record = { }) export class ButtonComponent implements ButtonLikeAbstraction { @HostBinding("class") get classList() { - return [ - "tw-font-semibold", - "tw-py-1.5", - "tw-px-3", - "tw-rounded-full", - "tw-transition", - "tw-border-2", - "tw-border-solid", - "tw-text-center", - "tw-no-underline", - "hover:tw-no-underline", - "focus:tw-outline-none", - ] - .concat(this.block ? ["tw-w-full", "tw-block"] : ["tw-inline-block"]) - .concat(buttonStyles[this.buttonType ?? "secondary"]) - .concat( - this.showDisabledStyles() || this.disabled() - ? [ - "disabled:tw-bg-secondary-300", - "disabled:hover:tw-bg-secondary-300", - "disabled:tw-border-secondary-300", - "disabled:hover:tw-border-secondary-300", - "disabled:!tw-text-muted", - "disabled:hover:!tw-text-muted", - "disabled:tw-cursor-not-allowed", - "disabled:hover:tw-no-underline", - ] - : [], - ); + return button({ + variant: this.buttonType ?? "secondary", + block: this.block, + disabled: this.showDisabledStyles() || this.disabled(), + }); } protected disabledAttr = computed(() => { diff --git a/package-lock.json b/package-lock.json index 7331fcdde44..30829f635ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "buffer": "6.0.3", "bufferutil": "4.0.9", "chalk": "4.1.2", + "class-variance-authority": "0.7.1", "commander": "11.1.0", "core-js": "3.40.0", "form-data": "4.0.1", @@ -15771,6 +15772,17 @@ "dev": true, "license": "MIT" }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -15939,6 +15951,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", diff --git a/package.json b/package.json index 943fde5fcf3..5ab11ebc9e3 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "buffer": "6.0.3", "bufferutil": "4.0.9", "chalk": "4.1.2", + "class-variance-authority": "0.7.1", "commander": "11.1.0", "core-js": "3.40.0", "form-data": "4.0.1",