UNPKG

vira

Version:

A simple and highly versatile design system using element-vir.

394 lines (370 loc) 17.4 kB
import { arrayToObject, getObjectTypedKeys } from '@augment-vir/common'; import { ContrastLevelName } from '@electrovir/color/dist/data/contrast/contrast.js'; import { css, html, nothing, unsafeCSS } from 'element-vir'; import { themeDefaultKey } from 'theme-vir/dist/color-theme/color-theme.js'; import { ChevronDown16Icon } from '../icons/index.js'; import { createFocusStyles } from '../styles/focus.js'; import { viraFormCssVars } from '../styles/form-styles.js'; import { standaloneThemeColorNames, viraColorVariantToHostClassKey, viraEmphasisVariants, viraSizeHeights, viraSizeVariants, } from '../styles/form-variants.js'; import { noUserSelect, ViraColorVariant, ViraEmphasis, ViraSize, viraTheme, } from '../styles/index.js'; import { noNativeFormStyles } from '../styles/native-styles.js'; import { viraThemeByKeys, ViraThemeColorName } from '../styles/vira-color-theme-object.js'; import { defineViraElement } from '../util/define-vira-element.js'; import { ViraIcon } from './vira-icon.element.js'; const transparentColor = { value: css `transparent`, }; function buildThemedButtonColors(colorName) { const behindBg = viraThemeByKeys[colorName]['behind-bg']; const foreground = viraThemeByKeys[colorName].foreground; const onSelf = viraThemeByKeys[colorName]['on-self']; return { [ViraEmphasis.Standard]: { idle: { backgroundColor: behindBg[ContrastLevelName.NonBodyText].background, textColor: behindBg[ContrastLevelName.NonBodyText].foreground, borderColor: behindBg[ContrastLevelName.BodyText].background, }, hover: { backgroundColor: behindBg[ContrastLevelName.Header].background, textColor: behindBg[ContrastLevelName.Header].foreground, borderColor: behindBg[ContrastLevelName.BodyText].background, }, active: { backgroundColor: behindBg[ContrastLevelName.BodyText].background, textColor: behindBg[ContrastLevelName.BodyText].foreground, borderColor: behindBg[ContrastLevelName.BodyText].background, }, }, [ViraEmphasis.Subtle]: { idle: { backgroundColor: transparentColor, textColor: foreground[ContrastLevelName.NonBodyText].foreground, borderColor: transparentColor, }, hover: { backgroundColor: onSelf[ContrastLevelName.BodyText].background, textColor: onSelf[ContrastLevelName.BodyText].foreground, borderColor: onSelf[ContrastLevelName.BodyText].foreground, }, active: { backgroundColor: onSelf[ContrastLevelName.NonBodyText].background, textColor: onSelf[ContrastLevelName.NonBodyText].foreground, borderColor: onSelf[ContrastLevelName.NonBodyText].foreground, }, }, }; } const plainButtonColors = { [ViraEmphasis.Standard]: { idle: { backgroundColor: viraTheme.colors[themeDefaultKey].foreground, textColor: viraTheme.colors[themeDefaultKey].background, borderColor: viraTheme.colors[themeDefaultKey].foreground, }, hover: { backgroundColor: viraTheme.colors['vira-grey-behind-bg-non-body'].background, textColor: viraTheme.colors['vira-grey-behind-bg-non-body'].foreground, borderColor: viraTheme.colors[themeDefaultKey].foreground, }, active: { backgroundColor: viraTheme.colors['vira-grey-behind-bg-body'].background, textColor: viraTheme.colors['vira-grey-behind-bg-body'].foreground, borderColor: viraTheme.colors[themeDefaultKey].foreground, }, }, [ViraEmphasis.Subtle]: { idle: { backgroundColor: transparentColor, textColor: viraTheme.colors[themeDefaultKey].foreground, borderColor: transparentColor, }, hover: { backgroundColor: viraTheme.colors['vira-grey-on-self-body'].background, textColor: viraTheme.colors['vira-grey-on-self-body'].foreground, borderColor: viraTheme.colors['vira-grey-on-self-body'].foreground, }, active: { backgroundColor: viraTheme.colors['vira-grey-on-self-non-body'].background, textColor: viraTheme.colors['vira-grey-on-self-non-body'].foreground, borderColor: viraTheme.colors['vira-grey-on-self-non-body'].foreground, }, }, }; const neutralButtonColors = { [ViraEmphasis.Standard]: { idle: { backgroundColor: viraTheme.colors[themeDefaultKey].background, textColor: viraTheme.colors[themeDefaultKey].foreground, borderColor: viraFormCssVars['vira-form-border-color'], }, hover: { backgroundColor: viraTheme.colors['vira-grey-behind-fg-small-body'].background, textColor: viraTheme.colors['vira-grey-behind-fg-small-body'].foreground, borderColor: viraFormCssVars['vira-form-border-color'], }, active: { backgroundColor: viraTheme.colors['vira-grey-behind-fg-body'].background, textColor: viraTheme.colors['vira-grey-behind-fg-body'].foreground, borderColor: viraFormCssVars['vira-form-border-color'], }, }, [ViraEmphasis.Subtle]: buildThemedButtonColors(ViraThemeColorName.grey)[ViraEmphasis.Subtle], }; /** * A custom button with default styling. * * @category Button * @category Elements * @see https://electrovir.github.io/vira/book/elements/vira-button */ export const ViraButton = defineViraElement()({ tagName: 'vira-button', hostClasses: { 'vira-button-with-menu-caret': ({ inputs }) => !!inputs.showMenuCaret, 'vira-button-size-large': ({ inputs }) => inputs.buttonSize === ViraSize.Large, 'vira-button-size-medium': ({ inputs }) => !inputs.buttonSize || inputs.buttonSize === ViraSize.Medium, 'vira-button-size-small': ({ inputs }) => inputs.buttonSize === ViraSize.Small, 'vira-button-emphasis-standard': ({ inputs }) => !inputs.buttonEmphasis || inputs.buttonEmphasis === ViraEmphasis.Standard, 'vira-button-emphasis-subtle': ({ inputs }) => inputs.buttonEmphasis === ViraEmphasis.Subtle, ...arrayToObject(getObjectTypedKeys(viraColorVariantToHostClassKey), (colorVariant) => { const colorKey = viraColorVariantToHostClassKey[colorVariant]; return { key: `vira-button-color-${colorKey}`, value: ({ inputs, }) => { return inputs.color === colorVariant || inputs.color === colorKey; }, }; }, { useRequired: true, }), 'vira-button-color-plain': ({ inputs, }) => !inputs.color || inputs.color === ViraColorVariant.Plain, 'vira-button-color-neutral': ({ inputs, }) => inputs.color === ViraColorVariant.Neutral, ...arrayToObject(standaloneThemeColorNames, (colorName) => { return { key: `vira-button-color-${colorName}`, value: ({ inputs, }) => { return inputs.color === colorName; }, }; }, { useRequired: true, }), 'vira-button-disabled': ({ inputs }) => !!inputs.isDisabled, 'vira-button-icon-only': ({ inputs }) => !inputs.text && !!inputs.icon, }, cssVars: { 'vira-button-text-color': 'transparent', 'vira-button-background-color': 'transparent', 'vira-button-border-color': 'transparent', 'vira-button-hover-text-color': 'transparent', 'vira-button-hover-background-color': 'transparent', 'vira-button-hover-border-color': 'transparent', 'vira-button-active-text-color': 'transparent', 'vira-button-active-background-color': 'transparent', 'vira-button-active-border-color': 'transparent', 'vira-button-disabled-text-color': viraTheme.colors['vira-grey-behind-bg-decoration'].foreground.value, 'vira-button-disabled-background-color': viraTheme.colors['vira-grey-behind-bg-decoration'].background.value, 'vira-button-disabled-border-color': viraTheme.colors['vira-grey-behind-bg-decoration'].background.value, 'vira-button-border-width': '1px', 'vira-button-border-radius': viraFormCssVars['vira-form-radius'].value, }, styles: ({ hostClasses, cssVars }) => { function buildVariantCssRule({ variantSelector, emphasisSelector, colors, }) { return css ` ${variantSelector}${emphasisSelector} { ${cssVars['vira-button-background-color'].name}: ${colors.idle.backgroundColor .value}; ${cssVars['vira-button-text-color'].name}: ${colors.idle.textColor.value}; ${cssVars['vira-button-border-color'].name}: ${colors.idle.borderColor.value}; ${cssVars['vira-button-hover-background-color'].name}: ${colors.hover .backgroundColor.value}; ${cssVars['vira-button-hover-text-color'].name}: ${colors.hover.textColor .value}; ${cssVars['vira-button-hover-border-color'].name}: ${colors.hover.borderColor .value}; ${cssVars['vira-button-active-background-color'].name}: ${colors.active .backgroundColor.value}; ${cssVars['vira-button-active-text-color'].name}: ${colors.active.textColor .value}; ${cssVars['vira-button-active-border-color'].name}: ${colors.active.borderColor .value}; } `; } function generateVariantCss() { const allStyles = viraEmphasisVariants.flatMap((emphasis) => { const emphasisSelector = hostClasses[`vira-button-emphasis-${emphasis}`].selector; const themedStyles = getObjectTypedKeys(viraColorVariantToHostClassKey).map((colorVariant) => { const colorKey = viraColorVariantToHostClassKey[colorVariant]; const colors = buildThemedButtonColors(colorKey)[emphasis]; const variantSelector = hostClasses[`vira-button-color-${colorKey}`].selector; return buildVariantCssRule({ variantSelector, emphasisSelector, colors, }); }); const plainStyle = buildVariantCssRule({ variantSelector: hostClasses['vira-button-color-plain'].selector, emphasisSelector, colors: plainButtonColors[emphasis], }); const neutralStyle = buildVariantCssRule({ variantSelector: hostClasses['vira-button-color-neutral'].selector, emphasisSelector, colors: neutralButtonColors[emphasis], }); const standaloneStyles = standaloneThemeColorNames.map((colorName) => { const colors = buildThemedButtonColors(colorName)[emphasis]; const variantSelector = hostClasses[`vira-button-color-${colorName}`].selector; return buildVariantCssRule({ variantSelector, emphasisSelector, colors, }); }); return [ ...themedStyles, plainStyle, neutralStyle, ...standaloneStyles, ]; }); return unsafeCSS(allStyles.join('\n')); } function generateSizeVariantCss() { const styles = viraSizeVariants.map((sizeVariant) => { return css ` ${hostClasses[`vira-button-size-${sizeVariant}`].selector} { font-size: ${viraFormCssVars[`vira-form-${sizeVariant}-text-size`].value}; button { min-height: ${viraSizeHeights[sizeVariant]}px; padding: 2px ${viraFormCssVars[`vira-form-${sizeVariant}-text-size`].value}; } &${hostClasses['vira-button-icon-only'].selector} { min-width: ${viraSizeHeights[sizeVariant]}px; } } `; }); return unsafeCSS(styles.join('\n')); } return css ` :host { cursor: pointer; display: inline-flex; position: relative; vertical-align: middle; align-items: center; box-sizing: border-box; ${noUserSelect}; ${viraFormCssVars['vira-form-focus-outline-color'].name}: ${viraFormCssVars['vira-form-accent-primary-hover-color'].value} } ${generateSizeVariantCss()} ${generateVariantCss()} button { ${noNativeFormStyles}; flex-shrink: 0; position: relative; cursor: pointer; width: 100%; height: 100%; border-width: ${cssVars['vira-button-border-width'].value}; border-style: solid; border-color: ${cssVars['vira-button-border-color'].value}; box-sizing: border-box; display: inline-flex; justify-content: center; align-items: center; border-radius: ${cssVars['vira-button-border-radius'].value}; background-color: ${cssVars['vira-button-background-color'].value}; color: ${cssVars['vira-button-text-color'].value}; ${createFocusStyles({ elementBorderSize: cssVars['vira-button-border-width'], })} } :host(:hover) button, & button:hover { background-color: ${cssVars['vira-button-hover-background-color'].value}; color: ${cssVars['vira-button-hover-text-color'].value}; border-color: ${cssVars['vira-button-hover-border-color'].value}; } :host(:active) button, & button:active { background-color: ${cssVars['vira-button-active-background-color'].value}; color: ${cssVars['vira-button-active-text-color'].value}; border-color: ${cssVars['vira-button-active-border-color'].value}; } .empty-text { width: 0; } button ${ViraIcon} + .text-template { margin-left: 8px; } ${ViraIcon} { height: 0; display: flex; align-items: center; } .caret-icon { margin-left: 8px; } ${hostClasses['vira-button-with-menu-caret'].selector} { button { padding-right: 6px; } } ${hostClasses['vira-button-disabled'].selector} { cursor: not-allowed; & button { pointer-events: none; color: ${cssVars['vira-button-disabled-text-color'].value}; background-color: ${cssVars['vira-button-disabled-background-color'].value}; border-color: ${cssVars['vira-button-disabled-border-color'].value}; } &${hostClasses['vira-button-emphasis-subtle'].selector} button { color: ${viraTheme.colors['vira-grey-foreground-decoration'].foreground.value}; background-color: transparent; border-color: transparent; } } ${hostClasses['vira-button-icon-only'].selector} { button { padding: 0; } } `; }, render: ({ inputs }) => { const iconTemplate = inputs.icon ? html ` <${ViraIcon.assign({ icon: inputs.icon, })}></${ViraIcon}> ` : nothing; const textTemplate = inputs.text ? html ` <span class="text-template">${inputs.text}</span> ` : html ` <span class="empty-text">&nbsp;</span> `; const caretIconTemplate = inputs.showMenuCaret ? html ` <${ViraIcon.assign({ icon: ChevronDown16Icon, })} class="caret-icon" ></${ViraIcon}> ` : nothing; return html ` <button ?disabled=${inputs.isDisabled}> ${iconTemplate}${textTemplate}${caretIconTemplate} </button> `; }, });