Skip to content

Commit dd009be

Browse files
Merge pull request #49 from SOPT-36-NINEDOT/feat/#43/goalOverallWrite
[Feat]: 전체 목표 작성 뷰 구현
2 parents 5af16cc + 2aceef1 commit dd009be

7 files changed

Lines changed: 195 additions & 18 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { styleVariants } from '@vanilla-extract/css';
2+
3+
const baseGradientCircle = {
4+
position: 'absolute' as const,
5+
borderRadius: '50%',
6+
flexShrink: 0,
7+
zIndex: 0,
8+
};
9+
10+
export const gradientCircle = styleVariants({
11+
topRight: [
12+
baseGradientCircle,
13+
{
14+
top: '-89.2rem',
15+
right: '-89.2rem',
16+
width: '178.5rem',
17+
height: '178.5rem',
18+
background:
19+
'radial-gradient(50% 50% at 50% 50%, rgba(50, 95, 236, 0.50) 0%, rgba(2, 5, 11, 0.50) 100%)',
20+
},
21+
],
22+
bottomLeft1: [
23+
baseGradientCircle,
24+
{
25+
bottom: '-18rem',
26+
left: '-31rem',
27+
width: '78.6rem',
28+
height: '82.6rem',
29+
background:
30+
'radial-gradient(50% 50% at 50% 50%, rgba(50, 95, 236, 0.30) 0%, rgba(50, 95, 236, 0.00) 100%)',
31+
},
32+
],
33+
bottomLeft2: [
34+
baseGradientCircle,
35+
{
36+
bottom: '-18rem',
37+
left: '-1rem',
38+
width: '65.8rem',
39+
height: '65.8rem',
40+
opacity: 0.3,
41+
background:
42+
'radial-gradient(50% 50% at 50% 50%, rgba(59, 255, 160, 0.70) 0%, rgba(59, 255, 160, 0.00) 100%)',
43+
},
44+
],
45+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { gradientCircle } from './GradientCircle.css';
2+
3+
type VariantType = keyof typeof gradientCircle;
4+
5+
interface GradientCircleProps {
6+
variant: VariantType;
7+
}
8+
9+
export const GradientCircle = ({ variant }: GradientCircleProps) => {
10+
return <div className={gradientCircle[variant]} />;
11+
};

src/page/todo/Todo.css.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { style } from '@vanilla-extract/css';
2+
3+
import { colors } from '@/style/token';
4+
import { fonts } from '@/style/token/typography.css';
5+
6+
export const todoContainer = style({
7+
height: '100vh',
8+
display: 'flex',
9+
flexDirection: 'column',
10+
alignItems: 'center',
11+
justifyContent: 'center',
12+
backgroundColor: colors.bg_black01,
13+
position: 'relative',
14+
overflow: 'hidden',
15+
});
16+
17+
export const todoTitle = style({
18+
color: colors.white01,
19+
...fonts.display01,
20+
textAlign: 'center',
21+
marginBottom: '5.6rem',
22+
position: 'relative',
23+
zIndex: 1,
24+
height: '15.2rem',
25+
});
26+
27+
export const todoInputContainer = style({
28+
display: 'flex',
29+
flexDirection: 'row',
30+
alignItems: 'center',
31+
gap: '2rem',
32+
position: 'relative',
33+
});

src/page/todo/Todo.tsx

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,88 @@
1-
import { Outlet } from 'react-router-dom';
1+
import { useEffect, useState, useRef } from 'react';
2+
import { Link } from 'react-router-dom';
3+
4+
import * as styles from './Todo.css';
5+
6+
import { GradientCircle } from '@/common/component/GradientCircle/GradientCircle';
7+
import GoButton from '@/common/component/GoButton/GoButton';
8+
import { PATH } from '@/route';
9+
10+
const TYPING_DURATION = 3000;
11+
const FULL_TEXT = '66일간 달성할 목표를 입력하고\n만다라트를 시작해보세요!';
12+
const CHARARRAY = Array.from(FULL_TEXT);
213

314
const Todo = () => {
15+
const [displayedText, setDisplayedText] = useState('');
16+
const [inputText, setInputText] = useState('');
17+
const indexRef = useRef(0);
18+
const textRef = useRef('');
19+
20+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
21+
setInputText(e.target.value);
22+
};
23+
24+
useEffect(() => {
25+
let startTime: number | null = null;
26+
const totalDuration = TYPING_DURATION;
27+
const totalChars = CHARARRAY.length;
28+
let isMounted = true;
29+
30+
const step = (timestamp: number) => {
31+
if (!isMounted) {
32+
return;
33+
}
34+
if (startTime === null) {
35+
startTime = timestamp;
36+
}
37+
const elapsed = timestamp - startTime;
38+
const progress = Math.min(elapsed / totalDuration, 1);
39+
const charsToShow = Math.floor(progress * totalChars);
40+
41+
if (charsToShow > indexRef.current) {
42+
textRef.current = CHARARRAY.slice(0, charsToShow).join('');
43+
setDisplayedText(textRef.current);
44+
indexRef.current = charsToShow;
45+
}
46+
47+
if (progress < 1) {
48+
requestAnimationFrame(step);
49+
}
50+
};
51+
52+
const rafId = requestAnimationFrame(step);
53+
54+
return () => {
55+
isMounted = false;
56+
cancelAnimationFrame(rafId);
57+
};
58+
}, []);
59+
60+
const renderTextWithLineBreaks = () =>
61+
displayedText.split('\n').map((line, idx) => (
62+
<span key={idx}>
63+
{line}
64+
<br />
65+
</span>
66+
));
67+
468
return (
5-
<div>
6-
<h1>Todo</h1>
7-
<Outlet />
8-
</div>
69+
<main className={styles.todoContainer}>
70+
<GradientCircle variant="topRight" />
71+
<GradientCircle variant="bottomLeft1" />
72+
<GradientCircle variant="bottomLeft2" />
73+
<h2 className={styles.todoTitle}>{renderTextWithLineBreaks()}</h2>
74+
<section className={styles.todoInputContainer}>
75+
<input
76+
type="text"
77+
value={inputText}
78+
onChange={handleInputChange}
79+
placeholder="이루고 싶은 목표를 작성하세요."
80+
/>
81+
<Link to={PATH.TODO_UPPER}>
82+
<GoButton isActive={inputText.length > 0} />
83+
</Link>
84+
</section>
85+
</main>
986
);
1087
};
1188

src/route/MainRoutes.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ export const mainRoutes: RouteObject[] = [
1717
{
1818
path: PATH.TODO,
1919
element: <Todo />,
20-
children: [
21-
{
22-
path: 'upper',
23-
element: <UpperGoal />,
24-
},
25-
{
26-
path: 'lower',
27-
element: <LowerGoal />,
28-
},
29-
],
20+
},
21+
{
22+
path: PATH.TODO_UPPER,
23+
element: <UpperGoal />,
24+
},
25+
{
26+
path: PATH.TODO_LOWER,
27+
element: <LowerGoal />,
3028
},
3129
{
3230
path: PATH.MANDAL,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { style } from '@vanilla-extract/css';
2+
3+
export const layoutContainer = style({
4+
display: 'flex',
5+
flexDirection: 'column',
6+
height: '100vh',
7+
overflow: 'hidden',
8+
});
9+
10+
export const layoutMain = style({
11+
flex: 1,
12+
overflow: 'hidden',
13+
});

src/shared/component/Layout/Layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { Outlet } from 'react-router-dom';
22

33
import Header from './Header/Header';
4+
import * as styles from './Layout.css';
45

56
const Layout = () => {
67
return (
7-
<div>
8+
<div className={styles.layoutContainer}>
89
<Header />
9-
10-
<main>
10+
<main className={styles.layoutMain}>
1111
<Outlet />
1212
</main>
1313

0 commit comments

Comments
 (0)