@ihabdevteam/core 0.130.0 → 0.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/components.cjs.js +174 -174
- package/dist/components.esm.css +1 -1
- package/dist/hooks.cjs.js +1 -1
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +180 -180
- package/dist/ihabdevteam-core.d.ts +15 -15
- package/dist/packages/core/src/components/BottomNav.css +15 -0
- package/dist/packages/core/src/components/BottomNav.d.ts +6 -0
- package/dist/packages/core/src/components/BottomNav.js +12 -1
- package/dist/packages/core/src/components/Header.js +3 -1
- package/dist/packages/core/src/components/PassCard/PassCardBg.css +33 -0
- package/dist/packages/core/src/i18n/locales/en.json +2 -1
- package/dist/packages/core/src/i18n/locales/ko.json +2 -1
- package/dist/packages/core/src/utils/CategoryData.d.ts +12 -40
- package/dist/packages/core/src/utils/CategoryData.js +13 -8
- package/dist/packages/core/src/version.d.ts +1 -1
- package/dist/packages/core/src/version.js +1 -1
- package/dist/utils.cjs.js +1 -1
- package/package.json +1 -1
|
@@ -1355,7 +1355,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1355
1355
|
*
|
|
1356
1356
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1357
1357
|
*/
|
|
1358
|
-
export declare const CORE_VERSION = "0.
|
|
1358
|
+
export declare const CORE_VERSION = "0.131.0";
|
|
1359
1359
|
|
|
1360
1360
|
/** @public */
|
|
1361
1361
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -3017,6 +3017,12 @@ export declare type NavItem = {
|
|
|
3017
3017
|
label?: string;
|
|
3018
3018
|
icon?: string;
|
|
3019
3019
|
state?: string;
|
|
3020
|
+
/**
|
|
3021
|
+
* 단추 우상단 원형 숫자 배지(SideNav 항목 배지와 같은 모양). 0/undefined 면 숨기고, 99 에서 자른다.
|
|
3022
|
+
* 배지는 낭독기에서 숨기고 개수는 단추 이름에 붙인다. 넘쳐 "…" 메뉴로 가면 메뉴 항목의 배지가 되고,
|
|
3023
|
+
* "…" 단추에는 넘친 항목 개수의 합이 선다.
|
|
3024
|
+
*/
|
|
3025
|
+
badgeCount?: number;
|
|
3020
3026
|
};
|
|
3021
3027
|
|
|
3022
3028
|
/**
|
|
@@ -6641,28 +6647,22 @@ export declare interface TopBannerProps {
|
|
|
6641
6647
|
'aria-label'?: string;
|
|
6642
6648
|
}
|
|
6643
6649
|
|
|
6644
|
-
/**
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
key: string;
|
|
6652
|
-
label: string;
|
|
6653
|
-
}[];
|
|
6654
|
-
desc?: undefined;
|
|
6655
|
-
} | {
|
|
6650
|
+
/**
|
|
6651
|
+
* 주제·세부 주제 목록(열쇠·이름·아이콘·색). **설명(desc)은 두지 않는다** — 코어·앱 어디에서도 주제 설명을
|
|
6652
|
+
* 그리지 않고, 한때 7개 주제에 붙어 있던 '소음 환경 속 문장 이해 훈련' 은 훈련 유형(소음 하 문장 듣기)의
|
|
6653
|
+
* 설명을 옮겨 붙인 값이었다(2026-09-14 걷음). 화면에 보이는 설명은 훈련 유형(`TRAINING_TYPES`)의 것이다.
|
|
6654
|
+
* @public
|
|
6655
|
+
*/
|
|
6656
|
+
export declare const TOPIC_TYPES: {
|
|
6656
6657
|
key: string;
|
|
6657
6658
|
label: string;
|
|
6658
6659
|
icon: string;
|
|
6659
|
-
desc: string;
|
|
6660
6660
|
color: string;
|
|
6661
6661
|
subs: {
|
|
6662
6662
|
key: string;
|
|
6663
6663
|
label: string;
|
|
6664
6664
|
}[];
|
|
6665
|
-
}
|
|
6665
|
+
}[];
|
|
6666
6666
|
|
|
6667
6667
|
/**
|
|
6668
6668
|
* 훈련 카테고리 데이터 DI — 앱에서 주입하는 함수 기반 인터페이스.
|
|
@@ -38,6 +38,21 @@
|
|
|
38
38
|
overflow: hidden;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/* 단추 한 칸 — 단추와 같은 52px 이라 넘침 계산이 그대로다. 배지(.circle-badge--top-right)의 기준.
|
|
42
|
+
배지는 Badge 기본 코너(-6px)가 아니라 칸 안쪽 모서리(0)에 둔다 — .bottom-nav__items 가
|
|
43
|
+
overflow: hidden 이라 바깥으로 나간 6px 가 잘린다(넘침 계산을 위해 그 hidden 은 둬야 한다). */
|
|
44
|
+
.bottom-nav__slot {
|
|
45
|
+
position: relative;
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-shrink: 0;
|
|
48
|
+
width: 52px;
|
|
49
|
+
height: 52px;
|
|
50
|
+
}
|
|
51
|
+
.bottom-nav__slot .circle-badge--top-right {
|
|
52
|
+
top: 0;
|
|
53
|
+
right: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
.bottom-nav__right {
|
|
42
57
|
display: flex;
|
|
43
58
|
align-items: center;
|
|
@@ -6,6 +6,12 @@ export type NavItem = {
|
|
|
6
6
|
label?: string;
|
|
7
7
|
icon?: string;
|
|
8
8
|
state?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 단추 우상단 원형 숫자 배지(SideNav 항목 배지와 같은 모양). 0/undefined 면 숨기고, 99 에서 자른다.
|
|
11
|
+
* 배지는 낭독기에서 숨기고 개수는 단추 이름에 붙인다. 넘쳐 "…" 메뉴로 가면 메뉴 항목의 배지가 되고,
|
|
12
|
+
* "…" 단추에는 넘친 항목 개수의 합이 선다.
|
|
13
|
+
*/
|
|
14
|
+
badgeCount?: number;
|
|
9
15
|
};
|
|
10
16
|
/**
|
|
11
17
|
* 항상 "더보기(…)" 메뉴에만 노출되는 유틸 항목(알림·설정·계정 등).
|
|
@@ -4,6 +4,7 @@ import { useCallback, useId, useMemo, useState, useRef, useEffect } from 'react'
|
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import './BottomNav.css';
|
|
6
6
|
import Button from './Button';
|
|
7
|
+
import Badge from './Badge';
|
|
7
8
|
import MenuTooltip from './Tooltip/MenuTooltip';
|
|
8
9
|
// .tooltip-content(메뉴 표면)를 직접 쓰므로 정의 파일을 명시적으로 가져온다.
|
|
9
10
|
// Tooltip.tsx만 이 CSS를 import하는데 BottomNav는 그 컴포넌트를 거치지 않아,
|
|
@@ -13,6 +14,8 @@ import { useAnchoredMenu } from '../hooks/useAnchoredMenu';
|
|
|
13
14
|
import { useTranslation } from 'react-i18next';
|
|
14
15
|
const BTN_SIZE = 52;
|
|
15
16
|
const BTN_GAP = 10;
|
|
17
|
+
/** 배지 숫자 — SideNav·MenuTooltip 과 같이 99 에서 자른다. 0 이하·없음은 0. */
|
|
18
|
+
const badgeOf = (n) => (n && n > 0 ? n : 0);
|
|
16
19
|
/** @public */
|
|
17
20
|
export default function BottomNav({ items = [], activeKey, onChange, appMode, onAppModeChange, moreItems = [], }) {
|
|
18
21
|
const { t } = useTranslation();
|
|
@@ -59,6 +62,8 @@ export default function BottomNav({ items = [], activeKey, onChange, appMode, on
|
|
|
59
62
|
onScroll: 'ignore',
|
|
60
63
|
onResize: 'ignore',
|
|
61
64
|
});
|
|
65
|
+
/* 단추 이름에 개수를 붙인다 — 배지는 aria-hidden 이라 개수는 이름으로만 전해진다. */
|
|
66
|
+
const nameWithCount = useCallback((label, count) => count > 0 ? t('nav.badgeLabel', { label, count, defaultValue: '{{label}}, {{count}}개' }) : label, [t]);
|
|
62
67
|
const handleModeChange = useCallback(() => onAppModeChange?.(nextMode), [onAppModeChange, nextMode]);
|
|
63
68
|
// 넘친 nav 항목 또는 유틸 항목이 현재 활성 상태면 … 버튼에 그 아이콘/선택표시를 반영
|
|
64
69
|
const activeMore = useMemo(() => hiddenItems.find(it => it.key === activeKey) ?? moreItems.find(it => it.selected || (it.key && it.key === activeKey)), [hiddenItems, activeKey, moreItems]);
|
|
@@ -68,6 +73,7 @@ export default function BottomNav({ items = [], activeKey, onChange, appMode, on
|
|
|
68
73
|
...hiddenItems.map(it => ({
|
|
69
74
|
label: it.label,
|
|
70
75
|
icon: it.icon,
|
|
76
|
+
badgeCount: it.badgeCount,
|
|
71
77
|
selected: it.key === activeKey,
|
|
72
78
|
closeOnSelect: true,
|
|
73
79
|
onClick: () => {
|
|
@@ -89,5 +95,10 @@ export default function BottomNav({ items = [], activeKey, onChange, appMode, on
|
|
|
89
95
|
})),
|
|
90
96
|
]
|
|
91
97
|
}], [hiddenItems, moreItems, activeKey, onChange, handleCloseMore]);
|
|
92
|
-
|
|
98
|
+
// "…" 단추 배지 — 메뉴 안에서만 보이는 개수(넘친 항목 + 유틸)를 닫힌 채로도 알린다.
|
|
99
|
+
const moreBadge = [...hiddenItems, ...moreItems].reduce((sum, it) => sum + badgeOf(it.badgeCount), 0);
|
|
100
|
+
return (_jsxs("nav", { className: "bottom-nav", "aria-label": t('nav.bottomNav', '하단 네비게이션'), children: [onAppModeChange && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bottom-nav__left", children: _jsx(Button, { size: 52, type: "dot", icon: modeIcon, ariaLabel: modeLabel, className: "bottom-nav-item", onClick: handleModeChange }) }), _jsx("div", { className: "bottom-nav__divider", "aria-hidden": "true" })] })), _jsx("div", { className: "bottom-nav__items", ref: itemsRef, children: visibleItems.map((it) => {
|
|
101
|
+
const count = badgeOf(it.badgeCount);
|
|
102
|
+
return (_jsxs("span", { className: "bottom-nav__slot", children: [_jsx(Button, { size: 52, type: "dot", icon: it.icon, ariaLabel: nameWithCount(it.label, count), selected: it.key === activeKey, disabled: it.state === 'disabled', className: "bottom-nav-item", onClick: () => it.key && onChange?.(it.key) }), count > 0 && (_jsx("span", { "aria-hidden": "true", children: _jsx(Badge, { type: "dot", position: "top-right", number: Math.min(count, 99), size: 24 }) }))] }, it.key));
|
|
103
|
+
}) }), showMore && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bottom-nav__divider", "aria-hidden": "true" }), _jsx("div", { className: "bottom-nav__right", children: _jsxs("div", { ref: moreRef, className: "bottom-nav__slot", children: [_jsx(Button, { size: 52, type: "dot", icon: activeMore?.icon ?? 'ellipsis', ariaLabel: nameWithCount(activeMore?.label ?? t('nav.more', '더보기'), moreBadge), selected: !!activeMore, className: "bottom-nav-item", onClick: handleToggleMore, "aria-expanded": moreOpen, "aria-controls": moreOpen ? moreMenuId : undefined }), moreBadge > 0 && (_jsx("span", { "aria-hidden": "true", children: _jsx(Badge, { type: "dot", position: "top-right", number: Math.min(moreBadge, 99), size: 24 }) })), moreOpen && createPortal(_jsx("div", { id: moreMenuId, role: "group", "aria-label": t('nav.moreMenu', '더보기 메뉴'), className: "tooltip-content", style: menuStyle, ref: menuRef, children: _jsx(MenuTooltip, { columns: moreColumns, onClose: handleCloseMore }) }), document.body)] }) })] }))] }));
|
|
93
104
|
}
|
|
@@ -47,7 +47,8 @@ export default function Header({ activeKey, onChange, items, malgwiItems: _malgw
|
|
|
47
47
|
캐스팅만 해 넘겨, 두 타입의 이름이 다른 칸이 조용히 버려졌다 — `disabled` 가 `state` 로
|
|
48
48
|
안 가서 **비활성 항목이 모바일에서 눌렸고**, `href` 항목은 새 창 대신 `onChange(key)` 로
|
|
49
49
|
갔다. SideNav 가 거르는 구획(section)·빈 항목도 같이 거른다.
|
|
50
|
-
|
|
50
|
+
`badge` 는 `badgeCount` 로 옮긴다 — BottomNav 가 단추 모서리(넘치면 "…" 단추와 메뉴 항목)에
|
|
51
|
+
SideNav 와 같은 원형 숫자 배지로 그린다. 예전엔 받을 자리가 없어 숙제 개수가 모바일에서 안 보였다. */
|
|
51
52
|
const bottomItems = useMemo(() => (items ?? [])
|
|
52
53
|
.filter(it => !it.section && (it.key || it.label))
|
|
53
54
|
.map(it => ({
|
|
@@ -55,6 +56,7 @@ export default function Header({ activeKey, onChange, items, malgwiItems: _malgw
|
|
|
55
56
|
label: typeof it.label === 'string' ? it.label : undefined,
|
|
56
57
|
icon: it.icon,
|
|
57
58
|
state: it.disabled ? 'disabled' : undefined,
|
|
59
|
+
badgeCount: it.badge,
|
|
58
60
|
})), [items]);
|
|
59
61
|
const handleBottomChange = useCallback((key) => {
|
|
60
62
|
const it = items?.find(i => i.key === key);
|
|
@@ -37,3 +37,36 @@
|
|
|
37
37
|
-webkit-mask: var(--symbol-url) center / contain no-repeat;
|
|
38
38
|
mask: var(--symbol-url) center / contain no-repeat;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/* ── PassCardBg 컴포넌트 자체의 변형(default · blue · dark) ─────────────────
|
|
42
|
+
9종 배경은 각자 파일에 모양을 두고, 이 기본 컴포넌트의 원 셋과 변형은 규칙이 없어
|
|
43
|
+
세 변형이 똑같이(빈 바탕으로) 그려졌다(2026-09-14, 사용자가 정하도록 맡김).
|
|
44
|
+
**새 색은 쓰지 않는다** — 원 구도는 PassCardBgTwoCircle 과 같게(큰 원 우상단 · 작은 원
|
|
45
|
+
우하단)에 좌하단 원 하나를 더하고, 색은 브랜드·중립 토큰만 쓴다. 만료 카드
|
|
46
|
+
(.pass-card--expired)가 --color-main/sub 를 회색으로 바꾸는 규칙도 그대로 따라온다. */
|
|
47
|
+
.pass-card-bg__circle {
|
|
48
|
+
position: absolute;
|
|
49
|
+
border-radius: 50%;
|
|
50
|
+
opacity: var(--pass-card-symbol-opacity, 0.75);
|
|
51
|
+
}
|
|
52
|
+
.pass-card-bg__circle--1 { width: 340px; height: 340px; top: -140px; right: -150px; }
|
|
53
|
+
.pass-card-bg__circle--2 { width: 220px; height: 220px; bottom: -90px; right: -50px; }
|
|
54
|
+
.pass-card-bg__circle--3 { width: 140px; height: 140px; bottom: -70px; left: -40px; }
|
|
55
|
+
|
|
56
|
+
/* default — 흰 바탕 + main · sub 원(TwoCircle 과 같은 결) */
|
|
57
|
+
.pass-card-bg--default { background: var(--color-pureWhite); }
|
|
58
|
+
.pass-card-bg--default .pass-card-bg__circle--1 { background: var(--color-main); }
|
|
59
|
+
.pass-card-bg--default .pass-card-bg__circle--2 { background: var(--color-sub); }
|
|
60
|
+
.pass-card-bg--default .pass-card-bg__circle--3 { background: var(--color-main-a20); }
|
|
61
|
+
|
|
62
|
+
/* blue — 짙은 main 바탕 + 흰 반투명 원 */
|
|
63
|
+
.pass-card-bg--blue { background: var(--color-deepMain); }
|
|
64
|
+
.pass-card-bg--blue .pass-card-bg__circle--1 { background: var(--color-white-a30); }
|
|
65
|
+
.pass-card-bg--blue .pass-card-bg__circle--2 { background: var(--color-white-a10); }
|
|
66
|
+
.pass-card-bg--blue .pass-card-bg__circle--3 { background: var(--color-white-a10); }
|
|
67
|
+
|
|
68
|
+
/* dark — 검은 바탕 + main · sub 원 */
|
|
69
|
+
.pass-card-bg--dark { background: var(--color-softBlack); }
|
|
70
|
+
.pass-card-bg--dark .pass-card-bg__circle--1 { background: var(--color-main); }
|
|
71
|
+
.pass-card-bg--dark .pass-card-bg__circle--2 { background: var(--color-sub); }
|
|
72
|
+
.pass-card-bg--dark .pass-card-bg__circle--3 { background: var(--color-white-a10); }
|
|
@@ -6,28 +6,22 @@ export declare const TRAINING_TYPES: {
|
|
|
6
6
|
icon: string;
|
|
7
7
|
desc: string;
|
|
8
8
|
}[];
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
key: string;
|
|
17
|
-
label: string;
|
|
18
|
-
}[];
|
|
19
|
-
desc?: undefined;
|
|
20
|
-
} | {
|
|
9
|
+
/**
|
|
10
|
+
* 주제·세부 주제 목록(열쇠·이름·아이콘·색). **설명(desc)은 두지 않는다** — 코어·앱 어디에서도 주제 설명을
|
|
11
|
+
* 그리지 않고, 한때 7개 주제에 붙어 있던 '소음 환경 속 문장 이해 훈련' 은 훈련 유형(소음 하 문장 듣기)의
|
|
12
|
+
* 설명을 옮겨 붙인 값이었다(2026-09-14 걷음). 화면에 보이는 설명은 훈련 유형(`TRAINING_TYPES`)의 것이다.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export declare const TOPIC_TYPES: {
|
|
21
16
|
key: string;
|
|
22
17
|
label: string;
|
|
23
18
|
icon: string;
|
|
24
|
-
desc: string;
|
|
25
19
|
color: string;
|
|
26
20
|
subs: {
|
|
27
21
|
key: string;
|
|
28
22
|
label: string;
|
|
29
23
|
}[];
|
|
30
|
-
}
|
|
24
|
+
}[];
|
|
31
25
|
/** 훈련 유형 i18n 키 매핑 */
|
|
32
26
|
export declare const TRAINING_TYPE_I18N_KEY: Record<string, string>;
|
|
33
27
|
/** 번역된 훈련 유형 레이블/설명 반환 */
|
|
@@ -38,7 +32,7 @@ export declare function translateTrainingTypes(t: TFunction): {
|
|
|
38
32
|
desc: string;
|
|
39
33
|
}[];
|
|
40
34
|
/** 번역된 토픽/서브토픽 레이블 반환 — 컴포넌트에서 useMemo와 함께 사용 */
|
|
41
|
-
export declare function translateTopicTypes(t: TFunction):
|
|
35
|
+
export declare function translateTopicTypes(t: TFunction): {
|
|
42
36
|
label: string;
|
|
43
37
|
subs: {
|
|
44
38
|
label: string;
|
|
@@ -47,18 +41,7 @@ export declare function translateTopicTypes(t: TFunction): ({
|
|
|
47
41
|
key: string;
|
|
48
42
|
icon: string;
|
|
49
43
|
color: string;
|
|
50
|
-
|
|
51
|
-
} | {
|
|
52
|
-
label: string;
|
|
53
|
-
subs: {
|
|
54
|
-
label: string;
|
|
55
|
-
key: string;
|
|
56
|
-
}[];
|
|
57
|
-
key: string;
|
|
58
|
-
icon: string;
|
|
59
|
-
desc: string;
|
|
60
|
-
color: string;
|
|
61
|
-
})[];
|
|
44
|
+
}[];
|
|
62
45
|
declare const _default: {
|
|
63
46
|
TRAINING_TYPES: {
|
|
64
47
|
key: string;
|
|
@@ -66,7 +49,7 @@ declare const _default: {
|
|
|
66
49
|
icon: string;
|
|
67
50
|
desc: string;
|
|
68
51
|
}[];
|
|
69
|
-
TOPIC_TYPES:
|
|
52
|
+
TOPIC_TYPES: {
|
|
70
53
|
key: string;
|
|
71
54
|
label: string;
|
|
72
55
|
icon: string;
|
|
@@ -75,17 +58,6 @@ declare const _default: {
|
|
|
75
58
|
key: string;
|
|
76
59
|
label: string;
|
|
77
60
|
}[];
|
|
78
|
-
|
|
79
|
-
} | {
|
|
80
|
-
key: string;
|
|
81
|
-
label: string;
|
|
82
|
-
icon: string;
|
|
83
|
-
desc: string;
|
|
84
|
-
color: string;
|
|
85
|
-
subs: {
|
|
86
|
-
key: string;
|
|
87
|
-
label: string;
|
|
88
|
-
}[];
|
|
89
|
-
})[];
|
|
61
|
+
}[];
|
|
90
62
|
};
|
|
91
63
|
export default _default;
|
|
@@ -5,18 +5,23 @@ export const TRAINING_TYPES = [
|
|
|
5
5
|
{ key: "LONG_STORY", label: "긴 이야기 듣기", icon: "message-square-text", desc: "긴 이야기를 듣고 기억하여 다수의 문제를 풀어봅니다." },
|
|
6
6
|
{ key: "ORDERING_SENTENCE", label: "문장 순서화 하기", icon: "align-start-vertical", desc: "여러개의 짧은 문장을 듣고 기억하여 그 순서에 맞게 배열합니다." },
|
|
7
7
|
];
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* 주제·세부 주제 목록(열쇠·이름·아이콘·색). **설명(desc)은 두지 않는다** — 코어·앱 어디에서도 주제 설명을
|
|
10
|
+
* 그리지 않고, 한때 7개 주제에 붙어 있던 '소음 환경 속 문장 이해 훈련' 은 훈련 유형(소음 하 문장 듣기)의
|
|
11
|
+
* 설명을 옮겨 붙인 값이었다(2026-09-14 걷음). 화면에 보이는 설명은 훈련 유형(`TRAINING_TYPES`)의 것이다.
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
9
14
|
export const TOPIC_TYPES = [
|
|
10
15
|
{ key: 'FOOD', label: '음식', icon: 'salad', color: '#FF8A65', subs: [{ key: 'TRKRFD', label: '한국 전통음식' }, { key: 'MDKRFD', label: '한국 현대음식' }, { key: 'WRLDFD', label: '세계의 음식' }, { key: 'MTRFRT', label: '식재료와 과일' }, { key: 'GEFDKL', label: '음식 상식' }] },
|
|
11
16
|
{ key: 'HLTH', label: '건강', icon: 'heart', color: '#66BB6A', subs: [{ key: 'HTETHB', label: '건강한 식습관' }, { key: 'DISILL', label: '병과 질환' }, { key: 'EXCISE', label: '운동' }, { key: 'GEHTKL', label: '건강상식' }] },
|
|
12
17
|
{ key: 'WETH', label: '날씨', icon: 'sun-medium', color: '#42A5F5', subs: [{ key: 'GESSKL', label: '계절상식' }, { key: 'SNWRAN', label: '눈과 비' }, { key: 'WTHFCT', label: '일기예보' }, { key: 'GEWTKL', label: '날씨상식' }] },
|
|
13
|
-
{ key: 'PRSN', label: '인물', icon: 'user-round',
|
|
14
|
-
{ key: 'KRCT', label: '한국문화', icon: 'bow-arrow',
|
|
15
|
-
{ key: 'STRY', label: '이야기', icon: 'message-circle',
|
|
16
|
-
{ key: 'LOCA', label: '지역', icon: 'map-pin',
|
|
17
|
-
{ key: 'SOAR', label: '사회와 예술', icon: 'landmark',
|
|
18
|
-
{ key: 'NTSC', label: '자연과 과학', icon: 'leaf',
|
|
19
|
-
{ key: 'SPRT', label: '스포츠', icon: 'trophy',
|
|
18
|
+
{ key: 'PRSN', label: '인물', icon: 'user-round', color: '#AB47BC', subs: [{ key: 'JODYPS', label: '조선시대' }, { key: 'MDKRPS', label: '한국 근현대' }, { key: 'THKDPS', label: '삼국시대' }, { key: 'WRLART', label: '세계의 예술가' }, { key: 'WRLPSN', label: '세계의 인물' }] },
|
|
19
|
+
{ key: 'KRCT', label: '한국문화', icon: 'bow-arrow', color: '#EC407A', subs: [{ key: 'KRLANG', label: '우리말' }, { key: 'TRPLCT', label: '전통놀이' }, { key: 'HERITG', label: '문화재' }, { key: 'EVTFES', label: '행사와 축제' }, { key: 'GECTKL', label: '상식' }] },
|
|
20
|
+
{ key: 'STRY', label: '이야기', icon: 'message-circle', color: '#8D6E63', subs: [{ key: 'FRYTAL', label: '동화' }, { key: 'AMZING', label: '신기한 이야기' }, { key: 'DAYSTR', label: '일상 속 이야기' }] },
|
|
21
|
+
{ key: 'LOCA', label: '지역', icon: 'map-pin', color: '#26A69A', subs: [{ key: 'KRLDMK', label: '한국의 명소' }, { key: 'SELNMK', label: '서울의 명소' }, { key: 'WRDTRB', label: '세계여행' }, { key: 'NAVIGT', label: '길 찾기' }] },
|
|
22
|
+
{ key: 'SOAR', label: '사회와 예술', icon: 'landmark', color: '#5C6BC0', subs: [{ key: 'KRSOCI', label: '한국' }, { key: 'ECONEW', label: '경제와 뉴스' }, { key: 'GENART', label: '예술' }, { key: 'GELFKL', label: '생활 상식' }] },
|
|
23
|
+
{ key: 'NTSC', label: '자연과 과학', icon: 'leaf', color: '#9CCC65', subs: [{ key: 'ANIMAL', label: '동물' }, { key: 'PLANTS', label: '식물' }, { key: 'ETHPLT', label: '지구' }, { key: 'TOLELC', label: '도구와 전자제품' }, { key: 'GESCKL', label: '과학상식' }] },
|
|
24
|
+
{ key: 'SPRT', label: '스포츠', icon: 'trophy', color: '#FFCA28', subs: [{ key: 'INVDSP', label: '개인경기' }, { key: 'MARART', label: '격투기' }, { key: 'TEAMSP', label: '단체경기' }, { key: 'FOOTBL', label: '축구' }, { key: 'GESPKL', label: '스포츠 상식' }] },
|
|
20
25
|
];
|
|
21
26
|
/** 훈련 유형 i18n 키 매핑 */
|
|
22
27
|
export const TRAINING_TYPE_I18N_KEY = {
|