Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add loading state in button and tabs #326

Merged
merged 6 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/components/Button/BaseButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Icon, IconSize } from '../Icon';
import { Badge } from '../Badge';
import { Breakpoints, useMatchMedia } from '../../hooks/useMatchMedia';
import { mergeClasses } from '../../shared/utilities';
import { Loader } from '../Loader';

import styles from './button.module.scss';

Expand Down Expand Up @@ -46,6 +47,7 @@ export const BaseButton: FC<InternalButtonProps> = React.forwardRef(
theme,
toggle,
type,
loading = false,
...rest
},
ref: Ref<HTMLButtonElement>
Expand Down Expand Up @@ -105,6 +107,7 @@ export const BaseButton: FC<InternalButtonProps> = React.forwardRef(
iconExists && alignIcon === ButtonIconAlign.Right,
},
{ [styles.disabled]: allowDisabledFocus || disabled },
{ [styles.loading]: loading },
]);

const buttonTextClassNames: string = mergeClasses([
Expand Down Expand Up @@ -155,6 +158,9 @@ export const BaseButton: FC<InternalButtonProps> = React.forwardRef(
return iconSize;
};

const getButtonLoader = (): JSX.Element =>
loading && <Loader classNames={styles.loader} />;

const getButtonIcon = (): JSX.Element => (
<Icon
{...iconProps}
Expand All @@ -169,9 +175,10 @@ export const BaseButton: FC<InternalButtonProps> = React.forwardRef(
): JSX.Element => (
<span className={buttonTextClassNames}>
{text ? text : 'Button'}
{counterExists && (
{counterExists && !loading && (
<Badge classNames={badgeClassNames}>{counter}</Badge>
)}
{getButtonLoader()}
</span>
);

Expand All @@ -193,9 +200,10 @@ export const BaseButton: FC<InternalButtonProps> = React.forwardRef(
type={htmlType}
>
{iconExists && !textExists && getButtonIcon()}
{counterExists && !textExists && (
{counterExists && !textExists && !loading && (
<Badge classNames={badgeClassNames}>{counter}</Badge>
)}
{counterExists && !textExists && getButtonLoader()}
{iconExists && textExists && (
<span>
{getButtonIcon()}
Expand Down
1 change: 1 addition & 0 deletions src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ const buttonArgs: Object = {
theme: ButtonTheme.light,
toggle: false,
counter: 0,
loading: false,
};

Primary.args = {
Expand Down
4 changes: 4 additions & 0 deletions src/components/Button/Button.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,8 @@ export interface ButtonProps extends NativeButtonProps {
* @default false
*/
transparent?: boolean;
/**
* If the button is in loading state
*/
loading?: boolean;
}
9 changes: 9 additions & 0 deletions src/components/Button/button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@
cursor: not-allowed;
}

&.loading {
pointer-events: none;
cursor: not-allowed;
}

&:hover {
.counter {
background-color: var(--button-counter-hover-background-color);
Expand All @@ -178,6 +183,10 @@
&:focus-visible {
outline: none;
}

.loader {
margin-left: $space-xs;
}
}

.button-text-large {
Expand Down
40 changes: 40 additions & 0 deletions src/components/Loader/Loader.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { Stories } from '@storybook/addon-docs';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Loader, LoaderSize } from './';

export default {
title: 'Loader',
parameters: {
docs: {
page: (): JSX.Element => (
<main>
<article>
<section>
<h1>Loader</h1>
</section>
<section>
<Stories includePrimary title="" />
</section>
</article>
</main>
),
},
},
argTypes: {
size: {
options: [LoaderSize.Large, LoaderSize.Medium, LoaderSize.Small],
control: { type: 'select' },
},
},
} as ComponentMeta<typeof Loader>;

const Loader_Story: ComponentStory<typeof Loader> = (args) => (
<Loader {...args} />
);

export const Default = Loader_Story.bind({});

Default.args = {
classNames: 'my-loader-class',
};
27 changes: 27 additions & 0 deletions src/components/Loader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { FC } from 'react';
import { LoaderProps, LoaderSize } from './Loader.types';
import { Stack } from '../Stack';
import { mergeClasses } from '../../shared/utilities';

import styles from './loader.module.scss';

export const Loader: FC<LoaderProps> = ({
size = LoaderSize.Small,
...rest
}) => {
const dotClasses = mergeClasses([
styles.dot,
{
[styles.small]: size === LoaderSize.Small,
[styles.medium]: size === LoaderSize.Medium,
[styles.large]: size === LoaderSize.Large,
},
]);
return (
<Stack {...rest}>
<div className={dotClasses} />
<div className={dotClasses} />
<div className={dotClasses} />
</Stack>
);
};
11 changes: 11 additions & 0 deletions src/components/Loader/Loader.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { OcBaseProps } from '../OcBase';

export enum LoaderSize {
Large = 'large',
Medium = 'medium',
Small = 'small',
}

export interface LoaderProps extends OcBaseProps<HTMLDivElement> {
size?: LoaderSize;
}
2 changes: 2 additions & 0 deletions src/components/Loader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './Loader';
export * from './Loader.types';
107 changes: 107 additions & 0 deletions src/components/Loader/loader.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
.dot {
--translate: -2px;
background: var(--primary-color);
border-radius: 50%;
margin-right: $space-xxs;

&.small {
width: 4px;
height: 4px;
}

&.medium {
--translate: -3px;
width: 6px;
height: 6px;
}

&.large {
--translate: -4px;
width: 8px;
height: 8px;
}

&:nth-child(1) {
animation: 2s linear infinite both firstBall;
}

&:nth-child(2) {
animation: 2s linear infinite both secondBall;
}

&:nth-child(3) {
animation: 2s linear infinite both thirdBall;
}
}

@keyframes firstBall {
0% {
transform: translateY(0);
}
50% {
transform: translateY(0);
}
60% {
transform: translateY(var(--translate));
}
70% {
transform: translateY(0);
}
80% {
transform: translateY(0);
}
90% {
transform: translateY(0);
}
100% {
transform: translateY(0);
}
}

@keyframes secondBall {
0% {
transform: translateY(0);
}
50% {
transform: translateY(0);
}
60% {
transform: translateY(0);
}
70% {
transform: translateY(var(--translate));
}
80% {
transform: translateY(0);
}
90% {
transform: translateY(0);
}
100% {
transform: translateY(0);
}
}

@keyframes thirdBall {
0% {
transform: translateY(0);
}
50% {
transform: translateY(0);
}
60% {
transform: translateY(0);
}
70% {
transform: translateY(0);
}
80% {
transform: translateY(var(--translate));
}
90% {
transform: translateY(0);
}
100% {
transform: translateY(0);
}
}
5 changes: 3 additions & 2 deletions src/components/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const Wrapper: FC<SelectProps> = ({ children }) => {
);
};

const DynamicSelect: FC<SelectProps> = () => {
const DynamicSelect: FC<SelectProps> = (args) => {
const timer = useRef<ReturnType<typeof setTimeout>>(null);
const [options, setOptions] = useState([]);
const [isLoading, setIsLoading] = useState(false);
Expand All @@ -139,11 +139,12 @@ const DynamicSelect: FC<SelectProps> = () => {
};
return (
<Select
{...args}
filterable={true}
isLoading={isLoading}
loadOptions={loadOptions}
options={options}
></Select>
/>
);
};

Expand Down
9 changes: 8 additions & 1 deletion src/components/Tabs/Tab/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useConfig } from '../../ConfigProvider';
import { Badge } from '../../Badge';

import styles from '../tabs.module.scss';
import { Loader } from '../../Loader';

export const Tab: FC<TabProps> = React.forwardRef(
(
Expand All @@ -20,6 +21,7 @@ export const Tab: FC<TabProps> = React.forwardRef(
ariaLabel,
badgeContent,
classNames,
loading,
...rest
},
ref: Ref<HTMLButtonElement>
Expand Down Expand Up @@ -53,12 +55,16 @@ export const Tab: FC<TabProps> = React.forwardRef(
);

const getBadge = (): JSX.Element =>
!!badgeContent && (
!!badgeContent &&
!loading && (
<Badge active={isActive} classNames={styles.badge}>
{badgeContent}
</Badge>
);

const getLoader = (): JSX.Element =>
loading && <Loader classNames={styles.loader} />;

return (
<button
{...rest}
Expand All @@ -74,6 +80,7 @@ export const Tab: FC<TabProps> = React.forwardRef(
{getLabel()}
{getTabIndicator()}
{getBadge()}
{getLoader()}
</button>
);
}
Expand Down
22 changes: 22 additions & 0 deletions src/components/Tabs/Tabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,21 @@ const Default_Story: ComponentStory<typeof Tabs> = (args) => {

export const Default = Default_Story.bind({});

const Default_Loading_Story: ComponentStory<typeof Tabs> = (args) => {
const [activeTabs, setActiveTabs] = useState({ defaultTab: 'tab1' });
return (
<Tabs
{...args}
onChange={(tab) =>
setActiveTabs({ ...activeTabs, defaultTab: tab })
}
value={activeTabs.defaultTab}
/>
);
};

export const DefaultLoader = Default_Loading_Story.bind({});

const Small_Story: ComponentStory<typeof Tabs> = (args) => {
const [activeTabs, setActiveTabs] = useState({ defaultTab: 'tab1' });
return (
Expand Down Expand Up @@ -245,6 +260,13 @@ Default.args = {
...tabsArgs,
};

DefaultLoader.args = {
...tabsArgs,
children: badgeTabs.map((tab, index) => (
<Tab key={tab.value} {...tab} loading={index % 2 === 0} />
)),
};

Small.args = {
...tabsArgs,
variant: TabVariant.small,
Expand Down
Loading