// The "deep overlay": emit the per-component --bs-btn-* variables that Bootstrap // 5.3 bakes at Sass compile time, recomputed from the theme's colors -- so solid // and outline BUTTONS actually recolor under the CSS-variable overlay (a plain // :root{--bs-primary} override does NOT recolor .btn-primary, whose colors are // build-time literals). Mirrors scss/_buttons.scss + mixins/_buttons.scss. const { parseHexColor, toHex, toRgbString, mix, shade, tint, colorContrast, } = require("./bootstrapColor"); // The themeable button variants, in Bootstrap order. const BTN_VARIANTS = ["primary", "secondary", "success", "info", "warning", "danger", "light", "dark"]; // button-variant hover/active shade & tint amounts (scss/_variables.scss). const A = { hoverBgShade: 15, hoverBgTint: 15, hoverBorderShade: 20, hoverBorderTint: 10, activeBgShade: 20, activeBgTint: 20, activeBorderShade: 25, activeBorderTint: 10, }; // One solid .btn- rule. `name` selects the light/dark force-overrides: // .btn-light -> always SHADE; .btn-dark -> always TINT; else by text color. function solidButtonRule(name, color) { const c = parseHexColor(color); if (!c) { return ""; } const textColor = colorContrast(c); // "#ffffff" | "#000000" const shadeMode = name === "light" ? true : name === "dark" ? false : textColor === "#ffffff"; const hoverBg = shadeMode ? shade(c, A.hoverBgShade) : tint(c, A.hoverBgTint); const hoverBorder = shadeMode ? shade(c, A.hoverBorderShade) : tint(c, A.hoverBorderTint); const activeBg = shadeMode ? shade(c, A.activeBgShade) : tint(c, A.activeBgTint); const activeBorder = shadeMode ? shade(c, A.activeBorderShade) : tint(c, A.activeBorderTint); const focusRgb = toRgbString(mix(parseHexColor(textColor), c, 0.15)); const bg = toHex(c); return `.btn-${name}{` + `--bs-btn-color:${textColor};--bs-btn-bg:${bg};--bs-btn-border-color:${bg};` + `--bs-btn-hover-color:${colorContrast(hoverBg)};--bs-btn-hover-bg:${toHex(hoverBg)};--bs-btn-hover-border-color:${toHex(hoverBorder)};` + `--bs-btn-focus-shadow-rgb:${focusRgb};` + `--bs-btn-active-color:${colorContrast(activeBg)};--bs-btn-active-bg:${toHex(activeBg)};--bs-btn-active-border-color:${toHex(activeBorder)};` + `--bs-btn-disabled-color:${textColor};--bs-btn-disabled-bg:${bg};--bs-btn-disabled-border-color:${bg};` + `}`; } // One .btn-outline- rule (button-outline-variant): transparent fill that // inverts to the variant color on hover/active. function outlineButtonRule(name, color) { const c = parseHexColor(color); if (!c) { return ""; } const hex = toHex(c); const onColor = colorContrast(c); return `.btn-outline-${name}{` + `--bs-btn-color:${hex};--bs-btn-border-color:${hex};` + `--bs-btn-hover-color:${onColor};--bs-btn-hover-bg:${hex};--bs-btn-hover-border-color:${hex};` + `--bs-btn-focus-shadow-rgb:${toRgbString(c)};` + `--bs-btn-active-color:${onColor};--bs-btn-active-bg:${hex};--bs-btn-active-border-color:${hex};` + `--bs-btn-disabled-color:${hex};--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:${hex};` + `}`; } // Emit the full deep-overlay CSS for the given color map { primary, secondary, // ... }. Variants whose value is not a hex color are skipped (the :root overlay // still sets --bs- for them). function emitButtonRules(colors) { if (!colors || typeof colors !== "object") { return ""; } const out = []; for (const name of BTN_VARIANTS) { const color = colors[name]; if (!color || !parseHexColor(color)) { continue; } out.push(solidButtonRule(name, color)); out.push(outlineButtonRule(name, color)); } return out.filter(Boolean).join("\n"); } module.exports = { emitButtonRules, solidButtonRule, outlineButtonRule, BTN_VARIANTS };