From 6784f80c717c8c5484671223bed67cdecaf991e3 Mon Sep 17 00:00:00 2001 From: Lyza Danger Gardner Date: Tue, 13 Apr 2021 12:42:30 -0400 Subject: [PATCH 1/2] Add SASS styling for button components Integrate styling for button components and extend existing SASS structure. --- styles/_index.scss | 1 + styles/components/buttons/_base.scss | 109 ++++++++++++++++++ styles/components/buttons/_config.scss | 149 +++++++++++++++++++++++++ styles/components/buttons/_index.scss | 2 + styles/components/buttons/_mixins.scss | 149 +++++++++++++++++++++++++ styles/components/buttons/_styles.scss | 16 +++ styles/variables.scss | 2 - styles/variables/_colors.scss | 39 +++++++ styles/variables/_index.scss | 7 ++ 9 files changed, 472 insertions(+), 2 deletions(-) create mode 100644 styles/components/buttons/_base.scss create mode 100644 styles/components/buttons/_config.scss create mode 100644 styles/components/buttons/_index.scss create mode 100644 styles/components/buttons/_mixins.scss create mode 100644 styles/components/buttons/_styles.scss delete mode 100644 styles/variables.scss create mode 100644 styles/variables/_colors.scss create mode 100644 styles/variables/_index.scss diff --git a/styles/_index.scss b/styles/_index.scss index 971bf21a..e8584419 100644 --- a/styles/_index.scss +++ b/styles/_index.scss @@ -4,3 +4,4 @@ // @use '@hypothesis/frontend-shared/styles' @use './components/SvgIcon'; +@use './components/buttons/styles'; diff --git a/styles/components/buttons/_base.scss b/styles/components/buttons/_base.scss new file mode 100644 index 00000000..cdddce87 --- /dev/null +++ b/styles/components/buttons/_base.scss @@ -0,0 +1,109 @@ +@use "sass:map"; + +// Set colors for a button +@mixin _colors($colormap) { + color: map.get($colormap, 'foreground'); + background-color: map.get($colormap, 'background'); + + &:disabled { + color: map.get($colormap, 'disabled-foreground'); + } +} + +// Set hover colors and transition for a button +@mixin _hover-state($colormap) { + &:hover:not([disabled]) { + color: map.get($colormap, 'hover-foreground'); + background-color: map.get($colormap, 'hover-background'); + } + transition: color 0.2s ease-out, background-color 0.2s ease-out, + opacity 0.2s ease-out; +} + +// Set active state colors for a button +@mixin _active-state($colormap) { + &[aria-expanded='true'], + &[aria-pressed='true'] { + color: map.get($colormap, 'active-foreground'); + @if map.get($colormap, 'active-background') { + background-color: map.get($colormap, 'active-background'); + } + + &:hover:not([disabled]) { + color: map.get($colormap, 'hover-foreground'); + } + + &:focus:not([disabled]) { + color: map.get($colormap, 'active-foreground'); + } + } +} + +// Variant mixin: may be be used by variants (BEM modifier classes) +@mixin button--variant($options) { + @include _colors(map.get($options, 'colormap')); + @if map.get($options, 'withStates') { + @include _active-state(map.get($options, 'colormap')); + @include _hover-state(map.get($options, 'colormap')); + } + @content; +} + +// Base mixin for buttons. +@mixin button($options) { + @include _colors(map.get($options, 'colormap')); + + @if map.get($options, 'withStates') { + @include _active-state(map.get($options, 'colormap')); + @include _hover-state(map.get($options, 'colormap')); + } + + border-radius: map.get($options, 'border-radius'); + border: none; + padding: 0.5em; + + &--small { + padding: 0.25em; + } + + &--large { + padding: 0.75em; + } + + font-size: 1em; + font-weight: 700; + white-space: nowrap; // Keep multi-word button labels from wrapping + + @if map.get($options, 'inline') { + display: inline; + } @else { + display: flex; + justify-content: center; + align-items: center; + } + + @if map.get($options, 'withLayout') { + &--icon-left svg { + margin-right: map.get($options, 'margin'); + } + + &--icon-right svg { + margin-left: map.get($options, 'margin'); + } + // When a button has "layout", that indicates it has some textual content: + // Size text to the contextual 1em, and adjust the icon to look balanced. + // H frontend app buttons tend to apply an icon:text ratio of ~1.25:1 + svg { + width: 1.25em; + height: 1.25em; + } + } @else { + // In the case where an icon is the only content in a + ); +} + +/** + * An icon-only button + * + * @param {IconButtonProps} props + */ +export function IconButton(props) { + const { className = 'IconButton', ...restProps } = props; + const { icon } = props; + return ( + + + + ); +} + +/** + * A labeled button, with or without an icon + * + * @param {ButtonProps} props + */ +export function LabeledButton(props) { + const { icon, iconPosition = 'left' } = props; + const { children, className = 'LabeledButton', ...restProps } = props; + return ( + + {icon && iconPosition === 'left' && } + {children} + {icon && iconPosition === 'right' && } + + ); +} + +/** + * A button styled to appear as an HTML link () + * + * @param {ButtonProps} props + */ +export function LinkButton(props) { + const { children } = props; + return ( + + {children} + + ); +} diff --git a/src/components/test/buttons-test.js b/src/components/test/buttons-test.js new file mode 100644 index 00000000..1b7afe2b --- /dev/null +++ b/src/components/test/buttons-test.js @@ -0,0 +1,257 @@ +import { mount } from 'enzyme'; + +import { IconButton, LabeledButton, LinkButton } from '../buttons.js'; +import { $imports } from '../buttons.js'; + +import { checkAccessibility } from '../../../test/util/accessibility'; +import mockImportedComponents from '../../../test/util/mock-imported-components'; + +// Add common tests for a button component for stuff provided by `ButtonBase` +function addCommonTests({ componentName, createComponentFn, withIcon = true }) { + describe(`${componentName} common support`, () => { + if (withIcon) { + it('renders the indicated icon', () => { + const wrapper = createComponentFn({ icon: 'fakeIcon' }); + const button = wrapper.find('button'); + const icon = wrapper.find('SvgIcon'); + assert.equal(icon.prop('name'), 'fakeIcon'); + // Icon is positioned "left" even if it is the only element in the