Skip to content

Commit b050565

Browse files
committed
Merge branch 'develop' into refactor
2 parents 8f5e885 + 5c802b5 commit b050565

File tree

21 files changed

+792
-147
lines changed

21 files changed

+792
-147
lines changed

src/app/home/create-goal/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Sidebar } from '@/feature/goal/components/Sidebar';
66
import { ConfirmGoalBottomBar } from '@/feature/goal/components/ConfirmGoalBottomBar';
77
import FlexBox from '@/shared/components/layout/FlexBox';
88
import DatePicker from '@/shared/components/input/DatePicker';
9-
import { TextAreaWithCount } from '@/shared/components/input/TextArea';
9+
import { TextArea } from '@/shared/components/input/TextArea';
1010
import Image from 'next/image';
1111
import { useFetchPostCreateGoal } from '@/feature/goal/hooks/useFetchPostCreateGoal';
1212

@@ -200,7 +200,7 @@ export default function CreateGoalPage() {
200200
</p>
201201
</div>
202202
<FlexBox className="mt-6 gap-4">
203-
<TextAreaWithCount
203+
<TextArea
204204
label="목표 설정 (AS IS)"
205205
placeholder="현재 상태를 간단히 입력해주세요."
206206
isError={!!errors.beforeAfter?.asIs}
@@ -209,7 +209,7 @@ export default function CreateGoalPage() {
209209
maxLength={30}
210210
/>
211211
<Image src="/icon/arrow-right.svg" alt="Dashboard Preview" width={24} height={24} />
212-
<TextAreaWithCount
212+
<TextArea
213213
label="목표 설정 (TO BE)"
214214
placeholder="4주 후 이루고 싶은 목표를 간단히 입력해주세요."
215215
isError={!!errors.beforeAfter?.toBe}
@@ -228,7 +228,7 @@ export default function CreateGoalPage() {
228228
</div>
229229
<div className="grid grid-cols-1 gap-3">
230230
{[0, 1, 2, 3].map(idx => (
231-
<TextAreaWithCount
231+
<TextArea
232232
key={idx}
233233
label={`${idx + 1}주차`}
234234
placeholder={`ex) ${idx + 1}주차 목표를 입력하세요.`}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import clsx from 'clsx';
2+
3+
type BadgeType = 'default' | 'dot' | 'icon-right' | 'icon-left' | 'icon-only';
4+
type BadgeSize = 'sm' | 'md' | 'lg';
5+
6+
interface BadgeProps {
7+
type: BadgeType;
8+
size: BadgeSize;
9+
icon?: React.ReactNode;
10+
label?: string;
11+
className?: string;
12+
}
13+
14+
const Badge = ({ type, size, icon, label, className }: BadgeProps) => {
15+
const baseClass = clsx(
16+
'inline-flex items-center gap-2 font-medium bg-accent-violet text-label-normal',
17+
type === 'icon-only'
18+
? 'rounded-full p-2'
19+
: size === 'sm'
20+
? 'py-1 px-3 text-xs rounded-2xl'
21+
: size === 'md'
22+
? 'py-1 px-4 text-sm rounded-2xl'
23+
: size === 'lg'
24+
? 'py-2 px-5 text-sm rounded-2xl'
25+
: className
26+
);
27+
28+
const renderContent = () => {
29+
switch (type) {
30+
case 'dot':
31+
return (
32+
<>
33+
<svg
34+
width="8"
35+
height="8"
36+
viewBox="0 0 6 6"
37+
fill="none"
38+
xmlns="http://www.w3.org/2000/svg"
39+
aria-hidden="true"
40+
>
41+
<circle cx="3" cy="3" r="3" fill="#F7F7F8" />
42+
</svg>
43+
{label}
44+
</>
45+
);
46+
case 'icon-left':
47+
return (
48+
<>
49+
{icon && <span aria-hidden="true">{icon}</span>}
50+
{label}
51+
</>
52+
);
53+
case 'icon-right':
54+
return (
55+
<>
56+
{label}
57+
{icon && <span aria-hidden="true">{icon}</span>}
58+
</>
59+
);
60+
case 'icon-only':
61+
return <>{icon}</>;
62+
default:
63+
return <>{label}</>;
64+
}
65+
};
66+
67+
const accessibleLabel = type === 'icon-only' ? label : undefined;
68+
69+
return (
70+
<span className={baseClass} role={type === 'icon-only' ? 'img' : undefined} aria-label={accessibleLabel}>
71+
{renderContent()}
72+
</span>
73+
);
74+
};
75+
76+
export default Badge;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// 디자인 시스템에 반영되면 추후에 수정해놓겠습니다
2+
const SectionMessage = ({
3+
children,
4+
type = 'warning',
5+
}: {
6+
children: React.ReactNode;
7+
type?: 'warning' | 'info' | 'success' | 'error';
8+
}) => {
9+
// 타입별 색상 및 아이콘
10+
const config = {
11+
warning: {
12+
bg: 'bg-[#373226]',
13+
icon: (
14+
<svg width="18" height="18" viewBox="0 0 20 20" fill="none">
15+
<path
16+
d="M10 2C5.03 2 1 6.03 1 11C1 15.97 5.03 20 10 20C14.97 20 19 15.97 19 11C19 6.03 14.97 2 10 2ZM10 18C6.13 18 3 14.87 3 11C3 7.13 6.13 4 10 4C13.87 4 17 7.13 17 11C17 14.87 13.87 18 10 18ZM11 7H9V13H11V7ZM11 15H9V17H11V15Z"
17+
fill="#FFA938"
18+
/>
19+
</svg>
20+
),
21+
},
22+
// 다른 타입도 필요하면 추가
23+
};
24+
25+
const { bg, icon } = config[type as 'warning']; // 임시로 타입 강제주입
26+
27+
return (
28+
<div className={`flex items-center rounded-r-[12px] rounded-l-[12px] px-4 py-2 ${bg}`}>
29+
<span className="mr-2 flex-shrink-0">{icon}</span>
30+
<span className="text-white text-sm">{children}</span>
31+
</div>
32+
);
33+
};
34+
35+
export default SectionMessage;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { InputHTMLAttributes } from 'react';
2+
3+
const Checkbox = ({ ...props }: InputHTMLAttributes<HTMLInputElement>) => {
4+
const getStyles = () => {
5+
if (props.disabled) {
6+
return 'bg-interaction-disable outline-line-normal';
7+
} else {
8+
return 'outline-line-normal hover:outline-gray-500 focus:border focus:border-line-normal focus:drop-shadow-sm checked:bg-primary-normal';
9+
}
10+
};
11+
12+
return (
13+
<label className="inline-flex items-center cursor-pointer relative">
14+
<input
15+
type="checkbox"
16+
disabled={props.disabled}
17+
className={`peer appearance-none w-5 h-5 rounded-sm outline-1 ${getStyles()}`}
18+
{...props}
19+
/>
20+
<svg
21+
width="14"
22+
height="14"
23+
viewBox="0 0 24 24"
24+
fill="none"
25+
xmlns="http://www.w3.org/2000/svg"
26+
className={`absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-0 peer-checked:opacity-100 transition-opacity`}
27+
>
28+
<path
29+
d="M20 6L9 17L4 12"
30+
stroke={props.disabled ? 'gray' : 'black'}
31+
strokeWidth="4"
32+
strokeLinecap="round"
33+
strokeLinejoin="round"
34+
/>
35+
</svg>
36+
</label>
37+
);
38+
};
39+
40+
export default Checkbox;

src/shared/components/input/DatePanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ const DatePanel = React.forwardRef<HTMLDivElement, DatePanelProps>(
179179
return (
180180
<div
181181
ref={ref}
182-
className="absolute top-full left-0 right-0 mt-2 bg-label-button-neutral text-white border border-label-assistive rounded-lg shadow-lg z-50"
182+
className="min-w-[308px] absolute top-full left-0 right-0 mt-2 bg-label-button-neutral text-white border border-label-assistive rounded-lg shadow-lg z-50"
183183
role="dialog"
184184
aria-label="날짜 선택"
185185
>

src/shared/components/input/DatePicker.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@ const DatePicker = ({
2828

2929
// 날짜 포맷팅 함수
3030
const formatDate = (date: Date) => {
31-
return date.toLocaleDateString('ko-KR', {
32-
year: 'numeric',
33-
month: 'long',
34-
day: 'numeric',
35-
});
31+
// const year = String(date.getFullYear()).slice(2);
32+
// const month = String(date.getMonth() + 1).padStart(2, '0');
33+
// const day = String(date.getDate()).padStart(2, '0');
34+
// return `${year}-${month}-${day}`;
35+
return date
36+
.toLocaleDateString('ko-KR', {
37+
year: '2-digit',
38+
month: '2-digit',
39+
day: '2-digit',
40+
})
41+
.slice(0, 10);
3642
};
3743

3844
// 패널 외부 클릭 시 닫기
@@ -55,7 +61,7 @@ const DatePicker = ({
5561
return () => {
5662
document.removeEventListener('mousedown', handleClickOutside);
5763
};
58-
}, [isOpen]);
64+
}, []);
5965

6066
// ESC 키로 패널 닫기
6167
useEffect(() => {
@@ -73,7 +79,7 @@ const DatePicker = ({
7379
return () => {
7480
document.removeEventListener('keydown', handleEscKey);
7581
};
76-
}, [isOpen]);
82+
}, []);
7783

7884
const handleToggle = () => {
7985
setIsOpen(!isOpen);
@@ -101,7 +107,7 @@ const DatePicker = ({
101107
ref={triggerRef}
102108
type="button"
103109
{...props}
104-
className="w-full h-full flex items-center gap-4 px-4 py-3 rounded-lg bg-label-button-neutral text-white body-1-normal border border-label-assistive shadow-xs focus:ring-2 focus:outline-none"
110+
className="w-full h-full flex items-center gap-2 md:gap-4 px-4 py-3 rounded-lg bg-label-button-neutral text-white body-1-normal border border-label-assistive shadow-xs focus:ring-2 focus:outline-none"
105111
onClick={handleToggle}
106112
onKeyDown={handleKeyDown}
107113
aria-haspopup="dialog"

src/shared/components/input/Dropdown.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useRef, useState, useEffect } from 'react';
22
import FlexBox from '../layout/FlexBox';
33

4+
// 개발 예정
45
interface DropdownProps {
56
options: string[];
67
selected: string;
@@ -153,7 +154,7 @@ interface DropdownFieldProps extends DropdownProps {
153154
errorMessage?: string;
154155
}
155156

156-
export const DropdownField = ({ label, isError, errorMessage, ...rest }: DropdownFieldProps) => {
157+
const DropdownField = ({ label, isError, errorMessage, ...rest }: DropdownFieldProps) => {
157158
return (
158159
<div className="space-y-2">
159160
{label && <label className="block text-sm font-medium label-1-regular text-label-normal">{label}</label>}
@@ -162,3 +163,5 @@ export const DropdownField = ({ label, isError, errorMessage, ...rest }: Dropdow
162163
</div>
163164
);
164165
};
166+
167+
export default DropdownField;

src/shared/components/input/ProgressBar.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,37 @@ import FlexBox from '@/shared/components/layout/FlexBox';
22
import { motion } from 'framer-motion';
33

44
export interface ProgressBarProps {
5-
doneTask: number;
6-
totalTask: number;
5+
percentage: number;
6+
totalTask?: number;
77
label?: string;
8+
showPercentage?: boolean;
89
}
910

10-
const ProgressBar = ({ doneTask, totalTask, label = '작업 완성률' }: ProgressBarProps) => {
11-
const percentage = totalTask > 0 ? Math.min(Math.round((doneTask / totalTask) * 100), 100) : 0;
11+
const ProgressBar = ({ percentage, totalTask, label = '작업 완성률', showPercentage = false }: ProgressBarProps) => {
12+
const value = Math.min(percentage, 100);
13+
const hasTotalTask = typeof totalTask === 'number' && !isNaN(totalTask);
14+
const doneTask = hasTotalTask ? Math.min(Math.round((percentage / 100) * totalTask), totalTask) : null;
15+
1216
return (
1317
<div className="flex flex-col gap-2 items-start w-full">
14-
<FlexBox className="w-full justify-between">
15-
{label && (
16-
<div className="body-1-medium text-primary-normal">
17-
{label}{' '}
18-
<span className="ml-2 text-label-alternative">{`${Math.min(doneTask, totalTask)}/${totalTask}`}</span>
19-
</div>
20-
)}
21-
<span className="title-3-bold text-accent-violet">{percentage}%</span>
22-
</FlexBox>
18+
{showPercentage && (
19+
<FlexBox className="w-full justify-between">
20+
{label && (
21+
<div className="body-1-medium text-primary-normal">
22+
{label}{' '}
23+
{hasTotalTask && doneTask !== null && (
24+
<span className="ml-2 text-label-alternative">{`${Math.min(doneTask, totalTask)}/${totalTask}`}</span>
25+
)}
26+
</div>
27+
)}
28+
<span className="title-3-bold text-accent-violet">{value}%</span>
29+
</FlexBox>
30+
)}
2331
<FlexBox className="relative w-full rounded-full h-3 bg-fill-normal">
2432
<motion.div
2533
className={`absolute h-full top-0 left-0 rounded-full bg-blue-500`}
2634
initial={{ width: 0 }}
27-
animate={{ width: `${percentage}%` }}
35+
animate={{ width: `${value}%` }}
2836
transition={{ duration: 0.4, ease: 'easeOut' }}
2937
/>
3038
</FlexBox>

0 commit comments

Comments
 (0)