diff --git a/README.md b/README.md
index 14f1418..92288d1 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,24 @@
-# 🌊 Awesome React Component Library 💦
+# 🐳 Awesome React Component Library 🐳
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
## `Installing WAP-UI`
diff --git a/package.json b/package.json
index 0c58a6f..5fa778b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wap-ui",
- "version": "1.1.8",
+ "version": "1.1.9",
"repository": "https://github.com/pknu-wap/2022_2_WAP_WEB_TEAM1.git",
"author": "neko113 ",
"license": "MIT",
diff --git a/packages/components/Loader/Loader.stories.tsx b/packages/components/Loader/Loader.stories.tsx
new file mode 100644
index 0000000..b42b91b
--- /dev/null
+++ b/packages/components/Loader/Loader.stories.tsx
@@ -0,0 +1,76 @@
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+import { Loader, LoaderProps } from './Loader';
+import React from 'react';
+import styled from '@emotion/styled';
+import { BarsProps } from './Loaders/Bars/Bars';
+import { DotsProps } from './Loaders/Dots/Dots';
+
+export default {
+ title: 'Components/Loader',
+ component: Loader,
+} as ComponentMeta;
+
+const Template: ComponentStory = (args: LoaderProps) => {
+ return (
+
+
+
+ );
+};
+
+export const Default = Template.bind({});
+
+export const Spinner = ({ size }: Pick) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const Bars = ({ size }: Pick) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const Dots = ({ size }: Pick) => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 3rem;
+ height: 100vh;
+ padding: 3rem;
+`;
+
+const FlexRow = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+`;
diff --git a/packages/components/Loader/Loader.tsx b/packages/components/Loader/Loader.tsx
new file mode 100644
index 0000000..f4b8ef3
--- /dev/null
+++ b/packages/components/Loader/Loader.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { NormalColorType } from '../../theme/types';
+import { Bars } from './Loaders/Bars/Bars';
+import { Dots } from './Loaders/Dots/Dots';
+import { Spinner } from './Loaders/Spinner/Spinner';
+
+export interface LoaderProps {
+ /**
+ * @default 'md'
+ */
+ size?: 'sm' | 'md' | 'lg';
+ /**
+ * @default 'primary'
+ */
+ color?: NormalColorType;
+ /**
+ * @default 'spinner'
+ */
+ type?: 'spinner' | 'bars' | 'dots';
+}
+
+/**
+ * @example
+ * ```jsx
+ * const App = () => {
+ * return (
+ * <>
+ *
+ *
+ *
+ *
+ * >
+ * )
+ * }
+ * ```
+ */
+
+export const Loader = ({
+ color = 'primary',
+ size = 'md',
+ type = 'spinner',
+}: LoaderProps) => {
+ return (
+ <>
+ {type === 'spinner' ? (
+
+ ) : type === 'bars' ? (
+
+ ) : type === 'dots' ? (
+
+ ) : null}
+ >
+ );
+};
diff --git a/packages/components/Loader/Loaders/Bars/Bars.styles.ts b/packages/components/Loader/Loaders/Bars/Bars.styles.ts
new file mode 100644
index 0000000..5759663
--- /dev/null
+++ b/packages/components/Loader/Loaders/Bars/Bars.styles.ts
@@ -0,0 +1,58 @@
+import { css, keyframes } from '@emotion/react';
+import styled from '@emotion/styled';
+import { palette } from '../../../../theme/palette';
+import { NormalColorType } from '../../../../theme/types';
+
+const BarKeyframes = keyframes`
+ 0% {
+ transform: none;
+ }
+ 25% {
+ transform: scaleY(2);
+ }
+ 50%,
+ 100% {
+ transform: none;
+ }
+`;
+
+export const Container = styled.div<{ size: 'sm' | 'md' | 'lg' }>`
+ display: flex;
+ justify-content: space-around;
+ ${({ size }) =>
+ size === 'sm'
+ ? css`
+ width: 1.75rem;
+ height: 0.875rem;
+ div {
+ width: 0.25rem;
+ height: 100%;
+ }
+ `
+ : size === 'md'
+ ? css`
+ width: 2.7rem;
+ height: 1.375rem;
+ div {
+ width: 0.375rem;
+ height: 100%;
+ }
+ `
+ : css`
+ width: 4.5rem;
+ height: 2.25rem;
+ div {
+ width: 0.6rem;
+ height: 100%;
+ }
+ `};
+`;
+
+export const Bar = styled.div<{
+ color: NormalColorType;
+ delay: number;
+}>`
+ animation: ${BarKeyframes} 1s infinite ease-in-out;
+ animation-delay: ${({ delay }) => delay + 's'};
+ background-color: ${({ color }) => palette[color]};
+`;
diff --git a/packages/components/Loader/Loaders/Bars/Bars.tsx b/packages/components/Loader/Loaders/Bars/Bars.tsx
new file mode 100644
index 0000000..c0b6b22
--- /dev/null
+++ b/packages/components/Loader/Loaders/Bars/Bars.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { NormalColorType } from '../../../../theme/types';
+import * as S from './Bars.styles';
+
+export interface BarsProps {
+ size: 'sm' | 'md' | 'lg';
+ color: NormalColorType;
+}
+
+export const Bars = ({ size, color }: BarsProps) => {
+ return (
+
+ {[0, 0.1, 0.2, 0.3, 0.4].map((number) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/components/Loader/Loaders/Dots/Dots.styles.ts b/packages/components/Loader/Loaders/Dots/Dots.styles.ts
new file mode 100644
index 0000000..4a2c497
--- /dev/null
+++ b/packages/components/Loader/Loaders/Dots/Dots.styles.ts
@@ -0,0 +1,56 @@
+import { css, keyframes } from '@emotion/react';
+import styled from '@emotion/styled';
+import { palette } from '../../../../theme/palette';
+import { NormalColorType } from '../../../../theme/types';
+
+const DotsKeyframes = keyframes`
+ 0%{
+ opacity: 1;
+ }
+ 50%,100%{
+ opacity: 0.3;
+ }
+`;
+
+export const Container = styled.div<{ size: 'sm' | 'md' | 'lg' }>`
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ ${({ size }) =>
+ size === 'sm' &&
+ css`
+ width: 2.5rem;
+ div {
+ width: 0.5rem;
+ height: 0.5rem;
+ }
+ `}
+ ${({ size }) =>
+ size === 'md' &&
+ css`
+ width: 3.5rem;
+ div {
+ width: 0.7rem;
+ height: 0.7rem;
+ }
+ `}
+ ${({ size }) =>
+ size === 'lg' &&
+ css`
+ width: 5rem;
+ div {
+ width: 1rem;
+ height: 1rem;
+ }
+ `}
+`;
+
+export const Dot = styled.div<{
+ delay: number;
+ color: NormalColorType;
+}>`
+ border-radius: 50%;
+ background-color: ${({ color }) => palette[color]};
+ animation: ${DotsKeyframes} 1s infinite linear alternate;
+ animation-delay: ${({ delay }) => delay + 's'};
+`;
diff --git a/packages/components/Loader/Loaders/Dots/Dots.tsx b/packages/components/Loader/Loaders/Dots/Dots.tsx
new file mode 100644
index 0000000..e3028de
--- /dev/null
+++ b/packages/components/Loader/Loaders/Dots/Dots.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { NormalColorType } from '../../../../theme/types';
+import * as S from './Dots.styles';
+
+export interface DotsProps {
+ size: 'sm' | 'md' | 'lg';
+ color: NormalColorType;
+}
+
+export const Dots = ({ size, color }: DotsProps) => {
+ return (
+
+ {[0, 0.5, 1].map((number) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/components/Loader/Loaders/Spinner/Spinner.styles.ts b/packages/components/Loader/Loaders/Spinner/Spinner.styles.ts
new file mode 100644
index 0000000..6af2958
--- /dev/null
+++ b/packages/components/Loader/Loaders/Spinner/Spinner.styles.ts
@@ -0,0 +1,66 @@
+import { css, keyframes } from '@emotion/react';
+import styled from '@emotion/styled';
+import { palette } from '../../../../theme/palette';
+import { NormalColorType } from '../../../../theme/types';
+
+const containerKeyframes = keyframes`
+ 100% {
+ transform: rotate(360deg)
+ }
+`;
+
+const spinningDotKeyframes = keyframes`
+ 80%, 100% {
+ transform: rotate(360deg);
+ }
+`;
+const beforeKeyframes = keyframes`
+ 50% {
+ transform: scale(0.4);
+ } 100%, 0% {
+ transform: scale(1.0);
+ }
+`;
+
+export const Container = styled.div<{ size: 'sm' | 'md' | 'lg' }>`
+ ${({ size }) =>
+ size === 'sm'
+ ? css`
+ width: 1.375rem;
+ height: 1.375rem;
+ `
+ : size === 'md'
+ ? css`
+ width: 2.25rem;
+ height: 2.25rem;
+ `
+ : css`
+ width: 2.75rem;
+ height: 2.75rem;
+ `}
+ position: relative;
+ animation: ${containerKeyframes} 2.5s infinite linear both;
+`;
+
+export const SpinningDot = styled.div<{
+ delay: number;
+ color: NormalColorType;
+}>`
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ left: 0;
+ right: 0;
+ animation: ${spinningDotKeyframes} 2s infinite ease-in-out both;
+ animation-delay: ${({ delay }) => -1.2 + delay + 's'};
+ ::before {
+ content: ' ';
+ display: block;
+ width: 25%;
+ height: 25%;
+ background-color: ${({ color }) => palette[color]};
+ border-radius: 50%;
+ animation: ${beforeKeyframes} 2s infinite ease-in-out both;
+ animation-delay: ${({ delay }) => -1.2 + delay + 's'};
+ }
+`;
diff --git a/packages/components/Loader/Loaders/Spinner/Spinner.tsx b/packages/components/Loader/Loaders/Spinner/Spinner.tsx
new file mode 100644
index 0000000..a7d8a08
--- /dev/null
+++ b/packages/components/Loader/Loaders/Spinner/Spinner.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+import { NormalColorType } from '../../../../theme/types';
+import * as S from './Spinner.styles';
+
+interface SpinnerProps {
+ size: 'sm' | 'md' | 'lg';
+ color: NormalColorType;
+}
+
+export const Spinner = ({ size, color }: SpinnerProps) => {
+ return (
+
+ {[0.1, 0.2, 0.3, 0.4, 0.5, 0.6].map((number) => (
+
+ ))}
+
+ );
+};
diff --git a/packages/components/Loader/index.ts b/packages/components/Loader/index.ts
new file mode 100644
index 0000000..d702788
--- /dev/null
+++ b/packages/components/Loader/index.ts
@@ -0,0 +1 @@
+export { Loader } from './Loader';
diff --git a/packages/components/index.ts b/packages/components/index.ts
index 5e44d26..984654e 100644
--- a/packages/components/index.ts
+++ b/packages/components/index.ts
@@ -7,3 +7,4 @@ export * from './Portal';
export * from './Accordion';
export * from './TextInput';
export * from './Toggle';
+export * from './Loader';