Skip to content

Commit

Permalink
feat(experience): support loading state for buttons (#6232)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyijun authored Jul 12, 2024
1 parent d203c8d commit 6bf3beb
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 13 deletions.
3 changes: 3 additions & 0 deletions packages/experience/src/assets/icons/loading-ring.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
@@ -0,0 +1,14 @@
.icon {
animation: rotating 1s linear infinite;
}


@keyframes rotating {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Ring from '@/assets/icons/loading-ring.svg';

import * as RotatingRingIconStyles from './RotatingRingIcon.module.scss';

const RotatingRingIcon = () => <Ring className={RotatingRingIconStyles.icon} />;

export default RotatingRingIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
overflow: hidden;
}

.loadingIcon {
display: block;
// To avoid the layout shift, add padding manually (keep the same size as the icon)
padding: 0 1.5px;
color: var(--color-brand-70);
font-size: 0;
line-height: normal;
}

.name {
flex: 1;
overflow: hidden;
Expand Down
24 changes: 21 additions & 3 deletions packages/experience/src/components/Button/SocialLinkButton.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useDebouncedLoader } from 'use-debounced-loader';

import RotatingRingIcon from './RotatingRingIcon';
import * as socialLinkButtonStyles from './SocialLinkButton.module.scss';
import * as styles from './index.module.scss';

export type Props = {
readonly isDisabled?: boolean;
readonly isLoading?: boolean;
readonly className?: string;
readonly target: string;
readonly logo: string;
readonly name: Record<string, string>;
readonly onClick?: () => void;
};

const SocialLinkButton = ({ isDisabled, className, target, name, logo, onClick }: Props) => {
const SocialLinkButton = ({
isDisabled,
isLoading = false,
className,
target,
name,
logo,
onClick,
}: Props) => {
const {
t,
i18n: { language },
} = useTranslation();

const localName = name[language] ?? name.en;

const isLoadingActive = useDebouncedLoader(isLoading, 300);

return (
<button
disabled={isDisabled}
Expand All @@ -29,20 +42,25 @@ const SocialLinkButton = ({ isDisabled, className, target, name, logo, onClick }
styles.secondary,
styles.large,
socialLinkButtonStyles.socialButton,
isDisabled && styles.disabled,
(isDisabled ?? isLoadingActive) && styles.disabled,
className
)}
type="button"
onClick={onClick}
>
{logo && (
{logo && !isLoadingActive && (
<img
src={logo}
alt={target}
className={socialLinkButtonStyles.icon}
crossOrigin="anonymous"
/>
)}
{isLoadingActive && (
<span className={socialLinkButtonStyles.loadingIcon}>
<RotatingRingIcon />
</span>
)}
<div className={socialLinkButtonStyles.name}>
<div className={socialLinkButtonStyles.placeHolder} />
<span>{t('action.sign_in_with', { name: localName })}</span>
Expand Down
34 changes: 28 additions & 6 deletions packages/experience/src/components/Button/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,28 @@
user-select: none;
overflow: hidden;

.icon {
font-size: 0;
line-height: normal;
margin-right: _.unit(2);
.content {
position: relative;
transition: padding-left 0.2s ease;

.icon {
font-size: 0;
line-height: normal;
position: absolute;
pointer-events: none;
left: 0;
opacity: 0%;
transition: opacity 0.2s ease;
}

&.iconVisible {
padding-left: _.unit(7);

.icon {
left: 0;
opacity: 100%;
}
}
}
}

Expand All @@ -45,6 +63,10 @@
&:active {
background: var(--color-brand-pressed);
}

&.loading {
background-color: var(--color-brand-70);
}
}

.secondary {
Expand Down Expand Up @@ -74,7 +96,7 @@
outline: 3px solid var(--color-overlay-brand-focused);
}

&:not(:disabled):not(:active):hover {
&:not(:disabled):not(:active):not(.loading):hover {
background: var(--color-brand-hover);
}
}
Expand All @@ -84,7 +106,7 @@
outline: 3px solid var(--color-overlay-neutral-focused);
}

&:not(:disabled):not(:active):hover {
&:not(:disabled):not(:active):not(.loading):hover {
background: var(--color-overlay-neutral-hover);
}
}
Expand Down
22 changes: 18 additions & 4 deletions packages/experience/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import type { HTMLProps } from 'react';
import { type HTMLProps } from 'react';
import { useDebouncedLoader } from 'use-debounced-loader';

import DynamicT from '../DynamicT';

import RotatingRingIcon from './RotatingRingIcon';
import * as styles from './index.module.scss';

export type ButtonType = 'primary' | 'secondary';
Expand All @@ -13,6 +15,7 @@ type BaseProps = Omit<HTMLProps<HTMLButtonElement>, 'type' | 'size' | 'title'> &
readonly type?: ButtonType;
readonly size?: 'small' | 'large';
readonly isDisabled?: boolean;
readonly isLoading?: boolean;
readonly className?: string;
readonly onClick?: React.MouseEventHandler;
};
Expand All @@ -31,26 +34,37 @@ const Button = ({
i18nProps,
className,
isDisabled = false,
isLoading = false,
icon,
onClick,
...rest
}: Props) => {
const isLoadingActive = useDebouncedLoader(isLoading, 300);

return (
<button
disabled={isDisabled}
className={classNames(
styles.button,
styles[type],
styles[size],
isDisabled && styles.isDisabled,
isDisabled && styles.disabled,
isLoadingActive && styles.loading,
className
)}
type={htmlType}
onClick={onClick}
{...rest}
>
{icon && <span className={styles.icon}>{icon}</span>}
<DynamicT forKey={title} interpolation={i18nProps} />
<span
className={classNames(
styles.content,
(isLoadingActive || Boolean(icon)) && styles.iconVisible
)}
>
<span className={styles.icon}>{isLoadingActive ? <RotatingRingIcon /> : icon}</span>
<DynamicT forKey={title} interpolation={i18nProps} />
</span>
</button>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/experience/src/scss/_colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
--color-brand-30: #4300da;
--color-brand-40: #5d34f2;
--color-brand-50: #7958ff;
--color-brand-70: #af9eff;

--color-alert-60: #ca8000;
--color-alert-70: #eb9918;
Expand Down

0 comments on commit 6bf3beb

Please sign in to comment.