diff --git a/src/components/common/Chips/Chips.style.ts b/src/components/common/Chips/Chips.style.ts new file mode 100644 index 00000000..9b33262a --- /dev/null +++ b/src/components/common/Chips/Chips.style.ts @@ -0,0 +1,29 @@ +import { css } from '@emotion/react'; +import typo from '@/styles/typo'; +import color from '@/styles/color'; +import type { ChipsProps } from './Chips'; + +export const getSizeStyling = (size: Required['size']) => { + const style = { + large: css(typo.Body.SemiBold_5, { + padding: '6px 12px', + borderRadius: '8px', + }), + small: css(typo.Body.Medium_5, { + padding: '2px 8px', + borderRadius: '6px', + }), + }; + + return style[size]; +}; + +export const chipsStyling = css({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: 'fit-content', + backgroundColor: color.Primary[400], + color: color.Neutral[50], + cursor: 'pointer', +}); diff --git a/src/components/common/Chips/Chips.tsx b/src/components/common/Chips/Chips.tsx new file mode 100644 index 00000000..ff9854eb --- /dev/null +++ b/src/components/common/Chips/Chips.tsx @@ -0,0 +1,15 @@ +/* eslint-disable react/react-in-jsx-scope */ +import { ReactNode } from 'react'; +import type { Size } from '@/types/temp'; +import { chipsStyling, getSizeStyling } from './Chips.style'; + +export interface ChipsProps { + size?: Extract; + children?: ReactNode; +} + +const Chips = ({ size = 'large', children }: ChipsProps) => ( +
{children}
+); + +export default Chips; diff --git a/src/components/pattern/TopBanner/TopBanner.style.ts b/src/components/pattern/TopBanner/TopBanner.style.ts new file mode 100644 index 00000000..bbbe132e --- /dev/null +++ b/src/components/pattern/TopBanner/TopBanner.style.ts @@ -0,0 +1,45 @@ +/* eslint-disable import/no-cycle */ +import { css } from '@emotion/react'; +import typo from '@/styles/typo'; +import color from '@/styles/color'; +import { loadingStyle } from '@/styles/keyframes'; + +export const loadingStyling = css` + animation: ${loadingStyle} 2s infinite; +`; + +export const hideInfo = css({ + visibility: 'hidden', +}); + +export const bannerStyling = css({ + display: 'flex', + flexDirection: 'column-reverse', + width: '100%', + minHeight: '297px', + padding: '30px', + borderRadius: '20px', + backgroundColor: color.Neutral[800], + cursor: 'pointer', +}); + +export const bannerWrapper = css({ + display: 'flex', + flexDirection: 'column', + gap: '6px', +}); + +export const bannerInfoWrapper = css({ + display: 'flex', + flexDirection: 'column', + gap: '10px', +}); + +export const bannerTitle = css(typo.Heading_2, { + color: color.Neutral[50], +}); + +export const bannerSummary = css(typo.Body.Medium_1, { + color: color.Neutral[400], + whiteSpace: 'pre-line', +}); diff --git a/src/components/pattern/TopBanner/TopBanner.tsx b/src/components/pattern/TopBanner/TopBanner.tsx new file mode 100644 index 00000000..7f002f58 --- /dev/null +++ b/src/components/pattern/TopBanner/TopBanner.tsx @@ -0,0 +1,32 @@ +/* eslint-disable react/react-in-jsx-scope */ +import { ReactNode } from 'react'; +import Chips from '@/components/common/Chips/Chips'; +import { + bannerStyling, + bannerWrapper, + bannerInfoWrapper, + bannerSummary, + bannerTitle, + loadingStyling, + hideInfo, +} from './TopBanner.style'; + +export interface TopBannerProps { + title?: ReactNode; + summary?: ReactNode; + isLoading?: boolean; +} + +const TopBanner = ({ title, summary, isLoading = false }: TopBannerProps) => ( +
+
+ 오늘의 톡픽 +
+
{title}
+
{summary}
+
+
+
+); + +export default TopBanner; diff --git a/src/stories/common/Button.stories.tsx b/src/stories/common/Button.stories.tsx index 798a35e9..23745e47 100644 --- a/src/stories/common/Button.stories.tsx +++ b/src/stories/common/Button.stories.tsx @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { storyContainer, storyInnerContainer } from '@/stories/story.styles'; const meta = { - title: 'Button', + title: 'commons/Button', component: Button, parameters: { layout: 'centered', diff --git a/src/stories/common/Chips.stories.tsx b/src/stories/common/Chips.stories.tsx new file mode 100644 index 00000000..37f19337 --- /dev/null +++ b/src/stories/common/Chips.stories.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import Chips from '@/components/common/Chips/Chips'; +import type { Meta, StoryObj } from '@storybook/react'; +import { storyContainer, storyInnerContainer } from '@/stories/story.styles'; + +const meta = { + title: 'commons/Chips', + component: Chips, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + size: { + options: ['small', 'large'], + control: { type: 'radio' }, + }, + children: { control: { type: 'text' } }, + }, + args: { + size: 'large', + children: '오늘의 밸런스톡', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + size: 'large', + }, +}; + +export const All: Story = { + render: (args) => ( +
    +
  • +

    Size

    + + Small + + + Large + +
  • +
+ ), +}; diff --git a/src/stories/pattern/TopBanner.stories.tsx b/src/stories/pattern/TopBanner.stories.tsx new file mode 100644 index 00000000..f94a0536 --- /dev/null +++ b/src/stories/pattern/TopBanner.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import TopBanner from '@/components/pattern/TopBanner/TopBanner'; +import type { Meta, StoryObj } from '@storybook/react'; +import { storyContainer, storyInnerContainer } from '@/stories/story.styles'; + +const exampleBanner = { + title: '깻잎 논쟁 당신의 선택은?', + summary: + '1. 저녁 식사 중 남자친구가 친구에게 깻잎을 떼어주자 나는 불편한 감정을 느꼈다. \n2. 집에 돌아와 남자친구에게 솔직하게 말했고, 그는 미안해하며 이해해 주었다. \n3. 친구들과 이 문제를 논의했지만 결론은 나지 않았고, 사람마다 다르게 받아들일 수 있음을 깨달았다.', +}; + +const meta = { + title: 'patterns/TopBanner', + component: TopBanner, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + title: { control: { type: 'text' } }, + summary: { control: { type: 'text' } }, + isLoading: { control: { type: 'boolean' } }, + }, + args: { + title: exampleBanner.title, + summary: exampleBanner.summary, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + title: exampleBanner.title, + summary: exampleBanner.summary, + isLoading: false, + }, +}; + +export const All: Story = { + render: (args) => ( +
    +
  • +

    Default

    + +

    Loading

    + +
  • +
+ ), +}; diff --git a/src/styles/keyframes.ts b/src/styles/keyframes.ts index 77893a19..f56923b0 100644 --- a/src/styles/keyframes.ts +++ b/src/styles/keyframes.ts @@ -1,4 +1,5 @@ import { keyframes } from '@emotion/react'; +import color from './color'; export const pulsate = keyframes({ '0%': { @@ -11,3 +12,15 @@ export const pulsate = keyframes({ transform: 'scale(1.1)', }, }); + +export const loadingStyle = keyframes({ + '0%': { + backgroundColor: color.Neutral[800], + }, + '50%': { + backgroundColor: color.Neutral[700], + }, + '100%': { + backgroundColor: color.Neutral[800], + }, +});