import { html, PropertyValues, unsafeCSS } from "lit";
import { property, query, state } from "lit/decorators.js";
import eleStyle from "./f-icon-button.scss?inline";
import globalStyle from "./f-icon-button-global.scss?inline";
import { FRoot } from "../../mixins/components/f-root/f-root";
import { classMap } from "lit-html/directives/class-map.js";
import { unsafeSVG } from "lit-html/directives/unsafe-svg.js";
import loader from "../../mixins/svg/loader";
import { FIcon } from "../f-icon/f-icon";
import { FCounter } from "../f-counter/f-counter";
import { validateHTMLColorName } from "validate-color";
import { validateHTMLColor } from "validate-color";
import getTextContrast from "../../utils/get-text-contrast";
import getCustomFillColor from "../../utils/get-custom-fill-color";
import LightenDarkenColor from "../../utils/get-lighten-darken-color";
import { flowElement } from "./../../utils";
import { injectCss } from "@cldcvr/flow-core-config";
injectCss("f-icon-button", globalStyle);
const variants = ["round", "curved", "block"] as const;
const categories = ["fill", "outline", "transparent", "packed"] as const;
const sizes = ["large", "medium", "small", "x-small"] as const;
export type FIconButtonVariant = (typeof variants)[number];
export type FIconButtonType = (typeof categories)[number];
export type FIconButtonSize = (typeof sizes)[number];
export type FIconButtonState =
| "primary"
| "danger"
| "warning"
| "success"
| "neutral"
| "inherit"
| `custom, ${string}`;
@flowElement("f-icon-button")
export class FIconButton extends FRoot {
/**
* css loaded from scss file
*/
static styles = [unsafeCSS(eleStyle), unsafeCSS(globalStyle), ...FIcon.styles];
/**
* @attribute local state for managing custom fill.
*/
@state()
fill = "";
/**
* @attribute local state for managing input class.
*/
@state()
iconInputClass = false;
/**
* @attribute Icon property defines what icon will be displayed on the icon. It can take the icon name from a library , any inline SVG or any URL for the image.
*/
@property({ type: String })
icon!: string;
/**
* @attribute Variants are various representations of an icon button. For example an icon button can be round, curved or block.
*/
@property({ type: String })
variant?: FIconButtonVariant = "round";
/**
* @attribute Type of f-icon-button
*/
@property({ type: String })
category?: FIconButtonType = "fill";
/**
* @attribute Size of f-icon-button
*/
@property({ type: String, reflect: true })
size?: FIconButtonSize = "medium";
/**
* @attribute Size of f-icon-button
*/
@property({ type: String })
state?: FIconButtonState = "primary";
/**
* @attribute Counter property enables a counter on the button.
*/
@property({ reflect: true, type: Number })
counter?: string;
/**
* @attribute Loader icon replaces the content of the button .
*/
@property({ reflect: true, type: Boolean })
loading?: boolean = false;
/**
* @attribute The disabled attribute can be set to keep a user from clicking on the button.
*/
@property({ reflect: true, type: Boolean })
disabled?: boolean = false;
/**
* icon element reference
*/
@query("f-icon")
iconElement!: FIcon;
/**
* icon element reference
*/
@query("f-counter")
counterElement?: FCounter;
/**
* compute counter size based on button size
*/
get counterSize() {
if (this.size === "small") {
return this.category === "packed" ? "small" : "medium";
}
if (this.size === "x-small") {
return "small";
}
if (this.size === "large" && this.category === "packed") {
return "medium";
}
if (this.size === "medium" && this.category === "packed") {
return "small";
}
return this.size;
}
/**
* apply inline styles to shadow-dom for custom fill.
*/
applyStyles() {
if (this.fill) {
if (this.loading) {
if (this.category === "fill") {
if (this.variant !== "block") {
return `background-color: ${LightenDarkenColor(
this.fill,
-150
)}; border: 1px solid ${LightenDarkenColor(
this.fill,
-150
)}; color: transparent; fill: ${this.fill}`;
} else {
return `background: transparent; border: none; fill: ${this.fill};`;
}
} else if (this.category === "outline") {
return `background: transparent; border: 1px solid ${this.fill}; fill: ${this.fill};`;
} else {
return `background: transparent; border: none; fill: ${this.fill};`;
}
} else {
if (this.category === "fill") {
if (this.variant !== "block") {
return `background: ${this.fill}; border: 1px solid ${this.fill};`;
} else {
return "background: transparent; border: none;";
}
} else if (this.category === "outline") {
return `background: transparent; border: 1px solid ${this.fill};`;
} else {
return "background: transparent; border: none;";
}
}
} else return "";
}
/**
* validation for all atrributes
*/
validateProperties() {
if (
this.state?.includes("custom") &&
this.fill &&
!validateHTMLColor(this.fill) &&
!validateHTMLColorName(this.fill)
) {
throw new Error("f-icon-button : enter correct color-name or hex-color-code");
}
}
render() {
/**
* creating local fill variable out of state prop.
*/
this.fill = getCustomFillColor(this.state ?? "");
/**
* validate
*/
this.validateProperties();
const hasShimmer = (getComputedStyle(this, "::before") as any)["animation-name"] === "shimmer";
const iconClasses = {
"fill-button-surface":
this.category === "fill" && this.variant !== "block" && !this.fill ? true : false,
"fill-button-surface-light":
this.fill &&
this.category === "fill" &&
this.variant !== "block" &&
getTextContrast(this.fill) === "light-text"
? true
: false,
"fill-button-surface-dark":
this.fill &&
this.category === "fill" &&
this.variant !== "block" &&
getTextContrast(this.fill) === "dark-text"
? true
: false
};
if (hasShimmer) {
this.classList.add("hasShimmer");
}
/**
* create counter if available
*/
const counterClasses = {
"absolute-counter": true,
"outline-counter": this.category === "fill" ? true : false,
[`packed-${this.size}`]: this.category === "packed" ? true : false,
[`size-${this.size}`]: true,
"fill-outline-counter": this.category === "fill" && this.fill ? true : false
};
const counter =
this.counter && !(this.category === "packed" && this.size === "x-small")
? html``
: "";
// classes to apply on inner element
const classes: Record = {
"f-icon-button": true,
hasShimmer,
"custom-loader": this.fill ? true : false,
"custom-hover":
this.fill && this.category === "fill" && this.variant !== "block" ? true : false
};
// merging host classes
this.classList.forEach(cl => {
classes[cl] = true;
if (cl === "f-input-duplicate") {
this.iconInputClass = true;
}
});
return html``;
}
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
/**
* Force update child element
*/
this.iconElement.requestUpdate();
this.counterElement?.requestUpdate();
}
}
declare global {
interface HTMLElementTagNameMap {
"f-icon-button": FIconButton;
}
}