Skip to content

Commit 8a92eae

Browse files
authored
Merge pull request #63 from SOPT-36-NINEDOT/feat/#61/small-mandalart
[Feat/#61] 만다라트 small 추가
2 parents ea82b00 + 0119be0 commit 8a92eae

File tree

15 files changed

+443
-294
lines changed

15 files changed

+443
-294
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { style } from '@vanilla-extract/css';
2+
3+
export const gridDefault = style({
4+
display: 'grid',
5+
gridTemplateColumns: 'repeat(3, 1fr)',
6+
gap: '1.6rem',
7+
width: 'fit-content',
8+
margin: '0 auto',
9+
});
10+
11+
export const gridSmall = style({
12+
display: 'grid',
13+
gridTemplateColumns: 'repeat(3, 1fr)',
14+
gap: '2rem',
15+
width: 'fit-content',
16+
margin: '0 auto',
17+
});
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
3+
import Mandalart, { type Cycle } from './Mandalart';
4+
import { MOCK_MANDALART_DATA } from './mock';
5+
6+
const meta = {
7+
title: 'Components/Mandalart',
8+
component: Mandalart,
9+
parameters: {
10+
layout: 'centered',
11+
backgrounds: {
12+
default: 'dark',
13+
},
14+
},
15+
tags: ['autodocs'],
16+
} satisfies Meta<typeof Mandalart>;
17+
18+
export default meta;
19+
type Story = StoryObj<typeof meta>;
20+
21+
const CUSTOM_GOALS = {
22+
mainGoal: '나인도트 1등하기',
23+
subGoals: [
24+
{
25+
title: '이현준 갈구기',
26+
position: 0,
27+
cycle: 'DAILY' as Cycle,
28+
},
29+
{
30+
title: '매일 운동하기',
31+
position: 1,
32+
cycle: 'DAILY' as Cycle,
33+
},
34+
{
35+
title: '일찍 일어나기',
36+
position: 2,
37+
cycle: 'DAILY' as Cycle,
38+
},
39+
{
40+
title: '계획 세우기',
41+
position: 3,
42+
cycle: 'WEEKLY' as Cycle,
43+
},
44+
{
45+
title: '시간 관리하기',
46+
position: 4,
47+
cycle: 'WEEKLY' as Cycle,
48+
},
49+
{
50+
title: '건강 관리하기',
51+
position: 5,
52+
cycle: 'DAILY' as Cycle,
53+
},
54+
{
55+
title: '긍정적으로 생각하기',
56+
position: 6,
57+
cycle: 'DAILY' as Cycle,
58+
},
59+
{
60+
title: '꾸준히 노력하기',
61+
position: 7,
62+
cycle: 'DAILY' as Cycle,
63+
},
64+
],
65+
completedGoals: [1, 3, 5],
66+
};
67+
68+
export const Default: Story = {
69+
args: {
70+
mainGoal: '메인 목표를 입력하세요',
71+
subGoals: MOCK_MANDALART_DATA.subGoals,
72+
},
73+
render: (args) => (
74+
<div style={{ display: 'flex', gap: '2rem' }}>
75+
<div>
76+
<h3 style={{ color: 'white', marginBottom: '1rem' }}>Default 사이즈</h3>
77+
<Mandalart {...args} />
78+
</div>
79+
<div>
80+
<h3 style={{ color: 'white', marginBottom: '1rem' }}>Small 사이즈</h3>
81+
<Mandalart {...args} size="small" />
82+
</div>
83+
</div>
84+
),
85+
};
86+
87+
export const Small: Story = {
88+
args: {
89+
mainGoal: '메인 목표를 입력하세요',
90+
subGoals: CUSTOM_GOALS.subGoals,
91+
size: 'small',
92+
},
93+
render: (args) => <Mandalart {...args} />,
94+
};
95+
96+
export const WithCustomGoals: Story = {
97+
args: CUSTOM_GOALS,
98+
render: (args) => <Mandalart {...args} />,
99+
};
100+
101+
export const WithCustomGoalsSmall: Story = {
102+
args: {
103+
...CUSTOM_GOALS,
104+
size: 'small',
105+
},
106+
render: (args) => <Mandalart {...args} />,
107+
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useState } from 'react';
2+
3+
import { Square } from './Square';
4+
import * as styles from './Mandalart.css';
5+
import { MOCK_MANDALART_DATA } from './mock';
6+
7+
export type Cycle = 'DAILY' | 'WEEKLY' | 'ONCE';
8+
export type MandalartSize = 'small' | 'default';
9+
10+
export interface SubGoal {
11+
title: string;
12+
position: number;
13+
cycle: Cycle;
14+
}
15+
16+
interface MandalartProps {
17+
mainGoal?: string;
18+
subGoals?: SubGoal[];
19+
size?: MandalartSize;
20+
}
21+
22+
const CENTER_INDEX = 4;
23+
24+
const Mandalart = ({
25+
mainGoal = MOCK_MANDALART_DATA.mainGoal,
26+
subGoals = MOCK_MANDALART_DATA.subGoals,
27+
size = 'default',
28+
}: MandalartProps) => {
29+
const [selectedGoal, setSelectedGoal] = useState<number | null>(null);
30+
31+
const handleGoalClick = (position: number) => {
32+
setSelectedGoal(selectedGoal === position ? null : position);
33+
};
34+
35+
const renderSquare = (index: number) => {
36+
if (index === CENTER_INDEX) {
37+
return <Square.Main key={index} content={mainGoal} size={size} />;
38+
}
39+
40+
const subGoalIndex = index > CENTER_INDEX ? index - 1 : index;
41+
const subGoal = subGoals[subGoalIndex];
42+
43+
return (
44+
<Square.Sub
45+
key={index}
46+
content={subGoal.title}
47+
isCompleted={selectedGoal === subGoalIndex}
48+
onClick={() => handleGoalClick(subGoalIndex)}
49+
size={size}
50+
/>
51+
);
52+
};
53+
54+
const squares = Array(9)
55+
.fill(null)
56+
.map((_, index) => renderSquare(index));
57+
58+
return <div className={size === 'small' ? styles.gridSmall : styles.gridDefault}>{squares}</div>;
59+
};
60+
61+
export default Mandalart;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { style } from '@vanilla-extract/css';
2+
3+
import { colors, fonts } from '@/style/token';
4+
5+
export const squareContainer = style({
6+
display: 'grid',
7+
margin: '0 auto',
8+
});
9+
10+
const baseCellDefault = style({
11+
borderRadius: '8px',
12+
cursor: 'pointer',
13+
display: 'flex',
14+
alignItems: 'center',
15+
justifyContent: 'center',
16+
textAlign: 'center',
17+
width: '19.6rem',
18+
height: '19.6rem',
19+
boxSizing: 'border-box',
20+
});
21+
22+
const baseCellSmall = style({
23+
borderRadius: '8px',
24+
cursor: 'pointer',
25+
display: 'flex',
26+
alignItems: 'center',
27+
justifyContent: 'center',
28+
textAlign: 'center',
29+
width: '16rem',
30+
height: '16rem',
31+
boxSizing: 'border-box',
32+
});
33+
34+
export const mainCellDefault = style([
35+
baseCellDefault,
36+
fonts.title03,
37+
{
38+
color: colors.white01,
39+
backgroundImage: colors.gradient04,
40+
},
41+
]);
42+
43+
export const mainCellSmall = style([
44+
baseCellSmall,
45+
fonts.subtitle01,
46+
{
47+
color: colors.white01,
48+
backgroundImage: colors.gradient04,
49+
padding: '1.4rem',
50+
},
51+
]);
52+
53+
export const subCellDefault = style([
54+
baseCellDefault,
55+
fonts.subtitle01,
56+
{
57+
color: colors.grey8,
58+
background: colors.grey2,
59+
':hover': {
60+
background: colors.grey3,
61+
},
62+
selectors: {
63+
'&[data-completed="true"]': {
64+
border: '0.4rem solid #305088',
65+
background: colors.grey2,
66+
},
67+
},
68+
},
69+
]);
70+
71+
export const subCellSmall = style([
72+
baseCellSmall,
73+
fonts.subtitle05,
74+
{
75+
color: colors.grey8,
76+
background: colors.grey2,
77+
':hover': {
78+
background: colors.grey3,
79+
},
80+
selectors: {
81+
'&[data-completed="true"]': {
82+
border: '0.3rem solid #305088',
83+
background: colors.grey2,
84+
},
85+
},
86+
},
87+
]);
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
3+
import { Square } from '.';
4+
5+
import { colors } from '@/style/token';
6+
7+
const meta = {
8+
title: 'Components/Square',
9+
component: Square.Main,
10+
parameters: {
11+
layout: 'centered',
12+
backgrounds: {
13+
default: 'dark',
14+
},
15+
},
16+
tags: ['autodocs'],
17+
} satisfies Meta<typeof Square.Main>;
18+
19+
export default meta;
20+
type Story = StoryObj<typeof meta>;
21+
22+
const handleClick = () => {};
23+
24+
export const Default: Story = {
25+
args: {
26+
content: '상위 목표',
27+
},
28+
render: (args) => (
29+
<div style={{ display: 'flex', gap: '2rem' }}>
30+
<div>
31+
<h3 style={{ color: colors.white01, marginBottom: '1rem' }}>Default 사이즈</h3>
32+
<p style={{ color: colors.white01, marginBottom: '1rem' }}>
33+
메인: title03 / 서브: subtitle01
34+
</p>
35+
<div style={{ display: 'flex', gap: '2rem' }}>
36+
<Square.Main {...args} />
37+
<Square.Sub content="세부 목표" isCompleted={false} onClick={handleClick} />
38+
</div>
39+
</div>
40+
<div>
41+
<h3 style={{ color: colors.white01, marginBottom: '1rem' }}>Small 사이즈</h3>
42+
<p style={{ color: colors.white01, marginBottom: '1rem' }}>
43+
메인: body04 / 서브: subtitle05
44+
</p>
45+
<div style={{ display: 'flex', gap: '2rem' }}>
46+
<Square.Main content="상위 목표" size="small" />
47+
<Square.Sub content="세부 목표" isCompleted={false} onClick={handleClick} size="small" />
48+
</div>
49+
</div>
50+
</div>
51+
),
52+
};
53+
54+
export const MainGoal: Story = {
55+
args: {
56+
content: '메인 목표를 입력하세요',
57+
},
58+
render: (args) => (
59+
<div style={{ display: 'flex', gap: '2rem' }}>
60+
<div>
61+
<h3 style={{ color: colors.white01, marginBottom: '1rem' }}>Default 사이즈 (title03)</h3>
62+
<Square.Main {...args} />
63+
</div>
64+
<div>
65+
<h3 style={{ color: colors.white01, marginBottom: '1rem' }}>Small 사이즈 (body04)</h3>
66+
<Square.Main content="메인 목표를 입력하세요" size="small" />
67+
</div>
68+
</div>
69+
),
70+
};
71+
72+
export const SubGoalStates: Story = {
73+
args: {
74+
content: '세부 목표를 입력하세요',
75+
},
76+
render: (args) => (
77+
<div style={{ display: 'flex', gap: '2rem' }}>
78+
<div>
79+
<h3 style={{ color: colors.white01, marginBottom: '1rem' }}>Default 사이즈 (subtitle01)</h3>
80+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
81+
<div>
82+
<h4 style={{ color: colors.white01, marginBottom: '1rem' }}>기본 상태</h4>
83+
<Square.Sub content={args.content} isCompleted={false} onClick={handleClick} />
84+
</div>
85+
<div>
86+
<h4 style={{ color: colors.white01, marginBottom: '1rem' }}>완료 상태</h4>
87+
<Square.Sub content="완료된 목표입니다" isCompleted={true} onClick={handleClick} />
88+
</div>
89+
</div>
90+
</div>
91+
<div>
92+
<h3 style={{ color: colors.white01, marginBottom: '1rem' }}>Small 사이즈 (subtitle05)</h3>
93+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
94+
<div>
95+
<h4 style={{ color: colors.white01, marginBottom: '1rem' }}>기본 상태</h4>
96+
<Square.Sub
97+
content={args.content}
98+
isCompleted={false}
99+
onClick={handleClick}
100+
size="small"
101+
/>
102+
</div>
103+
<div>
104+
<h4 style={{ color: colors.white01, marginBottom: '1rem' }}>완료 상태</h4>
105+
<Square.Sub
106+
content="완료된 목표입니다"
107+
isCompleted={true}
108+
onClick={handleClick}
109+
size="small"
110+
/>
111+
</div>
112+
</div>
113+
</div>
114+
</div>
115+
),
116+
};

0 commit comments

Comments
 (0)