diff --git a/.changeset/sweet-knives-divide.md b/.changeset/sweet-knives-divide.md new file mode 100644 index 0000000000..cb36173d73 --- /dev/null +++ b/.changeset/sweet-knives-divide.md @@ -0,0 +1,5 @@ +--- +"@channel.io/bezier-react": patch +--- + +Add `AlphaSpinner` component diff --git a/packages/bezier-react/src/components/AlphaSpinner/AlphaSpinner.stories.tsx b/packages/bezier-react/src/components/AlphaSpinner/AlphaSpinner.stories.tsx new file mode 100644 index 0000000000..67479e56ba --- /dev/null +++ b/packages/bezier-react/src/components/AlphaSpinner/AlphaSpinner.stories.tsx @@ -0,0 +1,23 @@ +import React from 'react' + +import { type Meta, type StoryFn } from '@storybook/react' + +import { Spinner } from './Spinner' +import { type SpinnerProps } from './Spinner.types' + +const meta: Meta = { + component: Spinner, +} + +export default meta + +const Template: StoryFn = ({ ...args }) => + +export const Primary = { + render: Template, + + args: { + size: 'm', + variant: 'secondary', + }, +} diff --git a/packages/bezier-react/src/components/AlphaSpinner/Spinner.module.scss b/packages/bezier-react/src/components/AlphaSpinner/Spinner.module.scss new file mode 100644 index 0000000000..77b004b1cd --- /dev/null +++ b/packages/bezier-react/src/components/AlphaSpinner/Spinner.module.scss @@ -0,0 +1,66 @@ +@use '../../styles/mixins/dimension'; + +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.Spinner { + --b-spinner-size: initial; + --b-spinner-track-color: initial; + --b-spinner-indicator-color: initial; + --b-spinner-stroke-width: initial; + --b-spinner-stroke-dasharray: initial; + + @include dimension.square(var(--b-spinner-size)); + + display: inline-flex; + animation: rotate 1s linear infinite; + + & .track { + fill: none; + stroke: var(--b-spinner-track-color); + stroke-linecap: round; + stroke-width: var(--b-spinner-stroke-width); + } + + & .indicator { + fill: none; + stroke: var(--b-spinner-indicator-color); + stroke-dasharray: var(--b-spinner-stroke-dasharray); + stroke-linecap: round; + stroke-width: var(--b-spinner-stroke-width); + } + + &:where(.size-s) { + --b-spinner-size: 28px; + --b-spinner-stroke-width: 4px; + --b-spinner-stroke-dasharray: 40 9999; + } + + &:where(.size-m) { + --b-spinner-size: 50px; + --b-spinner-stroke-width: 6px; + --b-spinner-stroke-dasharray: 60 9999; + } + + &:where(.variant-primary) { + --b-spinner-track-color: var(--alpha-color-primary-bg-lightest); + --b-spinner-indicator-color: var(--alpha-color-fg-blue-normal); + } + + &:where(.variant-secondary) { + --b-spinner-track-color: var(--alpha-color-bg-black-light); + --b-spinner-indicator-color: var(--alpha-color-fg-black-light); + } + + &:where(.variant-on-overlay) { + --b-spinner-track-color: var(--alpha-color-bg-absolute-white-lightest); + --b-spinner-indicator-color: var(--alpha-color-fg-absolute-white-light); + } +} diff --git a/packages/bezier-react/src/components/AlphaSpinner/Spinner.test.tsx b/packages/bezier-react/src/components/AlphaSpinner/Spinner.test.tsx new file mode 100644 index 0000000000..96ec2697ea --- /dev/null +++ b/packages/bezier-react/src/components/AlphaSpinner/Spinner.test.tsx @@ -0,0 +1,34 @@ +import React from 'react' + +import { render } from '~/src/utils/test' + +import { SPINNER_TEST_ID, Spinner } from './Spinner' + +describe('Spinner >', () => { + const renderSpinner = (props?: React.ComponentProps) => + render() + + it('should render', () => { + const { getByTestId } = renderSpinner() + const renderedSpinner = getByTestId(SPINNER_TEST_ID) + expect(renderedSpinner).toBeInTheDocument() + }) + + it('should render as a span element by default', () => { + const { getByTestId } = renderSpinner() + const renderedSpinner = getByTestId(SPINNER_TEST_ID) + expect(renderedSpinner.tagName).toBe('SPAN') + }) + + it('should forward ref', () => { + const ref = React.createRef() + renderSpinner({ ref }) + expect(ref.current).toBeInTheDocument() + }) + + it('should receive size', () => { + const { getByTestId } = renderSpinner({ size: 'm' }) + const renderedSpinner = getByTestId(SPINNER_TEST_ID) + expect(renderedSpinner).toHaveClass('size-m') + }) +}) diff --git a/packages/bezier-react/src/components/AlphaSpinner/Spinner.tsx b/packages/bezier-react/src/components/AlphaSpinner/Spinner.tsx new file mode 100644 index 0000000000..1aaa6c1c82 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaSpinner/Spinner.tsx @@ -0,0 +1,50 @@ +import React, { forwardRef } from 'react' + +import classNames from 'classnames' + +import { type SpinnerProps } from './Spinner.types' + +import styles from './Spinner.module.scss' + +export const SPINNER_TEST_ID = 'bezier-spinner' + +export const Spinner = forwardRef( + function Spinner( + { className, size, variant = 'secondary', ...rest }, + forwardedRef + ) { + return ( + + + + + + + + ) + } +) diff --git a/packages/bezier-react/src/components/AlphaSpinner/Spinner.types.ts b/packages/bezier-react/src/components/AlphaSpinner/Spinner.types.ts new file mode 100644 index 0000000000..368364e3f4 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaSpinner/Spinner.types.ts @@ -0,0 +1,21 @@ +import { + type BezierComponentProps, + type ColorProps, + type SizeProps, +} from '~/src/types/props' + +type SpinnerSize = 's' | 'm' + +interface SpinnerOwnProps { + /** + * The style variant of Spinner. + * @default 'secondary' + */ + variant?: 'primary' | 'secondary' | 'on-overlay' +} + +export interface SpinnerProps + extends Omit, keyof ColorProps>, + SizeProps, + ColorProps, + SpinnerOwnProps {} diff --git a/packages/bezier-react/src/components/AlphaSpinner/index.ts b/packages/bezier-react/src/components/AlphaSpinner/index.ts new file mode 100644 index 0000000000..a45a7b9962 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaSpinner/index.ts @@ -0,0 +1,2 @@ +export { Spinner as AlphaSpinner } from './Spinner' +export { type SpinnerProps as AlphaSpinnerProps } from './Spinner.types' diff --git a/packages/bezier-react/src/index.ts b/packages/bezier-react/src/index.ts index 70ff6b8b4d..acc1807223 100644 --- a/packages/bezier-react/src/index.ts +++ b/packages/bezier-react/src/index.ts @@ -12,6 +12,7 @@ export * from '~/src/components/AlphaDialogPrimitive' export * from '~/src/components/AlphaFloatingButton' export * from '~/src/components/AlphaFloatingIconButton' export * from '~/src/components/AlphaIconButton' +export * from '~/src/components/AlphaSpinner' export * from '~/src/components/AlphaTooltipPrimitive' export * from '~/src/components/AppProvider' export * from '~/src/components/AutoFocus'