Skip to content

Commit 9625292

Browse files
Merge pull request #79 from SOPT-36-NINEDOT/feat/#43/goalOverallWrite
[Feat]: 전체 목표 뷰 텍스트 필드 추가 및 기능
2 parents 61427e5 + 3b03512 commit 9625292

File tree

11 files changed

+122
-94
lines changed

11 files changed

+122
-94
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { GradientCircle } from '@/common/component/GradientCircle/GradientCircle';
2+
3+
const GradientBackground = () => {
4+
return (
5+
<>
6+
<GradientCircle variant="topRight" />
7+
<GradientCircle variant="bottomLeft1" />
8+
<GradientCircle variant="bottomLeft2" />
9+
</>
10+
);
11+
};
12+
13+
export default GradientBackground;

src/common/component/GoButton/GoButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { goButtonContainer, goIcon } from '@/common/component/GoButton/GoButton.
33

44
type GoButtonProps = {
55
isActive: boolean;
6+
onClick?: React.MouseEventHandler<HTMLButtonElement>;
67
};
78

8-
const GoButton = ({ isActive = true }: GoButtonProps) => {
9+
const GoButton = ({ isActive = true, onClick }: GoButtonProps) => {
910
const state = isActive ? 'active' : 'disabled';
1011

1112
return (
12-
<button className={goButtonContainer({ state })}>
13+
<button className={goButtonContainer({ state })} onClick={onClick}>
1314
<IcBigNext className={goIcon({ state })} />
1415
</button>
1516
);

src/common/component/MandalartTextField/MandalartTextField.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ const TextField = ({
8787
placeholder,
8888
maxLength,
8989
disabled = false,
90+
onKeyDown,
9091
}: TextFieldProps) => {
9192
const [state, dispatch] = useReducer(reducer, {
9293
isFocused: false,
@@ -158,7 +159,7 @@ const TextField = ({
158159
disabled={disabled}
159160
onFocus={() => dispatch({ type: 'FOCUS' })}
160161
onBlur={() => dispatch({ type: 'BLUR' })}
161-
onKeyDown={handleKeyDown}
162+
onKeyDown={onKeyDown ?? handleKeyDown}
162163
onCompositionStart={() => dispatch({ type: 'COMPOSE_START' })}
163164
onCompositionEnd={() => dispatch({ type: 'COMPOSE_END' })}
164165
maxLength={effectiveMaxLength}

src/common/component/MandalartTextField/MandalartTextField.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export interface TextFieldProps {
77
placeholder?: string;
88
maxLength?: number;
99
disabled?: boolean;
10+
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
1011
}

src/common/hook/useTypingEffect.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const useTypingEffect = (fullText: string, duration: number) => {
4+
const [displayedText, setDisplayedText] = useState('');
5+
6+
useEffect(() => {
7+
let startTime: number | null = null;
8+
const charArray = Array.from(fullText);
9+
const totalChars = charArray.length;
10+
let isMounted = true;
11+
12+
const step = (timestamp: number) => {
13+
if (!isMounted) {
14+
return;
15+
}
16+
if (startTime === null) {
17+
startTime = timestamp;
18+
}
19+
20+
const elapsed = timestamp - startTime;
21+
const progress = Math.min(elapsed / duration, 1);
22+
const charsToShow = Math.floor(progress * totalChars);
23+
24+
setDisplayedText(charArray.slice(0, charsToShow).join(''));
25+
26+
if (progress < 1) {
27+
requestAnimationFrame(step);
28+
}
29+
};
30+
31+
const rafId = requestAnimationFrame(step);
32+
33+
return () => {
34+
isMounted = false;
35+
cancelAnimationFrame(rafId);
36+
};
37+
}, [fullText, duration]);
38+
39+
return displayedText;
40+
};
41+
42+
export default useTypingEffect;

src/page/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { default as Home } from './home/Home';
2-
export { default as Todo } from './todo/Todo';
2+
export { default as Todo } from './todo/entireTodo/Todo';
33
export { default as Mandal } from './mandal/Mandal';
44
export { default as History } from './history/History';

src/page/todo/Todo.tsx

Lines changed: 0 additions & 89 deletions
This file was deleted.

src/page/todo/entireTodo/Todo.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useState } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
4+
import { FULL_TEXT, TYPING_DURATION, PLACEHOLDER_TEXT } from './constant/constants';
5+
import * as styles from './Todo.css';
6+
7+
import useTypingEffect from '@/common/hook/useTypingEffect';
8+
import GoButton from '@/common/component/GoButton/GoButton';
9+
import GradientBackground from '@/common/component/\bBackground/GradientBackground';
10+
import TextField from '@/common/component/MandalartTextField/MandalartTextField';
11+
import { PATH } from '@/route';
12+
13+
const Todo = () => {
14+
const [inputText, setInputText] = useState('');
15+
const displayedText = useTypingEffect(FULL_TEXT, TYPING_DURATION);
16+
const navigate = useNavigate();
17+
18+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
19+
if (e.key === 'Enter') {
20+
handleGoNext();
21+
}
22+
};
23+
24+
const handleGoNext = () => {
25+
if (inputText.trim().length > 0) {
26+
navigate(PATH.TODO_UPPER);
27+
}
28+
};
29+
30+
const renderTextWithLineBreaks = () =>
31+
displayedText.split('\n').map((line, idx) => (
32+
<span key={idx}>
33+
{line}
34+
<br />
35+
</span>
36+
));
37+
38+
return (
39+
<main className={styles.todoContainer}>
40+
<GradientBackground />
41+
<h1 className={styles.todoTitle}>{renderTextWithLineBreaks()}</h1>
42+
<section className={styles.todoInputContainer}>
43+
<TextField
44+
variant="bigGoal"
45+
value={inputText}
46+
onChange={setInputText}
47+
onKeyDown={handleKeyDown}
48+
placeholder={PLACEHOLDER_TEXT}
49+
/>
50+
<GoButton isActive={inputText.length > 0} onClick={handleGoNext} />
51+
</section>
52+
</main>
53+
);
54+
};
55+
56+
export default Todo;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const FULL_TEXT = '66일간 달성할 목표를 입력하고\n만다라트를 시작해보세요!';
2+
export const TYPING_DURATION = 3000;
3+
export const PLACEHOLDER_TEXT = '이루고 싶은 목표를 작성하세요.';

0 commit comments

Comments
 (0)