Skip to content

Commit

Permalink
#371 Add loading state, medium size and slightly refresh the UI (#372)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
MrVorgoth authored Aug 9, 2022
1 parent 5aedb25 commit b55f00e
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 29 deletions.
4 changes: 2 additions & 2 deletions packages/icons/svg/material/lock_black.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const sizes = (): React.ReactElement => (
<div className="story-spacer">
<Loader size="small" />
<Loader size="medium" />
<Loader size="large"></Loader>
<Loader size="large" />
</div>
);

Expand Down
83 changes: 67 additions & 16 deletions packages/react-components/src/components/Switch/Switch.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,16 +35,21 @@ $compact-move: $compact-width - $compact-size;
}
}

&--basic {
height: $basic-height;
width: $basic-width;
&--large {
height: $large-height;
width: $large-width;
}

&--compact {
height: $compact-height;
width: $compact-width;
}

&--medium {
height: $medium-height;
width: $medium-width;
}

&__input {
cursor: pointer;
height: 100%;
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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;
}
}
}
31 changes: 31 additions & 0 deletions packages/react-components/src/components/Switch/Switch.spec.tsx
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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(<Switch disabled={true} />);
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(<Switch loading={true} />);
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(
<Switch loading={true} disabled={true} />
);
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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const States = (): JSX.Element => (
<Switch on={true} disabled={true} />
<Switch on={false} disabled={true} />
</StoryDescriptor>
<StoryDescriptor title="Loading">
<Switch on={true} loading={true} />
<Switch on={false} loading={true} />
</StoryDescriptor>
</>
);

Expand All @@ -33,8 +37,11 @@ export const Sizes = (): JSX.Element => (
<StoryDescriptor title="Compact">
<Switch size="compact" />
</StoryDescriptor>
<StoryDescriptor title="Basic">
<Switch size="basic" />
<StoryDescriptor title="Medium">
<Switch size="medium" />
</StoryDescriptor>
<StoryDescriptor title="Large">
<Switch size="large" />
</StoryDescriptor>
</>
);
34 changes: 26 additions & 8 deletions packages/react-components/src/components/Switch/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLInputElement> | undefined;
name?: string;
on?: boolean;
Expand All @@ -25,10 +27,11 @@ export const Switch: React.FC<SwitchProps> = ({
className = '',
defaultOn = false,
disabled = false,
loading = false,
name = baseClass,
on,
onChange = noop,
size = 'basic',
size = 'large',
innerRef,
...props
}) => {
Expand All @@ -42,14 +45,16 @@ export const Switch: React.FC<SwitchProps> = ({
}
}, [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;
Expand All @@ -73,7 +78,7 @@ export const Switch: React.FC<SwitchProps> = ({
checked={checked}
name={name}
ref={innerRef}
disabled={disabled}
disabled={shouldBehaveAsDisabled}
test-id="foo"
{...props}
/>
Expand All @@ -92,8 +97,21 @@ export const Switch: React.FC<SwitchProps> = ({
styles[`${baseClass}__slider--${size}--${toggleStyles}`]
)}
>
{disabled && (
<Icon size={iconSize} source={LockIcon} kind={'primary'} />
{loading && (
<Loader
className={cx(
styles[`${baseClass}__loader`],
styles[`${baseClass}__loader--${size}`]
)}
/>
)}
{shouldShowDisabledIcon && (
<Icon
data-testid="disabled-icon"
size={iconSize}
source={LockIcon}
kind="primary"
/>
)}
</span>
</span>
Expand Down

0 comments on commit b55f00e

Please sign in to comment.