diff --git a/packages/doc/content/components/components/button/default-web.mdx b/packages/doc/content/components/components/button/default-web.mdx index 3b4a590ab5..eca4cc0f54 100644 --- a/packages/doc/content/components/components/button/default-web.mdx +++ b/packages/doc/content/components/components/button/default-web.mdx @@ -14,6 +14,14 @@ You can use all differente compounds like `` and ` ``` +#### Inverted +```javascript + + + + +``` + #### As an anchor ```javascript @@ -35,6 +43,31 @@ You can use all differente compounds like `` and ` ``` +#### With Loading + +```javascript state +render(() => { + const [loading, setLoading] = useState(true); + const onChange = () => setLoading(!loading); + + return ( + + + Loading + + + + + + + + + + + ); +}); +``` + ### Props diff --git a/packages/doc/content/components/components/button/icon-web.mdx b/packages/doc/content/components/components/button/icon-web.mdx index ae3b35cb68..d187b3e2a1 100644 --- a/packages/doc/content/components/components/button/icon-web.mdx +++ b/packages/doc/content/components/components/button/icon-web.mdx @@ -23,6 +23,33 @@ ``` +#### With Loading + +```javascript state +render(() => { + const [loading, setLoading] = useState(true); + const onChange = () => setLoading(!loading); + + return ( + + + Loading + + + + + + + + + + + + + ); +}); +``` + ### Props diff --git a/packages/doc/content/components/components/button/outline.mdx b/packages/doc/content/components/components/button/outline.mdx index a1618b604d..3736307609 100644 --- a/packages/doc/content/components/components/button/outline.mdx +++ b/packages/doc/content/components/components/button/outline.mdx @@ -45,6 +45,33 @@ You can use all differente compounds like ` , ); expect(container).toMatchSnapshot(); }); - it('should match snapshot with text Button', () => { + it('Default Button with href prop and Loading', () => { const { container } = render( - + , ); expect(container).toMatchSnapshot(); }); - it('should match snapshot with link Button', () => { + it('Link Button with href prop', () => { const { container } = render( - + + Link as an anchor + , ); expect(container).toMatchSnapshot(); }); - it('should match snapshot with default Button with Icon', () => { + it('Outline Button with href prop', () => { const { container } = render( - + , ); - expect(container).toMatchSnapshot(); + fireEvent.click(getByText('Button')); + + expect(onClickMock).toHaveBeenCalled(); }); - it('Link Button with href prop', () => { - const { container } = render( + it('should call onClick function when click on Button.Outline', () => { + const onClickMock = jest.fn(); + const { getByText } = render( - - Link as an anchor - + + Button.Outline + , ); - expect(container).toMatchSnapshot(); + fireEvent.click(getByText('Button.Outline')); + + expect(onClickMock).toHaveBeenCalled(); }); - it('Outline Button with href prop', () => { - const { container } = render( + it('should call onClick function when click on Button.Text', () => { + const onClickMock = jest.fn(); + const { getByText } = render( - - Outline as an anchor - + Button.Text , ); - expect(container).toMatchSnapshot(); + fireEvent.click(getByText('Button.Text')); + + expect(onClickMock).toHaveBeenCalled(); }); - it('Text Button with href prop', () => { - const { container } = render( + it('should call onClick function when click on Button.Icon', () => { + const onClickMock = jest.fn(); + const { getByRole } = render( - - Text as an anchor - + , ); - expect(container).toMatchSnapshot(); - }); - }); + fireEvent.click(getByRole('button')); - describe('secondary buttons', () => { - describe('Without props', () => { - it('should match snapshot with default Button', () => { - const { container } = render( - - + , + ); - expect(container).toMatchSnapshot(); - }); + fireEvent.click(getByText('Button')); - it('should match snapshot with link Button with Icon', () => { - const { container } = render( - - - , - ); + expect(onClickMock).not.toHaveBeenCalled(); + }); - expect(container).toMatchSnapshot(); - }); + it('should not call onClick function when click on Button is loading', () => { + const onClickMock = jest.fn(); + const { getByText } = render( + + + , + ); - it('should match snapshot with icon Button', () => { - const { container } = render( - - - , - ); + fireEvent.click(getByText('Button')); - expect(container).toMatchSnapshot(); - }); + expect(onClickMock).not.toHaveBeenCalled(); }); + }); - describe('With inverted prop', () => { - it('should match snapshot with default Button', () => { - const { container } = render( + describe('Loading prop', () => { + describe('Default Button', () => { + it('Text should not to be visible', () => { + render( - , ); - expect(container).toMatchSnapshot(); - }); - - it('should match snapshot with default Button with Icon', () => { - const { container } = render( - - , ); - expect(container).toMatchSnapshot(); - }); + const button = screen.getByRole('button', { name: 'button' }); + const svg = within(button).getByRole('img'); - it('should match snapshot with outline Button with Icon', () => { - const { container } = render( - - - , - ); - - expect(container).toMatchSnapshot(); + expect(svg).toHaveStyle('fill: transparent'); }); - it('should match snapshot with text Button', () => { - const { container } = render( + it('Spinner should to be rendered', () => { + render( - + , ); - expect(container).toMatchSnapshot(); - }); + const spinner = screen.getByLabelText('loading-icon'); - it('should match snapshot with text Button with Icon', () => { - const { container } = render( - - - , - ); - - expect(container).toMatchSnapshot(); + expect(spinner).toBeVisible(); }); - it('should match snapshot with text Button with Icon', () => { - const { container } = render( + it('Button should to be disabled', () => { + render( - + , ); - expect(container).toMatchSnapshot(); - }); - - it('should match snapshot with icon Button', () => { - const { container } = render( - - - , - ); + const button = screen.getByRole('button', { name: 'button' }); - expect(container).toMatchSnapshot(); + expect(button).toBeDisabled(); }); }); - describe('With small prop', () => { - it('should match snapshot with default Button', () => { - const { container } = render( - - - , - ); - - fireEvent.click(getByText('Button')); - - expect(onClickMock).toHaveBeenCalled(); - }); - - it('should call onClick function when click on Button.Outline', () => { - const onClickMock = jest.fn(); - const { getByText } = render( - - Button.Outline - , - ); - - fireEvent.click(getByText('Button.Outline')); - - expect(onClickMock).toHaveBeenCalled(); - }); - - it('should call onClick function when click on Button.Text', () => { - const onClickMock = jest.fn(); - const { getByText } = render( - - Button.Text - , - ); - - fireEvent.click(getByText('Button.Text')); - - expect(onClickMock).toHaveBeenCalled(); - }); - - it('should call onClick function when click on Button.Icon', () => { - const onClickMock = jest.fn(); - const { getByRole } = render( - - - , - ); - - fireEvent.click(getByRole('button')); - - expect(onClickMock).toHaveBeenCalled(); - }); - - it('should not call onClick function when click on Button disabled', () => { - const onClickMock = jest.fn(); - const { getByText } = render( - - - , - ); - - fireEvent.click(getByText('Button')); - - expect(onClickMock).not.toHaveBeenCalled(); - }); - }); }); diff --git a/packages/yoga/src/Button/web/Icon.jsx b/packages/yoga/src/Button/web/Icon.jsx index d11f556ab8..95e712b64c 100644 --- a/packages/yoga/src/Button/web/Icon.jsx +++ b/packages/yoga/src/Button/web/Icon.jsx @@ -4,6 +4,7 @@ import styled, { withTheme } from 'styled-components'; import StyledButton from './StyledButton'; import Icon from '../../Icon'; +import Spinner from '../../Spinner'; const IconStyled = styled(StyledButton)` padding: 0; @@ -39,6 +40,7 @@ const ButtonIcon = forwardRef( }, small, disabled, + isLoading, ...props }, ref, @@ -48,13 +50,19 @@ const ButtonIcon = forwardRef( {...props} ref={ref} small={small} - disabled={disabled} + disabled={disabled || isLoading} aria-disabled={disabled} + isLoading={isLoading} > - + {isLoading ? ( + + ) : ( + + )} ); }, @@ -66,6 +74,7 @@ ButtonIcon.propTypes = { secondary: bool, inverted: bool, icon: oneOfType([node, func]), + isLoading: bool, }; ButtonIcon.defaultProps = { @@ -74,6 +83,7 @@ ButtonIcon.defaultProps = { secondary: false, inverted: false, icon: undefined, + isLoading: false, }; ButtonIcon.displayName = 'Button.Icon'; diff --git a/packages/yoga/src/Button/web/Link.jsx b/packages/yoga/src/Button/web/Link.jsx index 1058b7d4fc..212d443f9c 100644 --- a/packages/yoga/src/Button/web/Link.jsx +++ b/packages/yoga/src/Button/web/Link.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { forwardRef } from 'react'; import { bool, string, oneOfType, node, func } from 'prop-types'; import styled from 'styled-components'; @@ -68,9 +68,17 @@ const Link = styled(Button)` }} `; -const ButtonLink = ({ disabled, ...props }) => ( - -); +const ButtonLink = forwardRef(({ ...rest }, ref) => { + const props = Object.fromEntries( + Object.entries(rest).filter(([key]) => key !== 'isLoading'), + ); + + const { disabled } = props; + + return ( + + ); +}); ButtonLink.propTypes = { disabled: bool, diff --git a/packages/yoga/src/Button/web/Outline.jsx b/packages/yoga/src/Button/web/Outline.jsx index 6852b85f2e..acc30f9dcc 100644 --- a/packages/yoga/src/Button/web/Outline.jsx +++ b/packages/yoga/src/Button/web/Outline.jsx @@ -7,6 +7,7 @@ const ButtonOutline = styled(Button)` ${({ inverted, secondary, + isLoading, theme: { yoga: { colors: { white }, @@ -55,7 +56,7 @@ const ButtonOutline = styled(Button)` border-color: ${outline.font.disabled.color}; color: ${outline.font.disabled.color}; - svg { + svg { fill: ${outline.font.disabled.color}; } } @@ -99,6 +100,20 @@ const ButtonOutline = styled(Button)` ` : '' } + + ${ + isLoading + ? ` + &:disabled { + color: transparent; + + svg { + fill: transparent; + } + } + ` + : '' + } `; }} `; diff --git a/packages/yoga/src/Button/web/StyledButton.jsx b/packages/yoga/src/Button/web/StyledButton.jsx index 0729107d1a..1d0faaa2a0 100644 --- a/packages/yoga/src/Button/web/StyledButton.jsx +++ b/packages/yoga/src/Button/web/StyledButton.jsx @@ -14,6 +14,7 @@ const StyledButton = styled.button` inverted, secondary, disabled, + isLoading, theme: { yoga: { baseFont, @@ -95,7 +96,6 @@ const StyledButton = styled.button` ` : '' } - ${ inverted @@ -128,6 +128,25 @@ const StyledButton = styled.button` ` : '' } + + ${ + isLoading + ? ` + position: relative; + color: transparent; + ` + : '' + } + + ${ + (isLoading && inverted) || (isLoading && disabled) + ? ` + svg { + fill: transparent; + } + ` + : '' + } `; }} `; diff --git a/packages/yoga/src/Button/web/Text.jsx b/packages/yoga/src/Button/web/Text.jsx index 968188e836..6ba8d9c4e0 100644 --- a/packages/yoga/src/Button/web/Text.jsx +++ b/packages/yoga/src/Button/web/Text.jsx @@ -1,9 +1,11 @@ +import React, { forwardRef } from 'react'; import styled from 'styled-components'; import { hexToRgb } from '@gympass/yoga-common'; +import { bool } from 'prop-types'; import Button from './Button'; -const ButtonText = styled(Button)` +const StyledButton = styled(Button)` ${({ secondary, inverted, @@ -81,6 +83,33 @@ const ButtonText = styled(Button)` }} `; +const ButtonText = forwardRef(({ ...rest }, ref) => { + const props = Object.fromEntries( + Object.entries(rest).filter(([key]) => key !== 'isLoading'), + ); + + const { secondary, inverted } = props; + + return ( + + ); +}); + +ButtonText.propTypes = { + inverted: bool, + secondary: bool, +}; + +ButtonText.defaultProps = { + inverted: false, + secondary: false, +}; + ButtonText.displayName = 'Button.Text'; export default ButtonText; diff --git a/packages/yoga/src/Button/web/__snapshots__/Button.test.jsx.snap b/packages/yoga/src/Button/web/__snapshots__/Button.test.jsx.snap index d5a01acad7..fbf9e0f014 100644 --- a/packages/yoga/src/Button/web/__snapshots__/Button.test.jsx.snap +++ b/packages/yoga/src/Button/web/__snapshots__/Button.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` + +`; -.c1 { - height: unset; - padding: 0; - background-color: unset; +exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` - -`; +.c1 { + background-color: transparent; + border-color: transparent; + color: #D8385E; +} -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` + +`; -.c1 svg { - fill: #D8385E; -} - -.c1:not([disabled]):hover, -.c1:not([disabled]):focus, -.c1:not([disabled]):active { - background-color: transparent; - box-shadow: none; -} - -.c1:not([disabled]):hover { - color: rgba(216,56,94,0.5); -} - -.c1:not([disabled]):hover svg { - fill: rgba(216,56,94,0.5); -} - -.c1:not([disabled]):focus, -.c1:not([disabled]):active { - color: rgba(216,56,94,0.75); -} - -.c1:not([disabled]):focus svg, -.c1:not([disabled]):active svg { - fill: rgba(216,56,94,0.75); -} - -.c1:disabled { - background-color: transparent; - border-color: transparent; - color: #6B6B78; -} - -.c1:disabled svg { - fill: #6B6B78; -} - -
- -
-`; - -exports[` `; -exports[` `; -exports[` `; -exports[` + +`; -.c1:not([disabled]):active svg { - fill: rgba(255,255,255,0.75); -} - -.c1:disabled { - border-color: #6B6B78; - color: #6B6B78; -} - -.c1:disabled svg { - fill: #6B6B78; -} - -
- -
-`; - -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` - -`; +.c0 svg { + fill: #D8385E; +} + +.c0:active { + background-color: rgba(255,255,255,0.75); + color: rgba(216,56,94,0.75); +} + +.c0:active svg { + fill: rgba(216,56,94,0.75); +} + +.c0:not([disabled]):hover, +.c0:not([disabled]):focus { + box-shadow: 0 4px 8px rgba(255,255,255,0.45); +} -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` + +`; + +exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` + +`; + +exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` - -`; +.c1 { + background-color: transparent; + border-color: transparent; + color: #231B22; +} -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` `; -exports[` diff --git a/packages/yoga/src/Dialog/web/__snapshots__/Dialog.test.jsx.snap b/packages/yoga/src/Dialog/web/__snapshots__/Dialog.test.jsx.snap index c335856618..5972dfd740 100644 --- a/packages/yoga/src/Dialog/web/__snapshots__/Dialog.test.jsx.snap +++ b/packages/yoga/src/Dialog/web/__snapshots__/Dialog.test.jsx.snap @@ -449,6 +449,7 @@ exports[` should match snapshot with close button 1`] = ` aria-hidden="true" class="c5" height="24" + role="img" width="24" /> diff --git a/packages/yoga/src/Heading/web/__snapshots__/Heading.test.jsx.snap b/packages/yoga/src/Heading/web/__snapshots__/Heading.test.jsx.snap index 725291bb39..cb8a314018 100644 --- a/packages/yoga/src/Heading/web/__snapshots__/Heading.test.jsx.snap +++ b/packages/yoga/src/Heading/web/__snapshots__/Heading.test.jsx.snap @@ -169,6 +169,7 @@ exports[` should match snapshot no padding 1`] = ` aria-hidden="true" class="c3" height="24" + role="img" width="24" /> @@ -193,6 +194,7 @@ exports[` should match snapshot no padding 1`] = ` aria-hidden="true" class="c3" height="24" + role="img" width="24" /> @@ -375,6 +377,7 @@ exports[` should match snapshot with all components 1`] = ` aria-hidden="true" class="c3" height="24" + role="img" width="24" /> @@ -399,6 +402,7 @@ exports[` should match snapshot with all components 1`] = ` aria-hidden="true" class="c3" height="24" + role="img" width="24" /> @@ -566,6 +570,7 @@ exports[` should match snapshot with back button 1`] = ` aria-hidden="true" class="c3" height="24" + role="img" width="24" /> @@ -840,6 +845,7 @@ exports[` should match snapshot with title and back button 1`] = ` aria-hidden="true" class="c3" height="24" + role="img" width="24" /> diff --git a/packages/yoga/src/Menu/web/__snapshots__/Menu.test.jsx.snap b/packages/yoga/src/Menu/web/__snapshots__/Menu.test.jsx.snap index 21a8f5c92a..b915fd460d 100644 --- a/packages/yoga/src/Menu/web/__snapshots__/Menu.test.jsx.snap +++ b/packages/yoga/src/Menu/web/__snapshots__/Menu.test.jsx.snap @@ -91,6 +91,7 @@ exports[` should match snapshot Menu with a onMouseHover props false 1`] aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -189,6 +190,7 @@ exports[` should match snapshot Menu with an align props end 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -288,6 +290,7 @@ exports[` should match snapshot Menu with an align props start 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -387,6 +390,7 @@ exports[` should match snapshot Menu.Item with active 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -486,6 +490,7 @@ exports[` should match snapshot Menu.Item with disabled 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -585,6 +590,7 @@ exports[` should match snapshot Menu.Item with disabled and icon 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -684,6 +690,7 @@ exports[` should match snapshot Menu.Item with icon 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -783,6 +790,7 @@ exports[` should match snapshot Menu.Item with link 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> @@ -882,6 +890,7 @@ exports[` should match snapshot in default Menu 1`] = ` aria-hidden="true" class="c1" height="24" + role="img" width="24" /> diff --git a/packages/yoga/src/Spinner/web/Spinner.jsx b/packages/yoga/src/Spinner/web/Spinner.jsx index 47c97e26e8..957d8a7002 100644 --- a/packages/yoga/src/Spinner/web/Spinner.jsx +++ b/packages/yoga/src/Spinner/web/Spinner.jsx @@ -12,13 +12,11 @@ const StyledSpinner = styled.span` color: ${color}; animation: 1.4s linear 0s infinite normal none running rotation; } - .circular { display: block; height: 100%; width: 100%; } - .path { stroke-dasharray: 80px, 200px; stroke-dashoffset: 0; @@ -27,7 +25,6 @@ const StyledSpinner = styled.span` stroke-linecap: round; stroke: ${color}; } - @keyframes rotation { 0% { transform: rotate(0deg); @@ -36,7 +33,6 @@ const StyledSpinner = styled.span` transform: rotate(360deg); } } - @keyframes dash { 0% { stroke-dasharray: 1px, 200px; @@ -60,6 +56,7 @@ const Spinner = React.forwardRef(({ size, color, theme }, ref) => { color={get(theme.yoga.colors, color, color)} size={get(theme.yoga.spacing, size, size)} ref={ref} + aria-label="loading-icon" > diff --git a/packages/yoga/src/Spinner/web/__snapshots__/Spinner.test.jsx.snap b/packages/yoga/src/Spinner/web/__snapshots__/Spinner.test.jsx.snap index a3a127edaf..a4712da4cf 100644 --- a/packages/yoga/src/Spinner/web/__snapshots__/Spinner.test.jsx.snap +++ b/packages/yoga/src/Spinner/web/__snapshots__/Spinner.test.jsx.snap @@ -28,6 +28,7 @@ exports[` should match snapshot 1`] = `