From 3fed6efd886b0179b1ee29e7961f246c7647ac56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Connor=20B=C3=A4r?= Date: Mon, 27 Jul 2020 11:37:02 +0200 Subject: [PATCH] feat(components): migrate Tag to TypeScript (#654) * feat(components): migrate Tag to TypeScript * feat(components): add button type to Tag --- src/__snapshots__/storyshots.spec.js.snap | 254 +++++++++++------- src/components/Button/Button.tsx | 16 +- .../__snapshots__/CalendarTag.spec.js.snap | 27 +- .../CalendarTagTwoStep.spec.js.snap | 27 +- src/components/Input/Input.tsx | 6 +- src/components/Tag/Tag.docs.mdx | 2 +- src/components/Tag/Tag.js | 241 ----------------- .../Tag/{Tag.spec.js => Tag.spec.tsx} | 32 ++- .../Tag/{Tag.story.js => Tag.story.tsx} | 49 ++-- src/components/Tag/Tag.tsx | 238 ++++++++++++++++ .../{Tag.spec.js.snap => Tag.spec.tsx.snap} | 133 ++++++--- src/components/Tag/{index.js => index.tsx} | 2 +- 12 files changed, 583 insertions(+), 444 deletions(-) delete mode 100644 src/components/Tag/Tag.js rename src/components/Tag/{Tag.spec.js => Tag.spec.tsx} (84%) rename src/components/Tag/{Tag.story.js => Tag.story.tsx} (68%) create mode 100644 src/components/Tag/Tag.tsx rename src/components/Tag/__snapshots__/{Tag.spec.js.snap => Tag.spec.tsx.snap} (65%) rename src/components/Tag/{index.js => index.tsx} (95%) diff --git a/src/__snapshots__/storyshots.spec.js.snap b/src/__snapshots__/storyshots.spec.js.snap index 882987b686..93ec91dfda 100644 --- a/src/__snapshots__/storyshots.spec.js.snap +++ b/src/__snapshots__/storyshots.spec.js.snap @@ -12180,6 +12180,10 @@ exports[`Storyshots Components/Tabs Links 1`] = ` `; exports[`Storyshots Components/Tag Base 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -12189,24 +12193,33 @@ exports[`Storyshots Components/Tag Base 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; }
- Transactions +
+ Transactions +
`; exports[`Storyshots Components/Tag Clickable 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -12216,17 +12229,17 @@ exports[`Storyshots Components/Tag Clickable 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; cursor: pointer; outline: 0; - background: transparent; } .circuit-0:active { @@ -12234,7 +12247,7 @@ exports[`Storyshots Components/Tag Clickable 1`] = ` } .circuit-0:hover { - background-color: #D8DDE1; + background-color: #EEF0F2; border-color: #9DA7B1; } @@ -12247,15 +12260,24 @@ exports[`Storyshots Components/Tag Clickable 1`] = ` border: 0; } - + + `; exports[`Storyshots Components/Tag Removable 1`] = ` -.circuit-0 { +.circuit-3 { + position: relative; +} + +.circuit-1 { border: 0; -webkit-clip: rect(0 0 0 0); clip: rect(0 0 0 0); @@ -12268,7 +12290,7 @@ exports[`Storyshots Components/Tag Removable 1`] = ` width: 1px; } -.circuit-3 { +.circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; @@ -12277,14 +12299,16 @@ exports[`Storyshots Components/Tag Removable 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + padding-right: calc(4px + 32px); } .circuit-2 { @@ -12320,9 +12344,10 @@ exports[`Storyshots Components/Tag Removable 1`] = ` border-radius: 8px; padding: 8px; border: 0; - margin-left: 4px; - margin-right: -4px; - padding: 0; + position: absolute; + top: 1px; + right: 1px; + border-radius: 4px; } .circuit-2:focus { @@ -12352,11 +12377,15 @@ exports[`Storyshots Components/Tag Removable 1`] = ` }
- Transactions +
+ Transactions +
`; exports[`Storyshots Components/Tag With Suffix 1`] = ` +.circuit-2 { + position: relative; +} + .circuit-1 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -12473,14 +12524,15 @@ exports[`Storyshots Components/Tag With Suffix 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; } .circuit-0 { @@ -12489,25 +12541,29 @@ exports[`Storyshots Components/Tag With Suffix 1`] = ` }
- Transactions - - - + Transactions + + + +
`; @@ -12886,6 +12942,10 @@ exports[`Storyshots Components/Tooltip Top Left 1`] = ` `; exports[`Storyshots Forms/Calendar/CalendarTag Base 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -12895,17 +12955,17 @@ exports[`Storyshots Forms/Calendar/CalendarTag Base 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; cursor: pointer; outline: 0; - background: transparent; } .circuit-0:active { @@ -12913,7 +12973,7 @@ exports[`Storyshots Forms/Calendar/CalendarTag Base 1`] = ` } .circuit-0:hover { - background-color: #D8DDE1; + background-color: #EEF0F2; border-color: #9DA7B1; } @@ -12927,15 +12987,24 @@ exports[`Storyshots Forms/Calendar/CalendarTag Base 1`] = ` }
- + +
`; exports[`Storyshots Forms/Calendar/CalendarTagTwoStep Base 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -12945,17 +13014,17 @@ exports[`Storyshots Forms/Calendar/CalendarTagTwoStep Base 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; cursor: pointer; outline: 0; - background: transparent; } .circuit-0:active { @@ -12963,7 +13032,7 @@ exports[`Storyshots Forms/Calendar/CalendarTagTwoStep Base 1`] = ` } .circuit-0:hover { - background-color: #D8DDE1; + background-color: #EEF0F2; border-color: #9DA7B1; } @@ -12977,11 +13046,16 @@ exports[`Storyshots Forms/Calendar/CalendarTagTwoStep Base 1`] = ` }
- + +
`; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 341d59fa17..3b41ea466c 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -14,7 +14,15 @@ */ /** @jsx jsx */ -import React, { HTMLProps, ReactNode, FC, SVGProps, MouseEvent } from 'react'; +import { + forwardRef, + Ref, + HTMLProps, + ReactNode, + FC, + SVGProps, + MouseEvent, +} from 'react'; import { css, jsx } from '@emotion/core'; import isPropValid from '@emotion/is-prop-valid'; import { Theme } from '@sumup/design-tokens'; @@ -59,7 +67,7 @@ export interface BaseProps { /** The ref to the html dom element, it can be an anchor or a button */ - ref?: React.Ref; + ref?: Ref; /** * The HTML button type */ @@ -209,7 +217,7 @@ const BaseButton = styled('button', { function ButtonComponent( { children, icon: Icon, tracking, ...props }: ButtonProps, - ref?: React.Ref, + ref?: BaseProps['ref'], ): ReturnType { const { Link } = useComponents(); const LinkButton = BaseButton.withComponent(Link); @@ -232,4 +240,4 @@ function ButtonComponent( * The Button component enables the user to perform an action or navigate * to a different screen. */ -export const Button = React.forwardRef(ButtonComponent); +export const Button = forwardRef(ButtonComponent); diff --git a/src/components/CalendarTag/__snapshots__/CalendarTag.spec.js.snap b/src/components/CalendarTag/__snapshots__/CalendarTag.spec.js.snap index cb355cbfe9..0374364a3a 100644 --- a/src/components/CalendarTag/__snapshots__/CalendarTag.spec.js.snap +++ b/src/components/CalendarTag/__snapshots__/CalendarTag.spec.js.snap @@ -1,6 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CalendarTag should render with default styles 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -10,17 +14,17 @@ exports[`CalendarTag should render with default styles 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; cursor: pointer; outline: 0; - background: transparent; } .circuit-0:active { @@ -28,7 +32,7 @@ exports[`CalendarTag should render with default styles 1`] = ` } .circuit-0:hover { - background-color: #D8DDE1; + background-color: #EEF0F2; border-color: #9DA7B1; } @@ -42,10 +46,15 @@ exports[`CalendarTag should render with default styles 1`] = ` }
- + +
`; diff --git a/src/components/CalendarTagTwoStep/__snapshots__/CalendarTagTwoStep.spec.js.snap b/src/components/CalendarTagTwoStep/__snapshots__/CalendarTagTwoStep.spec.js.snap index de265a8a87..6fe569e49e 100644 --- a/src/components/CalendarTagTwoStep/__snapshots__/CalendarTagTwoStep.spec.js.snap +++ b/src/components/CalendarTagTwoStep/__snapshots__/CalendarTagTwoStep.spec.js.snap @@ -1,6 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CalendarTagTwoStep should render with default styles 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -10,17 +14,17 @@ exports[`CalendarTagTwoStep should render with default styles 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; cursor: pointer; outline: 0; - background: transparent; } .circuit-0:active { @@ -28,7 +32,7 @@ exports[`CalendarTagTwoStep should render with default styles 1`] = ` } .circuit-0:hover { - background-color: #D8DDE1; + background-color: #EEF0F2; border-color: #9DA7B1; } @@ -42,10 +46,15 @@ exports[`CalendarTagTwoStep should render with default styles 1`] = ` }
- + +
`; diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index 6e419b31ce..e10003733c 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -14,7 +14,7 @@ */ /** @jsx jsx */ -import React, { FC, HTMLProps, ReactNode } from 'react'; +import { forwardRef, Ref, FC, HTMLProps, ReactNode } from 'react'; import { css, jsx, InterpolationWithTheme } from '@emotion/core'; import { Theme } from '@sumup/design-tokens'; @@ -102,7 +102,7 @@ export interface InputProps extends Omit, 'label'> { /** * The ref to the html dom element */ - ref?: React.Ref; + ref?: Ref; } const containerStyles = ({ theme }: StyleProps) => css` @@ -350,4 +350,4 @@ function InputComponent( /** * Input component for forms. Takes optional prefix and suffix as render props. */ -export const Input = React.forwardRef(InputComponent); +export const Input = forwardRef(InputComponent); diff --git a/src/components/Tag/Tag.docs.mdx b/src/components/Tag/Tag.docs.mdx index 532a156e51..4d9f7d8f1e 100644 --- a/src/components/Tag/Tag.docs.mdx +++ b/src/components/Tag/Tag.docs.mdx @@ -1,5 +1,5 @@ import { Status, Props, Story } from '../../../.storybook/components'; -import Tag from './Tag'; +import { Tag } from './Tag'; # Tag diff --git a/src/components/Tag/Tag.js b/src/components/Tag/Tag.js deleted file mode 100644 index eece47185f..0000000000 --- a/src/components/Tag/Tag.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright 2019, SumUp Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** @jsx jsx */ -import React from 'react'; -import PropTypes from 'prop-types'; -import styled from '@emotion/styled'; -import { css, jsx } from '@emotion/core'; - -import { - eitherOrPropType, - childrenPropType, -} from '../../util/shared-prop-types'; -import { textMega, focusOutline } from '../../styles/style-helpers'; -import CloseButton from '../CloseButton'; -import useClickHandler from '../../hooks/use-click-handler'; - -const tagStyles = ({ theme }) => css` - label: tag; - display: inline-flex; - align-items: center; - border-radius: ${theme.borderRadius.mega}; - ${textMega({ theme })}; - border: 1px solid ${theme.colors.n300}; - padding: ${theme.spacings.bit} ${theme.spacings.kilo}; - cursor: default; - transition: opacity ${theme.transitions.default}, - color ${theme.transitions.default}, - background-color ${theme.transitions.default}, - border-color ${theme.transitions.default}; -`; - -const tagClickableStyles = ({ onClick, theme }) => - onClick && - css` - label: tag--clickable; - cursor: pointer; - outline: 0; - background: transparent; - - &:active { - color: ${theme.colors.bodyColor}; - } - - &:hover { - background-color: ${theme.colors.n300}; - border-color: ${theme.colors.n500}; - } - - &:focus { - ${focusOutline({ theme })}; - } - `; - -const tagSelectedStyles = ({ selected, theme }) => - selected && - css` - label: tag--selected; - background-color: ${theme.colors.p500}; - border-color: ${theme.colors.p500}; - color: ${theme.colors.white}; - `; - -const tagSelectedClickableStyles = ({ selected, onClick, theme }) => - selected && - onClick && - css` - label: tag--selected--clickable; - - &:active { - color: ${theme.colors.white}; - } - - &:hover { - background-color: ${theme.colors.p700}; - border-color: ${theme.colors.p700}; - } - `; - -const TagElement = styled('div')( - tagStyles, - tagClickableStyles, - tagSelectedStyles, - tagSelectedClickableStyles, -); - -const prefixStyles = ({ theme }) => css` - label: tag__prefix; - margin-left: -${theme.spacings.bit}; - margin-right: ${theme.spacings.bit}; -`; - -const suffixStyles = ({ theme }) => css` - label: tag__suffix; - margin-left: ${theme.spacings.bit}; - margin-right: -${theme.spacings.bit}; -`; - -const closeButtonStyles = () => css` - label: tag__close-button; - padding: 0; -`; - -const RemoveButton = styled(CloseButton)(suffixStyles, closeButtonStyles); - -/** - * Tag component - */ -const Tag = React.forwardRef( - ( - { - children, - prefix: Prefix, - suffix: Suffix, - onRemove, - labelRemoveButton, - selected, - onClick, - tracking, - ...props - }, - ref, - ) => { - const prefixElement = Prefix && ( - prefixStyles({ theme, selected })} - /> - ); - const suffixElement = Suffix && ( - suffixStyles({ theme, selected })} - /> - ); - const handleClick = useClickHandler(onClick, tracking, 'tag'); - const as = onClick ? 'button' : 'div'; - - return ( - - {prefixElement} - - {children} - - {onRemove && ( - - )} - - {!onRemove && suffixElement} - - ); - }, -); - -Tag.displayName = 'Tag'; - -Tag.propTypes = { - /** - * The content of the tag. - */ - children: childrenPropType, - /** - * Render prop that should render a left-aligned icon or element. - */ - prefix: PropTypes.func, - /** - * Render prop that should render a right-aligned icon or element. - */ - suffix: eitherOrPropType('suffix', 'onRemove', PropTypes.func), - /** - * Renders a close button inside the tag and calls the provided function - * when the button is clicked. - */ - onRemove: eitherOrPropType('suffix', 'onRemove', PropTypes.func), - /** - * Text label for the remove icon for screen readers. - * Important for accessibility. - */ - labelRemoveButton: PropTypes.string, - /** - * Triggers selected styles on the tag. - */ - selected: PropTypes.bool, - /** - * Additional data that is dispatched with the tracking event. - */ - tracking: PropTypes.shape({ - label: PropTypes.string.isRequired, - component: PropTypes.string, - customParameters: PropTypes.object, - }), - /** - * The ref to the html button dom element - */ - ref: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.shape({ - current: PropTypes.any, - }), - ]), -}; - -Tag.defaultProps = { - children: null, - prefix: null, - suffix: null, - onRemove: null, - selected: false, - labelRemoveButton: 'remove', - tracking: {}, - ref: undefined, -}; - -/** - * @component - */ -export default Tag; diff --git a/src/components/Tag/Tag.spec.js b/src/components/Tag/Tag.spec.tsx similarity index 84% rename from src/components/Tag/Tag.spec.js rename to src/components/Tag/Tag.spec.tsx index c99fcad0e9..912ed90633 100644 --- a/src/components/Tag/Tag.spec.js +++ b/src/components/Tag/Tag.spec.tsx @@ -15,9 +15,18 @@ import React from 'react'; -import Tag from '.'; +import { + create, + renderToHtml, + axe, + render, + act, + userEvent, +} from '../../util/test-utils'; -const DummyIcon = (props) =>
; +import { Tag } from './Tag'; + +const DummyIcon = (props: any) =>
; describe('Tag', () => { /** @@ -74,8 +83,8 @@ describe('Tag', () => { * Should accept a working ref */ it('should accept a working ref', () => { - const tref = React.createRef(); - const { container } = render( 1} />); + const tref = React.createRef(); + const { container } = render(); const button = container.querySelector('button'); expect(tref.current).toBe(button); }); @@ -103,11 +112,11 @@ describe('Tag', () => { expect(getByTestId('tag-close')).not.toBeNull(); }); - it('should calls onRemove when click close', () => { + it('should call onRemove when closed', () => { const { getByTestId } = render(SomeTest); act(() => { - fireEvent.click(getByTestId('tag-close')); + userEvent.click(getByTestId('tag-close')); }); expect(props.onRemove).toHaveBeenCalledTimes(1); @@ -123,17 +132,6 @@ describe('Tag', () => { const { getByTestId } = render(SomeTest); expect(getByTestId('tag-icon')).not.toBeNull(); }); - - it('gives priority to close button when removable', () => { - const onRemove = jest.fn(); - - const { queryByTestId } = render( - SomeTest, - ); - - expect(queryByTestId('tag-icon')).toBeNull(); - expect(queryByTestId('tag-close')).not.toBeNull(); - }); }); describe('when a prefix prop is passed', () => { diff --git a/src/components/Tag/Tag.story.js b/src/components/Tag/Tag.story.tsx similarity index 68% rename from src/components/Tag/Tag.story.js rename to src/components/Tag/Tag.story.tsx index e91e60c85f..fd2f93a78d 100644 --- a/src/components/Tag/Tag.story.js +++ b/src/components/Tag/Tag.story.tsx @@ -15,11 +15,11 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { boolean, text } from '@storybook/addon-knobs/react'; +import { boolean, text } from '@storybook/addon-knobs'; import { Check } from '@sumup/icons'; import docs from './Tag.docs.mdx'; -import Tag from './Tag'; +import { Tag, TagProps } from './Tag'; export default { title: 'Components/Tag', @@ -29,42 +29,39 @@ export default { }, }; -export const base = () => ( +const BaseTag = (props: Partial) => ( : null} - suffix={boolean('Suffix', false) ? : null} - onClick={boolean('Clickable', false) ? action('Tag clicked') : null} + prefix={boolean('Prefix', false) ? Check : undefined} + suffix={boolean('Suffix', false) ? Check : undefined} + onClick={boolean('Clickable', false) ? action('Tag clicked') : undefined} + onRemove={boolean('Removable', false) ? action('Tag removed') : undefined} + {...props} > Transactions ); -export const selected = () => Transactions; +export const base = () => ; -export const withPrefix = () => Transactions; +export const selected = () => ; -export const withSuffix = () => Transactions; +export const withPrefix = () => ; -export const removable = () => ( - - Transactions - -); +export const withSuffix = () => ; export const clickable = () => ( - - Transactions - + tracking={{ label: text('Tracking Label', 'trackingId') }} + /> +); + +export const removable = () => ( + ); diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx new file mode 100644 index 0000000000..545f3370dd --- /dev/null +++ b/src/components/Tag/Tag.tsx @@ -0,0 +1,238 @@ +/** + * Copyright 2019, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** @jsx jsx */ +import { forwardRef, HTMLProps, Ref, FC, SVGProps, MouseEvent } from 'react'; +import { css, jsx } from '@emotion/core'; +import { Dispatch as TrackingProps } from '@sumup/collector'; +import { Theme } from '@sumup/design-tokens'; + +import styled, { StyleProps } from '../../styles/styled'; +import { textMega, focusOutline } from '../../styles/style-helpers'; +import useClickHandler from '../../hooks/use-click-handler'; +import { CloseButton, CloseButtonProps } from '../CloseButton/CloseButton'; + +interface BaseProps { + /** + * Render prop that should render a leading-aligned icon or element. + */ + prefix?: FC>; + /** + * Render prop that should render a trailing-aligned icon or element. + */ + suffix?: FC>; + /** + * Renders a close button inside the tag and calls the provided function + * when the button is clicked. + */ + onRemove?: (event: MouseEvent) => void; + /** + * Text label for the remove icon for screen readers. + * Important for accessibility. + */ + labelRemoveButton?: string; + /** + * Triggers selected styles on the tag. + */ + selected?: boolean; + /** + * Additional data that is dispatched with the tracking event. + */ + tracking?: TrackingProps; + /** + * The ref to the DOM element + */ + ref?: Ref; +} + +type DivElProps = Omit, 'prefix'>; +type ButtonElProps = Omit, 'prefix'>; + +export type TagProps = BaseProps & DivElProps & ButtonElProps; + +const BORDER_WIDTH = '1px'; + +type TagElProps = Omit & { + removable: boolean; +}; + +const tagBaseStyles = ({ theme }: StyleProps) => css` + label: tag; + display: inline-flex; + align-items: center; + ${textMega({ theme })}; + border: ${BORDER_WIDTH} solid ${theme.colors.n300}; + border-radius: ${theme.borderRadius.giga}; + padding: ${theme.spacings.bit} ${theme.spacings.kilo}; + cursor: default; + background-color: ${theme.colors.white}; + transition: opacity ${theme.transitions.default}, + color ${theme.transitions.default}, + background-color ${theme.transitions.default}, + border-color ${theme.transitions.default}; +`; + +const tagRemovableStyles = ({ theme, removable }: StyleProps & TagElProps) => + removable && + css` + label: tag--removable; + padding-right: calc(${theme.spacings.bit} + ${theme.spacings.tera}); + `; + +const tagClickableStyles = ({ theme, onClick }: StyleProps & TagElProps) => + onClick && + css` + label: tag--clickable; + cursor: pointer; + outline: 0; + + &:active { + color: ${theme.colors.bodyColor}; + } + + &:hover { + background-color: ${theme.colors.n200}; + border-color: ${theme.colors.n500}; + } + + &:focus { + ${focusOutline({ theme })}; + } + `; + +const tagSelectedStyles = ({ theme, selected }: StyleProps & TagElProps) => + selected && + css` + label: tag--selected; + background-color: ${theme.colors.p500}; + border-color: ${theme.colors.p700}; + color: ${theme.colors.white}; + `; + +const tagSelectedClickableStyles = ({ + theme, + selected, + onClick, +}: StyleProps & TagElProps) => + selected && + onClick && + css` + label: tag--selected--clickable; + + &:active { + color: ${theme.colors.white}; + } + + &:hover { + background-color: ${theme.colors.p700}; + border-color: ${theme.colors.p700}; + } + `; + +const TagElement = styled('div')( + tagBaseStyles, + tagRemovableStyles, + tagClickableStyles, + tagSelectedStyles, + tagSelectedClickableStyles, +); + +const prefixStyles = (theme: Theme) => css` + label: tag__prefix; + margin-left: -${theme.spacings.bit}; + margin-right: ${theme.spacings.bit}; +`; + +const suffixStyles = (theme: Theme) => css` + label: tag__suffix; + margin-left: ${theme.spacings.bit}; + margin-right: -${theme.spacings.bit}; +`; + +const closeButtonStyles = ({ theme }: StyleProps) => css` + label: tag__close-button; + position: absolute; + top: ${BORDER_WIDTH}; + right: ${BORDER_WIDTH}; + border-radius: ${theme.borderRadius.mega}; +`; + +const RemoveButton = styled(CloseButton)(closeButtonStyles); + +const Container = styled.div` + label: tag__container; + position: relative; +`; + +const TagComponent = ( + { + children, + prefix: Prefix, + suffix: Suffix, + onRemove, + labelRemoveButton = 'Remove', + selected, + onClick, + tracking, + ...props + }: TagProps, + ref: BaseProps['ref'], +) => { + const as = onClick ? 'button' : 'div'; + const handleClick = useClickHandler>( + onClick, + tracking, + 'tag', + ); + const removable = Boolean(onRemove); + + return ( + + + {Prefix && } + + {children} + + {Suffix && } + + + {removable && ( + + )} + + ); +}; + +export const Tag = forwardRef(TagComponent); diff --git a/src/components/Tag/__snapshots__/Tag.spec.js.snap b/src/components/Tag/__snapshots__/Tag.spec.tsx.snap similarity index 65% rename from src/components/Tag/__snapshots__/Tag.spec.js.snap rename to src/components/Tag/__snapshots__/Tag.spec.tsx.snap index 629ece6a86..643d807748 100644 --- a/src/components/Tag/__snapshots__/Tag.spec.js.snap +++ b/src/components/Tag/__snapshots__/Tag.spec.tsx.snap @@ -1,6 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Tag when is clickable should render with clickable styles 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -10,17 +14,17 @@ exports[`Tag when is clickable should render with clickable styles 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; cursor: pointer; outline: 0; - background: transparent; } .circuit-0:active { @@ -28,7 +32,7 @@ exports[`Tag when is clickable should render with clickable styles 1`] = ` } .circuit-0:hover { - background-color: #D8DDE1; + background-color: #EEF0F2; border-color: #9DA7B1; } @@ -41,14 +45,23 @@ exports[`Tag when is clickable should render with clickable styles 1`] = ` border: 0; } - + +
`; exports[`Tag when is default should render with default styles 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -58,25 +71,34 @@ exports[`Tag when is default should render with default styles 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; }
- SomeTest +
+ SomeTest +
`; exports[`Tag when is selected should change the close icon color 1`] = ` .circuit-3 { + position: relative; +} + +.circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; display: -ms-inline-flexbox; @@ -85,16 +107,18 @@ exports[`Tag when is selected should change the close icon color 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + padding-right: calc(4px + 32px); background-color: #3388FF; - border-color: #3388FF; + border-color: #1760CE; color: #FFFFFF; } @@ -131,9 +155,10 @@ exports[`Tag when is selected should change the close icon color 1`] = ` border-radius: 8px; padding: 8px; border: 0; - margin-left: 4px; - margin-right: -4px; - padding: 0; + position: absolute; + top: 1px; + right: 1px; + border-radius: 4px; } .circuit-2:focus { @@ -162,7 +187,7 @@ exports[`Tag when is selected should change the close icon color 1`] = ` border-color: #003C8B; } -.circuit-0 { +.circuit-1 { border: 0; -webkit-clip: rect(0 0 0 0); clip: rect(0 0 0 0); @@ -176,13 +201,17 @@ exports[`Tag when is selected should change the close icon color 1`] = ` }
- SomeTest +
+ SomeTest +
`; exports[`Tag when is selected should change the given icon color 1`] = ` +.circuit-2 { + position: relative; +} + .circuit-1 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -219,16 +252,17 @@ exports[`Tag when is selected should change the given icon color 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; background-color: #3388FF; - border-color: #3388FF; + border-color: #1760CE; color: #FFFFFF; } @@ -238,17 +272,25 @@ exports[`Tag when is selected should change the given icon color 1`] = ` }
- SomeTest + class="circuit-1" + > +
+ SomeTest +
`; exports[`Tag when is selected should render with selected styles 1`] = ` +.circuit-1 { + position: relative; +} + .circuit-0 { display: -webkit-inline-box; display: -webkit-inline-flex; @@ -258,22 +300,27 @@ exports[`Tag when is selected should render with selected styles 1`] = ` -webkit-box-align: center; -ms-flex-align: center; align-items: center; - border-radius: 4px; font-size: 16px; line-height: 24px; border: 1px solid #D8DDE1; + border-radius: 6px; padding: 4px 12px; cursor: default; - -webkit-transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; - transition: opacity 120ms ease-in-out,color 120ms ease-in-out,background-color 120ms ease-in-out,border-color 120ms ease-in-out; + background-color: #FFFFFF; + -webkit-transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; + transition: opacity 120ms ease-in-out, color 120ms ease-in-out, background-color 120ms ease-in-out, border-color 120ms ease-in-out; background-color: #3388FF; - border-color: #3388FF; + border-color: #1760CE; color: #FFFFFF; }
- SomeTest +
+ SomeTest +
`; diff --git a/src/components/Tag/index.js b/src/components/Tag/index.tsx similarity index 95% rename from src/components/Tag/index.js rename to src/components/Tag/index.tsx index c9ce6070d3..1644c567ce 100644 --- a/src/components/Tag/index.js +++ b/src/components/Tag/index.tsx @@ -13,6 +13,6 @@ * limitations under the License. */ -import Tag from './Tag'; +import { Tag } from './Tag'; export default Tag;