Skip to content

Commit 16afe23

Browse files
authored
Feat(client): tree 레벨 컴포넌트 구현 (#54)
* feat: TreeStatusCard 구현 * style: TreeStatusCard 스타일 수정 및 클래스 이름 변경 * feat: 폰트 수정 * feat: 레벨 툴팁 아이콘 추가 * feat: LevelInfoCard 컴포넌트 추가 및 레벨 페이지에 통합 * feat: 색상 변경 * feat: 레벨 info 카드 썸네일 * feat: 로직 간소화 * chore: 주석 제거 * feat: 코드리뷰 반영 * refactor: 불필요한 함수 제거 * chore: 주석 제거
1 parent 1e370e4 commit 16afe23

File tree

11 files changed

+213
-1
lines changed

11 files changed

+213
-1
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Level = () => {
2-
return <div>Level</div>;
2+
return <div className="bg-secondary flex flex-col gap-[2rem] p-[1rem]"></div>;
33
};
44

55
export default Level;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { cn } from '@pinback/design-system/utils';
2+
import { Level } from '@pinback/design-system/ui';
3+
import { Icon, type IconName } from '@pinback/design-system/icons';
4+
import { TREE_LEVEL_TABLE, type TreeLevel } from '../utils/treeLevel';
5+
6+
const LEVEL_TOOLTIP_ICON = {
7+
1: 'tooltip_1',
8+
2: 'tooltip_2',
9+
3: 'tooltip_3',
10+
4: 'tooltip_4',
11+
5: 'tooltip_5',
12+
} as const satisfies Record<TreeLevel, IconName>;
13+
14+
export default function LevelInfoCard() {
15+
const rows = [...TREE_LEVEL_TABLE].reverse();
16+
17+
return (
18+
<section
19+
className={cn(
20+
'bg-white-bg common-shadow w-[24.6rem] rounded-[1.2rem] px-[1.6rem] py-[2.4rem]'
21+
)}
22+
aria-label="지식나무 숲 레벨 안내"
23+
>
24+
<h2 className="sub2-sb text-font-black-1 mb-[1.2rem] flex items-center justify-center">
25+
치삐의 지식나무 숲 레벨
26+
</h2>
27+
28+
<ul>
29+
{rows.map((row) => (
30+
<li
31+
key={row.level}
32+
className="flex w-full items-center justify-between py-[1.2rem]"
33+
>
34+
<div className="flex w-full items-center gap-[1.2rem]">
35+
<div className="bg-gray0 flex h-[4.6rem] w-[4.6rem] items-center justify-center rounded-[0.8rem]">
36+
<Icon
37+
name={LEVEL_TOOLTIP_ICON[row.level]}
38+
width={46}
39+
height={46}
40+
className="rounded-[0.8rem]"
41+
aria-label={`${row.level} 썸네일 아이콘`}
42+
/>
43+
</div>
44+
45+
<div className="ml-[0.8rem] flex flex-1 flex-col gap-[0.4rem]">
46+
<div className="flex justify-between">
47+
<span className="sub5-sb text-font-black-1">{row.name}</span>
48+
<Level level={row.level} aria-label={`레벨 ${row.level}`} />
49+
</div>
50+
51+
<span className="caption2-m text-font-gray-3">
52+
{row.rangeLabel}
53+
</span>
54+
</div>
55+
</div>
56+
</li>
57+
))}
58+
</ul>
59+
60+
<div className="bg-gray0 mt-[0.8rem] rounded-[0.4rem] px-[0.8rem] py-[1.2rem]">
61+
<p className="caption2-m text-font-gray-3 flex items-center justify-center">
62+
정보를 1분 동안 읽고 도토리를 모아보세요. 치삐를 행복하게 만들 수
63+
있어요.
64+
</p>
65+
</div>
66+
</section>
67+
);
68+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Level, Progress } from '@pinback/design-system/ui';
2+
import { cn } from '@pinback/design-system/utils';
3+
import { getTreeLevel } from '../utils/treeLevel';
4+
5+
export interface TreeStatusCardProps {
6+
acorns: number;
7+
}
8+
9+
export default function TreeStatusCard({ acorns }: TreeStatusCardProps) {
10+
const info = getTreeLevel(acorns);
11+
12+
const barPercent = Math.min(100, info.level * 20);
13+
14+
return (
15+
<div
16+
className={cn('bg-white-bg w-[32.3rem] rounded-[1.2rem] p-[1.2rem]')}
17+
role="group"
18+
aria-label={`${info.name} 진행 카드`}
19+
>
20+
<div className="flex items-baseline">
21+
<span className="head1 text-main500">{barPercent}%</span>
22+
</div>
23+
24+
<div className="mt-[0.8rem] flex items-center gap-[0.4rem]">
25+
<span className="sub4-sb text-font-gray-2">{info.name}</span>
26+
<Level level={info.level} />
27+
</div>
28+
29+
<div className="mt-[1.6rem]">
30+
<Progress
31+
value={barPercent}
32+
variant="tree"
33+
aria-label={`${info.name} 레벨 진행률`}
34+
/>
35+
</div>
36+
</div>
37+
);
38+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
type TreeLevelRowShape = {
2+
level: number;
3+
name: string;
4+
min: number;
5+
max?: number;
6+
rangeLabel: string;
7+
};
8+
9+
export const TREE_LEVEL_TABLE = [
10+
{ level: 1, name: '잊힌 기록의 숲', min: 0, max: 0, rangeLabel: '0개' },
11+
{ level: 2, name: '햇살의 터전', min: 1, max: 2, rangeLabel: '1–2개' },
12+
{ level: 3, name: '기록의 오솔길', min: 3, max: 4, rangeLabel: '3–4개' },
13+
{ level: 4, name: '지식 나무 언덕', min: 5, max: 6, rangeLabel: '5–6개' },
14+
{ level: 5, name: '도토리 만개 숲', min: 7, rangeLabel: '7개 이상' },
15+
] as const satisfies readonly TreeLevelRowShape[];
16+
17+
export type TreeLevel = (typeof TREE_LEVEL_TABLE)[number]['level'];
18+
export type TreeLevelRow = TreeLevelRowShape;
19+
20+
export type TreeLevelResult = TreeLevelRow & {
21+
progressToNext: number;
22+
nextMin?: number;
23+
remainingToNext?: number;
24+
};
25+
26+
function findLevelRow(count: number, rows: readonly TreeLevelRow[]) {
27+
const idx = rows.findIndex(
28+
(r) => count >= r.min && (r.max === undefined || count <= r.max)
29+
);
30+
const i = idx === -1 ? 0 : idx;
31+
return { row: rows[i], next: rows[i + 1] as TreeLevelRow | undefined };
32+
}
33+
34+
function calcProgress(count: number, row: TreeLevelRow, next?: TreeLevelRow) {
35+
if (!next)
36+
return {
37+
progressToNext: 1 as const,
38+
nextMin: undefined,
39+
remainingToNext: undefined,
40+
};
41+
const span = Math.max(1, next.min - row.min);
42+
const progressToNext = Math.min(1, (count - row.min) / span);
43+
const remainingToNext = Math.max(0, next.min - count);
44+
return { progressToNext, nextMin: next.min, remainingToNext };
45+
}
46+
47+
export function getTreeLevel(acorns: number): TreeLevelResult {
48+
const { row, next } = findLevelRow(acorns, TREE_LEVEL_TABLE);
49+
return { ...row, ...calcProgress(acorns, row, next) };
50+
}

packages/design-system/src/icons/iconNames.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@ export const iconNames = [
1111
'ic_details_disable',
1212
'ic_info',
1313
'ic_plus',
14+
'tooltip_1',
15+
'tooltip_2',
16+
'tooltip_3',
17+
'tooltip_4',
18+
'tooltip_5',
1419
] as const;
1520
export type IconName = (typeof iconNames)[number];
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { Icon } from './components/icon';
2+
export type { IconName } from './iconNames';

packages/design-system/src/icons/source/tooltip_1.svg

Lines changed: 10 additions & 0 deletions
Loading

packages/design-system/src/icons/source/tooltip_2.svg

Lines changed: 10 additions & 0 deletions
Loading

packages/design-system/src/icons/source/tooltip_3.svg

Lines changed: 10 additions & 0 deletions
Loading

packages/design-system/src/icons/source/tooltip_4.svg

Lines changed: 10 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)