diff --git a/components/button/button-styles.js b/components/button/button-styles.js index 331a246fb37..289f3cffd6c 100644 --- a/components/button/button-styles.js +++ b/components/button/button-styles.js @@ -1,33 +1,145 @@ import '../colors/colors.js'; -import { css } from 'lit'; +import { css, unsafeCSS } from 'lit'; +import { _isValidCssSelector } from '../../helpers/internal/css.js'; import { getFocusRingStyles } from '../../helpers/focus.js'; -export const buttonStyles = css` - button { - border-end-end-radius: var(--d2l-button-end-end-radius, 0.3rem); - border-end-start-radius: var(--d2l-button-end-start-radius, 0.3rem); - border-start-end-radius: var(--d2l-button-start-end-radius, 0.3rem); - border-start-start-radius: var(--d2l-button-start-start-radius, 0.3rem); - border-style: none; - box-sizing: border-box; - cursor: pointer; - display: inline-block; - margin: 0; - min-height: calc(2rem + 2px); - outline: none; - text-align: center; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - vertical-align: middle; - white-space: nowrap; - width: auto; - } - ${getFocusRingStyles('button')} - @media (prefers-contrast: more) { - button { - border: 2px solid transparent; +function _generateButtonBaseStyles(selector) { + if (!_isValidCssSelector(selector)) return unsafeCSS(''); + selector = unsafeCSS(selector.trim()); + + return css` + ${selector} { + border-end-end-radius: var(--d2l-button-end-end-radius, 0.3rem); + border-end-start-radius: var(--d2l-button-end-start-radius, 0.3rem); + border-start-end-radius: var(--d2l-button-start-end-radius, 0.3rem); + border-start-start-radius: var(--d2l-button-start-start-radius, 0.3rem); + border-style: none; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + margin: 0; + min-height: calc(2rem + 2px); + outline: none; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + vertical-align: middle; + white-space: nowrap; + width: auto; + } + `; +} + +function _generateMozillaButtonBorderStyles(selector) { + return css` + /* Firefox includes a hidden border which messes up button dimensions */ + ${selector}::-moz-focus-inner { + border: 0; + } + `; +} + +function _generateButtonDisabledStyles(selector, isForBsi = false) { + const activeDisabledSelector = isForBsi ? unsafeCSS(`${selector}[active][disabled]`) : unsafeCSS(`:host([active]) ${selector}[disabled]`); + + return css` + ${selector}, + ${selector}[disabled]:hover, + ${selector}[disabled]:focus, + ${activeDisabledSelector} { + background-color: var(--d2l-theme-background-color-interactive-secondary-default); + color: var(--d2l-theme-text-color-static-standard); + } + `; +} + +function _generateButtonEnabledStyles(selector, isForBsi = false) { + const activeSelector = isForBsi ? unsafeCSS(`${selector}[active]`) : unsafeCSS(`:host([active]) ${selector}`); + const additionalBSISelector = isForBsi ? unsafeCSS(`${selector}.d2l-button-hover, ${selector}.d2l-button-focus,`) : unsafeCSS(''); + + return css` + ${additionalBSISelector} + ${selector}:hover, + ${selector}:focus, + ${activeSelector} { + background-color: var(--d2l-theme-background-color-interactive-secondary-hover); + } + `; +} + +function _generatePrimaryButtonDisabledStyles(selector, isForBsi = false) { + const finalSelector = isForBsi ? `${selector}[primary], + ${selector}[primary][disabled]:hover, + ${selector}[primary][disabled]:focus, + ${selector}[primary][active][disabled]` : `:host([primary]) ${selector}, + :host([primary]) ${selector}[disabled]:hover, + :host([primary]) ${selector}[disabled]:focus, + :host([primary][active]) ${selector}[disabled]`; + + return css` + ${unsafeCSS(finalSelector)} { + background-color: var(--d2l-theme-background-color-interactive-primary-default); + color: var(--d2l-theme-text-color-static-inverted); + } + `; +} + +function _generatePrimaryButtonEnabledStyles(selector, isForBsi = false) { + const finalSelector = isForBsi ? `${selector}[primary]:hover, + ${selector}[primary].d2l-button-hover, + ${selector}[primary]:focus, + ${selector}[primary].d2l-button-focus, + ${selector}[primary][active]` : `:host([primary]) ${selector}:hover, + :host([primary]) ${selector}:focus, + :host([primary][active]) ${selector}`; + + return css` + ${unsafeCSS(finalSelector)} { + background-color: var(--d2l-theme-background-color-interactive-primary-hover); + } + `; +} + +/** + * A private helper method that should not be used by general consumers + */ +export function _generateBSIButtonFocusStyles(selector) { + const getSelector = (focusPseudoClass) => ` + ${selector}:${focusPseudoClass}, + ${selector}.d2l-button-focus, + ${selector}[primary]:${focusPseudoClass}, + ${selector}[primary].d2l-button-focus`; + + return getFocusRingStyles(getSelector); +} + +/** + * A private helper method that should not be used by general consumers + */ +export function _generateButtonStyles(selector) { + if (!_isValidCssSelector(selector)) return unsafeCSS(''); + const safeSelector = unsafeCSS(selector.trim()); + + return css` + ${safeSelector} { + font-family: inherit; + padding-block-end: 0; + padding-block-start: 0; + padding-inline-end: var(--d2l-button-padding-inline-end, 1.5rem); + padding-inline-start: var(--d2l-button-padding-inline-start, 1.5rem); } - } + + ${_generateMozillaButtonBorderStyles(safeSelector)} + ${_generateButtonDisabledStyles(safeSelector)} + ${_generateButtonEnabledStyles(safeSelector)} + ${_generatePrimaryButtonDisabledStyles(safeSelector)} + ${_generatePrimaryButtonEnabledStyles(safeSelector)} + `; +} + +export const buttonStyles = css` + ${_generateButtonBaseStyles('button')} + ${getFocusRingStyles('button', { preferContrastMediaQueryExtraStyles: css`border: 2px solid transparent;` })} `; diff --git a/components/button/button.js b/components/button/button.js index 10442c8033b..85b3f3bec19 100644 --- a/components/button/button.js +++ b/components/button/button.js @@ -1,12 +1,14 @@ import '../colors/colors.js'; import '../tooltip/tooltip.js'; +import { _generateButtonStyles, buttonStyles as baseButtonStyles } from './button-styles.js'; import { css, html, LitElement } from 'lit'; import { ButtonMixin } from './button-mixin.js'; -import { buttonStyles } from './button-styles.js'; import { getUniqueId } from '../../helpers/uniqueId.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { labelStyles } from '../typography/styles.js'; +const buttonStyles = _generateButtonStyles('button'); + /** * A button component that can be used just like the native button element. * @slot - Default content placed inside of the button @@ -27,7 +29,7 @@ class Button extends ButtonMixin(LitElement) { primary: { type: Boolean, reflect: true } }; - static styles = [labelStyles, buttonStyles, + static styles = [labelStyles, baseButtonStyles, buttonStyles, css` :host { display: inline-block; @@ -35,35 +37,9 @@ class Button extends ButtonMixin(LitElement) { :host([hidden]) { display: none; } - button { - font-family: inherit; - padding-block-end: 0; - padding-block-start: 0; - padding-inline-end: var(--d2l-button-padding-inline-end, 1.5rem); - padding-inline-start: var(--d2l-button-padding-inline-start, 1.5rem); width: 100%; } - - /* Firefox includes a hidden border which messes up button dimensions */ - button::-moz-focus-inner { - border: 0; - } - - button, - button[disabled]:hover, - button[disabled]:focus, - :host([active]) button[disabled] { - background-color: var(--d2l-theme-background-color-interactive-secondary-default); - color: var(--d2l-theme-text-color-static-standard); - } - - button:hover, - button:focus, - :host([active]) button { - background-color: var(--d2l-theme-background-color-interactive-secondary-hover); - } - :host([disabled]) button { cursor: default; position: relative; @@ -76,19 +52,6 @@ class Button extends ButtonMixin(LitElement) { opacity: var(--d2l-theme-opacity-disabled-control); position: absolute; } - - :host([primary]) button, - :host([primary]) button[disabled]:hover, - :host([primary]) button[disabled]:focus, - :host([primary][active]) button[disabled] { - background-color: var(--d2l-theme-background-color-interactive-primary-default); - color: var(--d2l-theme-text-color-static-inverted); - } - :host([primary]) button:hover, - :host([primary]) button:focus, - :host([primary][active]) button { - background-color: var(--d2l-theme-background-color-interactive-primary-hover); - } ` ];