Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 0 additions & 15 deletions src/components/Backdrop/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,20 @@ import styles from './styles.css';

export interface BackdropProps {
className?: string;
parentRef?: React.RefObject<HTMLElement>;
children?: React.ReactNode;
uiMode?: UiMode;
}

function Backdrop(props: BackdropProps) {
const {
className,
parentRef,
children,
uiMode,
} = props;

const ref = React.useRef<HTMLDivElement>(null);
const themeClassName = useThemeClassName(uiMode, styles.light, styles.dark);

React.useLayoutEffect(
() => {
const {
current: el,
} = ref;
if (parentRef && parentRef.current && el) {
const parentBCR = parentRef.current.getBoundingClientRect();
el.style.width = `${parentBCR.width}px`;
}
},
[parentRef],
);

return (
<div
ref={ref}
Expand Down
47 changes: 47 additions & 0 deletions src/components/Loading/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { _cs } from '@togglecorp/fujs';

import ThemeContext, { UiMode } from '../ThemeContext';

import Backdrop from '../Backdrop';
import LoadingAnimation from '../LoadingAnimation';

import styles from './styles.css';

export interface LoadingProps {
className?: string;
uiMode?: UiMode;
compact?: boolean;
}

const reverseUiMode: {
[key in UiMode]: UiMode;
} = {
light: 'dark',
dark: 'light',
};

function Loading(props: LoadingProps) {
const { uiMode: defaultUiMode } = React.useContext(ThemeContext);
const {
className,
uiMode = defaultUiMode,
compact,
} = props;

// const themeClassName = useThemeClassName(uiMode, styles.light, styles.dark);

return (
<Backdrop
uiMode={reverseUiMode[uiMode]}
className={_cs(className, styles.loading)}
>
<LoadingAnimation
uiMode={uiMode}
compact={compact}
/>
</Backdrop>
);
}

export default Loading;
5 changes: 5 additions & 0 deletions src/components/Loading/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.loading {
display: flex;
align-items: center;
justify-content: center;
}
41 changes: 41 additions & 0 deletions src/components/LoadingAnimation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { _cs } from '@togglecorp/fujs';

import { UiMode } from '../ThemeContext';
import { useThemeClassName } from '../../hooks';

import styles from './styles.css';

export interface LoadingAnimationProps {
className?: string;
uiMode?: UiMode;
compact?: boolean;
}

function LoadingAnimation(props: LoadingAnimationProps) {
const {
className,
uiMode,
compact,
} = props;

const themeClassName = useThemeClassName(uiMode, styles.light, styles.dark);

return (
<div className={
_cs(
className,
themeClassName,
styles.loadingAnimation,
compact && styles.compact,
)
}
>
<div className={styles.particle} />
<div className={styles.particle} />
<div className={styles.particle} />
</div>
);
}

export default LoadingAnimation;
54 changes: 54 additions & 0 deletions src/components/LoadingAnimation/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.loading-animation {
--size: 0.4em;
--half-size: 0.2em;
--gap: 0.1em;
--color: transparent;
--transition-duration: 1s;

display: flex;
align-items: center;

&.compact {
--size: 0.24em;
--half-size: 0.12em;
--gap: 0.06em;
}

&.light {
--color: var(--tui-color-loading-particle-light);
}

&.dark {
--color: var(--tui-color-loading-particle-dark);
}

.particle {
flex-shrink: 0;
margin: var(--gap);
border-radius: 50%;
background-color: var(--color);
width: var(--size);
height: var(--size);
animation: wiggle-vertically var(--transition-duration) linear infinite;

&:nth-child(1) {
animation-delay: 0;
}

&:nth-child(2) {
animation-delay: calc(var(--transition-duration) / 3);
}

&:nth-child(3) {
animation-delay: calc(2 * var(--transition-duration) / 3);
}
}
}

@keyframes wiggle-vertically {
0% { transform: translateY(0); }
25% { transform: translateY(calc(-1 * var(--half-size))); }
75% { transform: translateY(calc(-1 * var(--half-size))); }
100% { transform: translateY(0); }
}

11 changes: 11 additions & 0 deletions src/components/RawButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { _cs } from '@togglecorp/fujs';
import { UiMode } from '../ThemeContext';
import { useThemeClassName } from '../../hooks';
import VisualFeedback from '../VisualFeedback';
import Loading from '../Loading';

import styles from './styles.css';

Expand All @@ -23,6 +24,10 @@ export interface RawButtonProps<N extends number | string | undefined> extends O
uiMode?: UiMode;
name: N;
elementRef?: React.Ref<HTMLButtonElement>;
/**
* Shows the loading animation
*/
pending?: boolean;
}

/**
Expand All @@ -37,6 +42,7 @@ function RawButton<N extends number | string | undefined>(props: RawButtonProps<
disabled,
elementRef,
name,
pending,
...otherProps
} = props;

Expand All @@ -61,6 +67,11 @@ function RawButton<N extends number | string | undefined>(props: RawButtonProps<
// eslint-disable-next-line react/jsx-props-no-spreading
{...otherProps}
>
{pending && (
<Loading
uiMode={uiMode}
/>
)}
<VisualFeedback
disabled={disabled}
uiMode={uiMode}
Expand Down
6 changes: 6 additions & 0 deletions src/components/RawButton/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@
outline-color: var(--tui-color-outline-dark);
}
}

&.loading-container {
position: absolute;
top: 0;
left: 0;
}
}
2 changes: 2 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type { DateTimeInputProps } from './components/DateTimeInput';
export { default as DateTimeInput } from './components/DateTimeInput';
export type { ListProps } from './components/List';
export { default as List } from './components/List';
export type { LoadingAnimationProps } from './components/LoadingAnimation';
export { default as LoadingAnimation } from './components/LoadingAnimation';
export type { ModalProps } from './components/Modal';
export { default as Modal } from './components/Modal';
export type { MultiSelectInputProps } from './components/MultiSelectInput';
Expand Down
22 changes: 22 additions & 0 deletions src/stories/LoadingAnimation.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Story } from '@storybook/react/types-6-0';
import LoadingAnimation, { LoadingAnimationProps } from '#components/LoadingAnimation';

export default {
title: 'View/LoadingAnimation',
component: LoadingAnimation,
argTypes: {},
};

const Template: Story<LoadingAnimationProps> = (props) => (
<LoadingAnimation {...props} />
);

export const Default = Template.bind({});
Default.args = {
};

export const Compact = Template.bind({});
Compact.args = {
compact: true,
};
8 changes: 6 additions & 2 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@

--tui-color-shadow: rgba(0, 0, 0, .5);

--tui-color-backdrop-light: rgba(0, 0, 0, .5);
--tui-color-backdrop-dark: rgba(255, 255, 255, .5);
--tui-color-backdrop-light: rgba(0, 0, 0, .6);
--tui-color-backdrop-dark: rgba(255, 255, 255, .7);

--tui-color-background-switch-icon-light: rgba(0, 0, 0, .4);
--tui-color-background-switch-icon-dark: rgba(255, 255, 255, .4);
Expand All @@ -129,4 +129,8 @@
--tui-height-max-modal: 96vh;
--tui-width-max-modal: 96vw;
--tui-width-min-modal: 360px;


--tui-color-loading-particle-light: rgba(0, 0, 0, 1);
--tui-color-loading-particle-dark: rgba(255, 255, 255, 1);
}