From 245e70d4a89c55f392693ac7284f1befd0b38c50 Mon Sep 17 00:00:00 2001 From: ludwiczakpawel Date: Tue, 30 Nov 2021 20:40:32 +0100 Subject: [PATCH] Crayons: Buttons vs CTAs (#15311) * init * buttons * buttons, ctas, icons * ctas fix * links vs ctas * alias * fixes * add tests for Buttons component * add tooltip to button story controls * add link and CTA tests * add prop types * docs * designs * docs * focus visible moved to global place * Updated some component snapshot tests. * Mocked react-inlinesvg module because of https://github.com/gilbarbara/react-inlinesvg/issues/145\#issuecomment-623453339 * tests * Copy updates Co-authored-by: Suzanne Aitchison * missed spots * Apply suggestions from code review Co-authored-by: Suzanne Aitchison * danger accent colors * node * colors * adding code comment * better prop name: style -> variant * button new Co-authored-by: Suzanne Aitchison Co-authored-by: Nick Taylor --- .../stylesheets/components/buttons.scss | 103 ++++++++++++++++ app/assets/stylesheets/components/ctas.scss | 40 +++++++ app/assets/stylesheets/components/links.scss | 93 +++++++++++++++ app/assets/stylesheets/config/_colors.scss | 40 ++++++- app/assets/stylesheets/config/_variables.scss | 7 ++ app/assets/stylesheets/crayons.scss | 2 + app/assets/stylesheets/themes/dark.scss | 24 +++- app/javascript/.storybook/main.js | 5 + app/javascript/.storybook/preview.js | 1 + app/javascript/.storybook/storybook.scss | 15 +++ .../initializeActionsPanelToggle.js | 8 +- app/javascript/crayons/Buttons/Button.jsx | 84 +++++++++++++ .../crayons/Buttons/__stories__/Buttons.mdx | 63 ++++++++++ .../Buttons/__stories__/Buttons.stories.jsx | 76 ++++++++++++ .../Buttons/__tests__/Buttons.test.jsx | 110 ++++++++++++++++++ .../__snapshots__/Buttons.test.jsx.snap | 19 +++ app/javascript/crayons/Buttons/index.js | 1 + app/javascript/crayons/CTAs/CTA.jsx | 22 ++++ .../crayons/CTAs/__stories__/CTAs.mdx | 40 +++++++ .../crayons/CTAs/__stories__/CTAs.stories.jsx | 53 +++++++++ .../crayons/CTAs/__tests__/CTA.test.jsx | 30 +++++ .../__tests__/__snapshots__/CTA.test.jsx.snap | 5 + app/javascript/crayons/CTAs/index.js | 1 + app/javascript/crayons/Icons/Icon.jsx | 28 +++++ .../crayons/Icons/__stories__/Icons.mdx | 43 +++++++ .../Icons/__stories__/Icons.stories.jsx | 33 ++++++ app/javascript/crayons/Icons/index.js | 1 + app/javascript/crayons/Links/Link.jsx | 53 +++++++++ .../crayons/Links/__stories__/Links.mdx | 48 ++++++++ .../Links/__stories__/Links.stories.jsx | 71 +++++++++++ .../crayons/Links/__tests__/Link.test.jsx | 69 +++++++++++ .../__snapshots__/Link.test.jsx.snap | 15 +++ app/javascript/crayons/Links/index.js | 1 + app/javascript/crayons/index.js | 4 + app/javascript/packs/Search.jsx | 1 - app/javascript/packs/base.jsx | 1 + jest.config.js | 1 + package.json | 2 + yarn.lock | 19 +++ 39 files changed, 1221 insertions(+), 11 deletions(-) create mode 100644 app/assets/stylesheets/components/ctas.scss create mode 100644 app/assets/stylesheets/components/links.scss create mode 100644 app/javascript/crayons/Buttons/Button.jsx create mode 100644 app/javascript/crayons/Buttons/__stories__/Buttons.mdx create mode 100644 app/javascript/crayons/Buttons/__stories__/Buttons.stories.jsx create mode 100644 app/javascript/crayons/Buttons/__tests__/Buttons.test.jsx create mode 100644 app/javascript/crayons/Buttons/__tests__/__snapshots__/Buttons.test.jsx.snap create mode 100644 app/javascript/crayons/Buttons/index.js create mode 100644 app/javascript/crayons/CTAs/CTA.jsx create mode 100644 app/javascript/crayons/CTAs/__stories__/CTAs.mdx create mode 100644 app/javascript/crayons/CTAs/__stories__/CTAs.stories.jsx create mode 100644 app/javascript/crayons/CTAs/__tests__/CTA.test.jsx create mode 100644 app/javascript/crayons/CTAs/__tests__/__snapshots__/CTA.test.jsx.snap create mode 100644 app/javascript/crayons/CTAs/index.js create mode 100644 app/javascript/crayons/Icons/Icon.jsx create mode 100644 app/javascript/crayons/Icons/__stories__/Icons.mdx create mode 100644 app/javascript/crayons/Icons/__stories__/Icons.stories.jsx create mode 100644 app/javascript/crayons/Icons/index.js create mode 100644 app/javascript/crayons/Links/Link.jsx create mode 100644 app/javascript/crayons/Links/__stories__/Links.mdx create mode 100644 app/javascript/crayons/Links/__stories__/Links.stories.jsx create mode 100644 app/javascript/crayons/Links/__tests__/Link.test.jsx create mode 100644 app/javascript/crayons/Links/__tests__/__snapshots__/Link.test.jsx.snap create mode 100644 app/javascript/crayons/Links/index.js diff --git a/app/assets/stylesheets/components/buttons.scss b/app/assets/stylesheets/components/buttons.scss index 2233c83c5..9cda36929 100644 --- a/app/assets/stylesheets/components/buttons.scss +++ b/app/assets/stylesheets/components/buttons.scss @@ -412,3 +412,106 @@ label[class*='crayons-btn']:focus-within // label for input[type="file"] made to } } } + +//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +// .c-btn ////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// + +.c-btn { + position: relative; + display: inline-block; + padding: var(--su-2) var(--su-4); + border-radius: var(--radius); + font: inherit; + text-align: center; + cursor: pointer; + transition: all var(--transition-props); + border: none; + overflow-wrap: normal; + outline: 0; + background-color: var(--bg); + color: var(--color); + + &:hover:enabled { + background-color: var(--bg-hover); + color: var(--color-hover); + z-index: var(--z-elevate); + } + + // Both focus declarations below are the same but we unfortunately can't + // combine them because Safari doesn't handle it properly. + // • First declaration is for all browsers that support :focus-visible + // which is basically almost all of them. + // • Second declaration is for browsers that do not support :focus-visible + // which basically is only Safari right now :shrug: + &:focus-visible { + background-color: var(--bg-hover); + color: var(--color-hover); + z-index: var(--z-elevate); + box-shadow: var(--focus-ring); + } + + .js-focus-visible &.focus-visible:focus { + background-color: var(--bg-hover); + color: var(--color-hover); + z-index: var(--z-elevate); + box-shadow: var(--focus-ring); + } + + &[disabled] { + opacity: 0.6; + cursor: not-allowed; + } + + // This makes sure icons inside buttons don't cause problems with click handlers + // See: https://css-tricks.com/slightly-careful-sub-elements-clickable-things/ + & > * { + pointer-events: none; + + input { + pointer-events: revert; + } + } +} + +.c-btn { + --bg: var(--btn-bg); + --bg-hover: var(--btn-bg-hover); + --color: var(--btn-color); + --color-hover: var(--btn-color-hover); + + &.c-btn--destructive { + --bg: var(--btn-destructive-bg); + --bg-hover: var(--btn-destructive-bg-hover); + --color: var(--btn-destructive-color); + --color-hover: var(--btn-destructive-color-hover); + } +} + +.c-btn--primary { + --bg: var(--btn-primary-bg); + --bg-hover: var(--btn-primary-bg-hover); + --color: var(--btn-primary-color); + --color-hover: var(--btn-primary-color-hover); + + font-weight: var(--fw-medium); + + &.c-btn--destructive { + --bg: var(--btn-primary-destructive-bg); + --bg-hover: var(--btn-primary-destructive-bg-hover); + --color: var(--btn-primary-destructive-color); + --color-hover: var(--btn-primary-destructive-color-hover); + } +} + +.c-btn--icon-alone { + padding: var(--su-2); +} + +.c-btn--icon-left { + .c-btn__icon { + margin-right: var(--su-2); + margin-left: calc(var(--su-1) * -1); + } +} diff --git a/app/assets/stylesheets/components/ctas.scss b/app/assets/stylesheets/components/ctas.scss new file mode 100644 index 000000000..0b11d4d97 --- /dev/null +++ b/app/assets/stylesheets/components/ctas.scss @@ -0,0 +1,40 @@ +//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +// .c-cta ////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// + +.c-link { + &.c-cta { + --border-width: 1px; + padding: calc(var(--su-2) - var(--border-width)) + calc(var(--su-4) - var(--border-width)); + border: var(--border-width) solid var(--border); + text-decoration: none; + background-color: var(--bg); + color: var(--color); + text-align: center; + display: inline-block; + } + + &.c-cta { + --bg: transparent; + --bg-hover: var(--base-20); + --color: var(--base-80); + --color-hover: var(--base-100); + --border: var(--base-60); + --border-hover: var(--base-80); + } + + &.c-cta--branded { + --bg: transparent; + --bg-hover: var(--accent-brand-a10); + --color: var(--accent-brand); + --color-hover: var(--accent-brand-darker); + --border: var(--accent-brand); + --border-hover: var(--accent-brand-darker); + + font-weight: var(--fw-medium); + } + +} diff --git a/app/assets/stylesheets/components/links.scss b/app/assets/stylesheets/components/links.scss new file mode 100644 index 000000000..d54984cf9 --- /dev/null +++ b/app/assets/stylesheets/components/links.scss @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////// +// .c-link ///////////////////////////////////////////////// +//////////////////////////////////////////////////////////// + +.c-link { + position: relative; + display: inline-block; + border-radius: var(--radius); + font: inherit; + cursor: pointer; + transition: all var(--transition-props); + outline: 0; + text-decoration: none; + color: var(--color); + + &--block { + display: flex; + padding: var(--su-2) var(--su-4); + overflow-wrap: normal; + background-color: var(--bg); + + &:hover { + background-color: var(--bg-hover); + } + } + + &:hover { + color: var(--color-hover); + z-index: var(--z-elevate); + text-decoration: underline; + } + + // Both focus declarations below are the same but we unfortunately can't + // combine them because Safari doesn't recognize it properly. + // • First declaration is for all browsers that support :focus-visible + // which is basically almost all of them + // • Second declaration is for browsers that do not support :focus-visible + // which basically is only Safari right now. + &:focus-visible { + color: var(--color-hover); + z-index: var(--z-elevate); + box-shadow: var(--focus-ring); + text-decoration: underline; + } + + .js-focus-visible &.focus-visible:focus { + color: var(--color-hover); + z-index: var(--z-elevate); + box-shadow: var(--focus-ring); + text-decoration: underline; + } + + &--block { + &:focus-visible { + background-color: var(--bg-hover); + } + + .js-focus-visible &.focus-visible:focus { + background-color: var(--bg-hover); + } + } +} + +.c-link { + --bg: transparent; + --bg-hover: var(--base-20); + --color: var(--base-80); + --color-hover: var(--accent-brand-darker); +} + +.c-link--branded { + --bg: transparent; + --bg-hover: var(--accent-brand-a10); + --color: var(--accent-brand); + --color-hover: var(--accent-brand-darker); +} + +.c-link--icon-alone { + padding: var(--su-2); + // If we ever want to display only with icon it should probably + // no longer be `block` (or `flex`) element, i.e. "Notifications" icon in + // the header. Hence reverting to `inline-block`. + display: inline-block; +} + +.c-link--icon-left { + .c-link__icon { + margin-right: var(--su-2); + margin-left: calc(var(--su-1) * -1); + } +} diff --git a/app/assets/stylesheets/config/_colors.scss b/app/assets/stylesheets/config/_colors.scss index d0f69c01d..e3ea934a7 100644 --- a/app/assets/stylesheets/config/_colors.scss +++ b/app/assets/stylesheets/config/_colors.scss @@ -69,9 +69,9 @@ --accent-warning-a10: #{rgba(#ffcf4c, 0.1)}; // Danger - --accent-danger: #dc1818; - --accent-danger-darker: #c20a0a; - --accent-danger-lighter: #ec5050; + --accent-danger: #d31616; + --accent-danger-darker: #b90909; + --accent-danger-lighter: #ec4646; --accent-danger-a10: #{rgba(#dc1818, 0.1)}; //////////////////////////////////////////////////////////////////////////////////// @@ -241,3 +241,37 @@ --reaction-comment-color: var(--accent-warning); --reaction-comment-bg: var(--accent-warning-a10); } + +:root { + // Separating some variables here as these are the results + // of broader Crayons refactoring (see #15311) and I didn't + // want to mix everything together. + + //////////////////////////////////////////////// + // Buttons ///////////////////////////////////// + //////////////////////////////////////////////// + + // Buttons: Default + --btn-bg: transparent; + --btn-bg-hover: var(--base-20); + --btn-color: var(--base-80); + --btn-color-hover: var(--base-100); + + // Buttons: Default Destructive + --btn-destructive-bg: transparent; + --btn-destructive-bg-hover: var(--accent-danger-a10); + --btn-destructive-color: var(--accent-danger); + --btn-destructive-color-hover: var(--accent-danger-darker); + + // Buttons: Primary + --btn-primary-bg: var(--accent-brand); + --btn-primary-bg-hover: var(--accent-brand-darker); + --btn-primary-color: var(--base-inverted); + --btn-primary-color-hover: var(--base-inverted); + + // Buttons: Primary Destructive + --btn-primary-destructive-bg: var(--accent-danger); + --btn-primary-destructive-bg-hover: var(--accent-danger-darker); + --btn-primary-destructive-color: var(--base-inverted); + --btn-primary-destructive-color-hover: var(--base-inverted); +} diff --git a/app/assets/stylesheets/config/_variables.scss b/app/assets/stylesheets/config/_variables.scss index 48bdab50a..1a6c4d997 100644 --- a/app/assets/stylesheets/config/_variables.scss +++ b/app/assets/stylesheets/config/_variables.scss @@ -52,6 +52,13 @@ --transition-time: 100ms; --transition-props: var(--transition-func) var(--transition-time); + //////////////////////////////////////////////////////////////////////////////////// + // Focus ring ////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////// + + --focus-ring: 0 0 0 2px var(--base-inverted), + 0 0 0 4px var(--button-primary-bg-hover); // TODO: replace with appropriate variable from _colors.scss + //////////////////////////////////////////////////////////////////////////////////// // Typography. ///////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////// diff --git a/app/assets/stylesheets/crayons.scss b/app/assets/stylesheets/crayons.scss index aa7759916..3c37f2f15 100644 --- a/app/assets/stylesheets/crayons.scss +++ b/app/assets/stylesheets/crayons.scss @@ -14,6 +14,7 @@ @import 'components/buttons'; @import 'components/cards'; @import 'components/comments'; +@import 'components/ctas'; @import 'components/dropdowns'; @import 'components/editor-toolbar'; @import 'components/forms'; @@ -21,6 +22,7 @@ @import 'components/header'; @import 'components/headers'; @import 'components/indicators'; +@import 'components/links'; @import 'components/loaders'; @import 'components/media-cards'; @import 'components/modals'; diff --git a/app/assets/stylesheets/themes/dark.scss b/app/assets/stylesheets/themes/dark.scss index 8e4adc8fb..02879a063 100644 --- a/app/assets/stylesheets/themes/dark.scss +++ b/app/assets/stylesheets/themes/dark.scss @@ -65,10 +65,10 @@ --accent-warning-a10: #{rgba(#ffcf4c, 0.1)}; // Danger - --accent-danger: #dc1818; - --accent-danger-darker: #c20a0a; - --accent-danger-lighter: #ec5050; - --accent-danger-a10: #{rgba(#dc1818, 0.1)}; + --accent-danger: #ed3434; + --accent-danger-darker: #e40a0a; + --accent-danger-lighter: #f15555; + --accent-danger-a10: #{rgba(#ed3434, 0.1)}; // Main colors --body-bg: var(--base-0); @@ -230,3 +230,19 @@ --reaction-save-color: var(--accent-brand); --reaction-save-bg: var(--accent-brand-a10); } + +:root { + // Separating some variables here as these are the results + // of broader Crayons refactoring (see #15311) and I didn't + // want to mix everything together. + + //////////////////////////////////////////////// + // Buttons ///////////////////////////////////// + //////////////////////////////////////////////// + + // Buttons: Primary + --btn-primary-bg-hover: var(--accent-brand-lighter); + + // Buttons: Primary Destructive + --btn-primary-destructive-bg-hover: var(--accent-danger-lighter); +} diff --git a/app/javascript/.storybook/main.js b/app/javascript/.storybook/main.js index 732e940c0..ffe944fcc 100644 --- a/app/javascript/.storybook/main.js +++ b/app/javascript/.storybook/main.js @@ -11,6 +11,7 @@ module.exports = { // https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#correct-globs-in-mainjs stories: ['../**/__stories__/*.stories.@(mdx|jsx)'], addons: [ + '@storybook/addon-controls', '@storybook/addon-knobs', '@storybook/addon-actions', '@storybook/addon-links', @@ -81,4 +82,8 @@ module.exports = { return config; }, + babel: async (options) => ({ + ...options, + plugins: [...options.plugins, 'inline-react-svg'], + }), }; diff --git a/app/javascript/.storybook/preview.js b/app/javascript/.storybook/preview.js index 07a392fc9..3affda257 100644 --- a/app/javascript/.storybook/preview.js +++ b/app/javascript/.storybook/preview.js @@ -92,6 +92,7 @@ Foo.parameters = { }; export const parameters = { + controls: { expanded: true }, options: { storySort: { order: [ diff --git a/app/javascript/.storybook/storybook.scss b/app/javascript/.storybook/storybook.scss index 11f533715..380549805 100644 --- a/app/javascript/.storybook/storybook.scss +++ b/app/javascript/.storybook/storybook.scss @@ -6,4 +6,19 @@ body { #root { padding: 2rem; } + + .sbdocs-ul, + .sbdocs-ol { + list-style: revert; + margin: revert; + padding: revert; + } + + code { + all: revert; + } + + .docblock-source { + margin: revert; + } } diff --git a/app/javascript/actionsPanel/initializeActionsPanelToggle.js b/app/javascript/actionsPanel/initializeActionsPanelToggle.js index 00d79b1c1..de54be59a 100644 --- a/app/javascript/actionsPanel/initializeActionsPanelToggle.js +++ b/app/javascript/actionsPanel/initializeActionsPanelToggle.js @@ -17,7 +17,9 @@ export function initializeActionsPanel(user, path) { `; function toggleModActionsMenu() { - document.getElementById('mod-actions-menu-btn-area').classList.remove('hidden'); + document + .getElementById('mod-actions-menu-btn-area') + .classList.remove('hidden'); document .getElementsByClassName('mod-actions-menu')[0] .classList.toggle('showing'); @@ -41,8 +43,8 @@ export function initializeActionsPanel(user, path) { // eslint-disable-next-line no-restricted-globals if (!isModerationPage()) { // don't show mod button in mod center page - document.getElementById('mod-actions-menu-btn-area') - .innerHTML = modActionsMenuIconHTML + document.getElementById('mod-actions-menu-btn-area').innerHTML = + modActionsMenuIconHTML; document .getElementsByClassName('mod-actions-menu-btn')[0] .addEventListener('click', toggleModActionsMenu); diff --git a/app/javascript/crayons/Buttons/Button.jsx b/app/javascript/crayons/Buttons/Button.jsx new file mode 100644 index 000000000..54e118193 --- /dev/null +++ b/app/javascript/crayons/Buttons/Button.jsx @@ -0,0 +1,84 @@ +import { h } from 'preact'; +import PropTypes from 'prop-types'; +import { useState } from 'preact/hooks'; +import classNames from 'classnames/bind'; +import { defaultChildrenPropTypes } from '../../common-prop-types/default-children-prop-types'; +import { Icon } from '@crayons'; + +export const ButtonNew = (props) => { + const { + children, + primary, + icon, + rounded, + destructive, + type = 'button', + className, + tooltip, + onKeyUp, + ...otherProps + } = props; + + const [suppressTooltip, setSuppressTooltip] = useState(false); + + const handleKeyUp = (event) => { + onKeyUp?.(event); + if (!tooltip) { + return; + } + setSuppressTooltip(event.key === 'Escape'); + }; + + const classes = classNames('c-btn', { + 'c-btn--primary': primary, + 'c-btn--destructive': destructive, + 'c-btn--icon-left': icon && children, + 'c-btn--icon-alone': icon && !children, + 'crayons-tooltip__activator': tooltip, + 'radius-full': rounded, + [className]: className, + }); + + return ( + + ); +}; + +ButtonNew.displayName = 'ButtonNew'; + +ButtonNew.propTypes = { + children: defaultChildrenPropTypes, + primary: PropTypes.bool, + rounded: PropTypes.bool, + destructive: PropTypes.bool, + type: PropTypes.oneOf(['button', 'submit']), + className: PropTypes.string, + tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + onKeyUp: PropTypes.func, + icon: PropTypes.ReactNode, +}; diff --git a/app/javascript/crayons/Buttons/__stories__/Buttons.mdx b/app/javascript/crayons/Buttons/__stories__/Buttons.mdx new file mode 100644 index 000000000..4ef137697 --- /dev/null +++ b/app/javascript/crayons/Buttons/__stories__/Buttons.mdx @@ -0,0 +1,63 @@ +# Buttons + +Buttons are clickable elements that are used to trigger actions. +They communicate calls to action to the user and allow users to interact with pages in a variety of ways. +Button labels express what action will occur when the user interacts with it. + + It is therefore very important that the different variants are implemented consistently across product, + so that they message the correct actions. + +## CTAs vs Links vs Buttons + +The most important of these rules is that the `` (or ``) and `); + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + + it('has no accessibility errors when props provided', async () => { + const { container } = render( + , + ); + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); + + it('renders a default button', () => { + const { container } = render(); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('renders a primary button', () => { + const { container } = render(); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('renders with an icon and text', () => { + const { container } = render(); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('renders with an icon only', () => { + const { container } = render(); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('renders a rounded button', () => { + const { container } = render(); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('renders with a tooltip', () => { + const { container } = render( + , + ); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('renders with additional classnames', () => { + const { container } = render( + , + ); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('should render a button as a specific button type (HTML type attribute) when buttonType is set.', () => { + const { container } = render(); + expect(container.innerHTML).toMatchSnapshot(); + }); + + it('should attach additional passed props to the button element', () => { + const mockClickHandler = jest.fn(); + + const { getByRole } = render( + , + ); + + const button = getByRole('button', { name: 'Hello world!' }); + button.click(); + + expect(mockClickHandler).toHaveBeenCalledTimes(1); + }); + + it('should suppress any tooltip and execute any onKeyUp prop when Escape is pressed', async () => { + const mockKeyUpHandler = jest.fn(); + + const { getByRole, getByTestId } = render( + , + ); + + const button = getByRole('button', { name: 'Hello world! test tooltip' }); + button.focus(); + + expect(getByTestId('tooltip')).not.toHaveClass( + 'crayons-tooltip__suppressed', + ); + + fireEvent.keyUp(button, { key: 'Escape' }); + + expect(getByTestId('tooltip')).toHaveClass('crayons-tooltip__suppressed'); + expect(mockKeyUpHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/javascript/crayons/Buttons/__tests__/__snapshots__/Buttons.test.jsx.snap b/app/javascript/crayons/Buttons/__tests__/__snapshots__/Buttons.test.jsx.snap new file mode 100644 index 000000000..9ba8af636 --- /dev/null +++ b/app/javascript/crayons/Buttons/__tests__/__snapshots__/Buttons.test.jsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`"`; + +exports[`"`; + +exports[`"`; + +exports[`"`; + +exports[`"`; + +exports[`"`; + +exports[`"`; + +exports[`"`; + +exports[`"`; diff --git a/app/javascript/crayons/Buttons/index.js b/app/javascript/crayons/Buttons/index.js new file mode 100644 index 000000000..8b166a86e --- /dev/null +++ b/app/javascript/crayons/Buttons/index.js @@ -0,0 +1 @@ +export * from './Button'; diff --git a/app/javascript/crayons/CTAs/CTA.jsx b/app/javascript/crayons/CTAs/CTA.jsx new file mode 100644 index 000000000..df61ae055 --- /dev/null +++ b/app/javascript/crayons/CTAs/CTA.jsx @@ -0,0 +1,22 @@ +import { h } from 'preact'; +import PropTypes from 'prop-types'; +import classNames from 'classnames/bind'; +import { Link } from '@crayons'; + +export const CTA = (props) => { + const { variant = 'default', className, ...otherProps } = props; + + const classes = classNames('c-cta', { + [`c-cta--${variant}`]: variant, + [className]: className, + }); + + return ; +}; + +CTA.displayName = 'CTA'; + +CTA.propTypes = { + variant: PropTypes.oneOf(['default', 'branded']), + className: PropTypes.string, +}; diff --git a/app/javascript/crayons/CTAs/__stories__/CTAs.mdx b/app/javascript/crayons/CTAs/__stories__/CTAs.mdx new file mode 100644 index 000000000..1ab64905e --- /dev/null +++ b/app/javascript/crayons/CTAs/__stories__/CTAs.mdx @@ -0,0 +1,40 @@ +# CTAs (Call To Actions) + +Call To Action is an extension for `` component therefore it inherits Link's props and configuration. +CTA is meant to be more prominent and visually heavy so use these cautiously. + +## CTAs vs Links vs Buttons + +The most important of these rules is that the `` (or ``) and `