From b55f00e26ecd419a422a6066f80bf7df5f2d183e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kr=C3=B3lak?= Date: Tue, 9 Aug 2022 10:57:58 +0200 Subject: [PATCH] #371 Add loading state, medium size and slightly refresh the UI (#372) * Added new icon states to the `Switch` component: * `loading` state * `on` state * `off` state * The loading state is always on top when passed with other props (like disabled, which also adds an icon). The loading state also needs to have a disabled state, because we can't perform any action during loading. * New `medium` size available in `Switch` component. I've also renamed the size: * `basic` -> `large` * So to sum up - the 3 sizes are now: `compact` / `medium` / `large` * Changed the SVG for `lock_black` --- packages/icons/svg/material/lock_black.svg | 4 +- .../src/components/Loader/Loader.stories.tsx | 2 +- .../src/components/Switch/Switch.module.scss | 83 +++++++++++++++---- .../src/components/Switch/Switch.spec.tsx | 31 +++++++ .../src/components/Switch/Switch.stories.tsx | 11 ++- .../src/components/Switch/Switch.tsx | 34 ++++++-- 6 files changed, 136 insertions(+), 29 deletions(-) diff --git a/packages/icons/svg/material/lock_black.svg b/packages/icons/svg/material/lock_black.svg index 06e6d9665..8ca449d42 100644 --- a/packages/icons/svg/material/lock_black.svg +++ b/packages/icons/svg/material/lock_black.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/react-components/src/components/Loader/Loader.stories.tsx b/packages/react-components/src/components/Loader/Loader.stories.tsx index a711c43c7..c55cc0085 100644 --- a/packages/react-components/src/components/Loader/Loader.stories.tsx +++ b/packages/react-components/src/components/Loader/Loader.stories.tsx @@ -12,7 +12,7 @@ export const sizes = (): React.ReactElement => (
- +
); diff --git a/packages/react-components/src/components/Switch/Switch.module.scss b/packages/react-components/src/components/Switch/Switch.module.scss index c4f411299..3c949e132 100644 --- a/packages/react-components/src/components/Switch/Switch.module.scss +++ b/packages/react-components/src/components/Switch/Switch.module.scss @@ -2,16 +2,21 @@ $switch-transition-duration: 300ms; $switch-transition-timing-function: cubic-bezier(0.33, 0, 0.67, 1); $base-class: 'switch'; -$basic-height: 28px; -$basic-width: 44px; -$basic-size: $basic-height - 4px; -$basic-move: $basic-width - $basic-size; +$large-height: 24px; +$large-width: 44px; +$large-size: $large-height - 4px; +$large-move: $large-width - $large-size; $compact-height: 16px; -$compact-width: 25px; -$compact-size: $compact-height - 2px; +$compact-width: 32px; +$compact-size: $compact-height - 4px; $compact-move: $compact-width - $compact-size; +$medium-height: 20px; +$medium-width: 36px; +$medium-size: $medium-height - 4px; +$medium-move: $medium-width - $medium-size; + /* stylelint-disable max-nesting-depth */ .#{$base-class} { display: inline-block; @@ -30,9 +35,9 @@ $compact-move: $compact-width - $compact-size; } } - &--basic { - height: $basic-height; - width: $basic-width; + &--large { + height: $large-height; + width: $large-width; } &--compact { @@ -40,6 +45,11 @@ $compact-move: $compact-width - $compact-size; width: $compact-width; } + &--medium { + height: $medium-height; + width: $medium-width; + } + &__input { cursor: pointer; height: 100%; @@ -107,18 +117,18 @@ $compact-move: $compact-width - $compact-size; transition-property: left, right; transition-timing-function: $switch-transition-timing-function; - &--basic { - height: calc(#{$basic-size}); - width: calc(#{$basic-size}); + &--large { + height: calc(#{$large-size}); + width: calc(#{$large-size}); &--on { - left: calc(#{$basic-move} - 2px); - right: 2px; + left: calc(#{$large-move} - 3px); + right: 3px; } &--off { - left: 2px; - right: calc(#{$basic-move} - 2px); + left: 3px; + right: calc(#{$large-move} - 3px); } } @@ -136,5 +146,46 @@ $compact-move: $compact-width - $compact-size; right: calc(#{$compact-move} - 2px); } } + + &--medium { + height: calc(#{$medium-size}); + width: calc(#{$medium-size}); + + &--on { + left: calc(#{$medium-move} - 3px); + right: 3px; + } + + &--off { + left: 3px; + right: calc(#{$medium-move} - 3px); + } + } + } + + &__loader { + height: 100%; + width: 100%; + + &--compact { + padding: 1.5px; + } + + &--medium { + padding: 2px; + } + + &--large { + padding: 3px; + } + + > div { + height: 100%; + width: 100%; + } + + div:last-child { + border-width: 2px; + } } } diff --git a/packages/react-components/src/components/Switch/Switch.spec.tsx b/packages/react-components/src/components/Switch/Switch.spec.tsx index b7ed29f6b..b9fbb1f19 100644 --- a/packages/react-components/src/components/Switch/Switch.spec.tsx +++ b/packages/react-components/src/components/Switch/Switch.spec.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { render, fireEvent, vi } from 'test-utils'; import { Switch } from './Switch'; +import loaderStyles from '../Loader/Loader.module.scss'; describe('Switch', () => { it('should call onChange without changing state when custom handler is passed', () => { @@ -36,4 +37,34 @@ describe('Switch', () => { fireEvent.click(checkbox); expect(checkbox.checked).toEqual(false); }); + + it('should be disabled if disabled is set to true', () => { + const { getByRole, queryByTestId } = render(); + const checkbox = getByRole('checkbox') as HTMLInputElement; + expect(checkbox.disabled).toEqual(true); + expect(queryByTestId('disabled-icon')).toBeVisible(); + }); + + it('should have loader icon visible if loading is set to true', () => { + const { container, getByRole } = render(); + const checkbox = getByRole('checkbox') as HTMLInputElement; + const loader = container.querySelector( + `.${loaderStyles['loader__spinner']}` + ); + expect(checkbox.disabled).toEqual(true); + expect(loader).toBeVisible(); + }); + + it('should always show loader icon on top if there are other icons to be displayed', () => { + const { container, getByRole, queryByTestId } = render( + + ); + const checkbox = getByRole('checkbox') as HTMLInputElement; + const loader = container.querySelector( + `.${loaderStyles['loader__spinner']}` + ); + expect(loader).toBeVisible(); + expect(checkbox.disabled).toEqual(true); + expect(queryByTestId('disabled-icon')).not.toBeInTheDocument(); + }); }); diff --git a/packages/react-components/src/components/Switch/Switch.stories.tsx b/packages/react-components/src/components/Switch/Switch.stories.tsx index a9161a5ca..39f3fee29 100644 --- a/packages/react-components/src/components/Switch/Switch.stories.tsx +++ b/packages/react-components/src/components/Switch/Switch.stories.tsx @@ -25,6 +25,10 @@ export const States = (): JSX.Element => ( + + + + ); @@ -33,8 +37,11 @@ export const Sizes = (): JSX.Element => ( - - + + + + + ); diff --git a/packages/react-components/src/components/Switch/Switch.tsx b/packages/react-components/src/components/Switch/Switch.tsx index d9ba8d8a1..0729d94b2 100644 --- a/packages/react-components/src/components/Switch/Switch.tsx +++ b/packages/react-components/src/components/Switch/Switch.tsx @@ -6,14 +6,16 @@ import { Icon, IconSize } from '../../components/Icon'; import noop from '../../utils/noop'; import styles from './Switch.module.scss'; +import { Loader } from '../Loader'; -const baseClass = 'switch'; +export const baseClass = 'switch'; -export type SwitchSize = 'basic' | 'compact'; +export type SwitchSize = 'compact' | 'medium' | 'large'; export interface SwitchProps { className?: string; defaultOn?: boolean; disabled?: boolean; + loading?: boolean; innerRef?: React.LegacyRef | undefined; name?: string; on?: boolean; @@ -25,10 +27,11 @@ export const Switch: React.FC = ({ className = '', defaultOn = false, disabled = false, + loading = false, name = baseClass, on, onChange = noop, - size = 'basic', + size = 'large', innerRef, ...props }) => { @@ -42,14 +45,16 @@ export const Switch: React.FC = ({ } }, [on]); - const iconSize: IconSize = size === 'basic' ? 'small' : 'xsmall'; + const iconSize: IconSize = size === 'large' ? 'small' : 'xsmall'; const toggleStyles = checked ? 'on' : 'off'; - const availabilityStyles = disabled ? 'disabled' : 'enabled'; + const shouldBehaveAsDisabled = disabled || loading; + const availabilityStyles = shouldBehaveAsDisabled ? 'disabled' : 'enabled'; const mergedClassNames = cx( styles[baseClass], styles[`${baseClass}--${size}`], className ); + const shouldShowDisabledIcon = disabled && !loading; const handleChange = (e: React.FormEvent) => { const hasOnChangePassed = onChange !== noop; @@ -73,7 +78,7 @@ export const Switch: React.FC = ({ checked={checked} name={name} ref={innerRef} - disabled={disabled} + disabled={shouldBehaveAsDisabled} test-id="foo" {...props} /> @@ -92,8 +97,21 @@ export const Switch: React.FC = ({ styles[`${baseClass}__slider--${size}--${toggleStyles}`] )} > - {disabled && ( - + {loading && ( + + )} + {shouldShowDisabledIcon && ( + )}