@ihabdevteam/core 0.245.0 → 0.247.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 +54 -0
- package/dist/components.cjs.js +171 -171
- package/dist/hooks.cjs.js +3 -3
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +22 -22
- package/dist/ihabdevteam-core.d.ts +21 -3
- package/dist/packages/core/src/components/FilterPanel.d.ts +11 -1
- package/dist/packages/core/src/components/FilterPanel.js +10 -3
- package/dist/packages/core/src/components/Panel.d.ts +9 -1
- package/dist/packages/core/src/components/Panel.js +3 -2
- package/dist/packages/core/src/components/Training/CategoryIcon.js +3 -1
- package/dist/packages/core/src/i18n/locales/en.json +1 -0
- package/dist/packages/core/src/i18n/locales/ko.json +1 -0
- 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
|
@@ -1645,7 +1645,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1645
1645
|
*
|
|
1646
1646
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1647
1647
|
*/
|
|
1648
|
-
export declare const CORE_VERSION = "0.
|
|
1648
|
+
export declare const CORE_VERSION = "0.247.0";
|
|
1649
1649
|
|
|
1650
1650
|
/** @public */
|
|
1651
1651
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -2369,7 +2369,7 @@ export declare const FILTER_ALL_VALUE = "\uC804\uCCB4";
|
|
|
2369
2369
|
export declare function filterContrastCandidates(answer: string, candidates: Iterable<string>, opts: ContrastFilterOpts): string[];
|
|
2370
2370
|
|
|
2371
2371
|
/** @public */
|
|
2372
|
-
export declare function FilterPanel({ title, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): JSX.Element;
|
|
2372
|
+
export declare function FilterPanel({ title, titleLevel, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): JSX.Element;
|
|
2373
2373
|
|
|
2374
2374
|
/** @public */
|
|
2375
2375
|
export declare interface FilterPanelItem {
|
|
@@ -2390,6 +2390,16 @@ export declare interface FilterPanelItem {
|
|
|
2390
2390
|
/** @public */
|
|
2391
2391
|
export declare interface FilterPanelProps {
|
|
2392
2392
|
title?: ReactNode;
|
|
2393
|
+
/** 제목을 **머리글 몇 급**으로 그릴지(말귀 요청 (57), 2026-10-02).
|
|
2394
|
+
*
|
|
2395
|
+
* 전에는 `<span class="h4">` 였다 — `h4` 는 글자 꼴이지 머리글이 아니다. 그래서 판이
|
|
2396
|
+
* 셋 서는 화면(단어표 만들기)에 **머리글이 `h1` 하나뿐**이고 그 아래 조작 백예순다섯이
|
|
2397
|
+
* 평평하게 깔렸다. "단어표 설정으로 건너뛰기" 도 "지금 어느 판 안인지" 도 알 길이
|
|
2398
|
+
* 없었다 — 자모 격자만 백 개가 넘는 자리라 더 그렇다.
|
|
2399
|
+
*
|
|
2400
|
+
* 기본 2 — 쪽 제목(`h1`) 바로 아래 서는 자리가 대부분이다. 창 안이면 3 으로 내린다
|
|
2401
|
+
* (창 제목이 `h2` 다). `0` 이면 전과 같이 `span` 으로 그린다. */
|
|
2402
|
+
titleLevel?: 0 | 2 | 3 | 4 | 5 | 6;
|
|
2393
2403
|
headerAction?: ReactNode;
|
|
2394
2404
|
/** 머리 오른쪽에 **접혀도 남는** 자리 — 접힌 판이 제 속을 한 줄로 말하게 한다("등급 전체 · 품사 전체" 같은 요약).
|
|
2395
2405
|
*
|
|
@@ -4179,7 +4189,7 @@ export declare interface PairStats {
|
|
|
4179
4189
|
total: number;
|
|
4180
4190
|
}
|
|
4181
4191
|
|
|
4182
|
-
export declare function Panel({ title, icon, disc, actions, size, minHeight, maxWidth, className, scrollable, scrollbarGutter, compactRows, useBorder, useBg, useRowBorder, labelWidth, form, loading, empty, children }: PanelProps): JSX.Element;
|
|
4192
|
+
export declare function Panel({ title, titleLevel, icon, disc, actions, size, minHeight, maxWidth, className, scrollable, scrollbarGutter, compactRows, useBorder, useBg, useRowBorder, labelWidth, form, loading, empty, children }: PanelProps): JSX.Element;
|
|
4183
4193
|
|
|
4184
4194
|
/** @public */
|
|
4185
4195
|
export declare type PanelElement = HTMLDivElement & {
|
|
@@ -4194,6 +4204,14 @@ export declare function PanelGrid({ cols, children }: {
|
|
|
4194
4204
|
}): JSX.Element;
|
|
4195
4205
|
|
|
4196
4206
|
declare interface PanelProps {
|
|
4207
|
+
/** 제목을 **머리글 몇 급**으로 그릴지(말귀 요청 (59), 2026-10-02).
|
|
4208
|
+
*
|
|
4209
|
+
* 기본 2 는 전과 같다 — **창 제목이 머리글이 아니던 때**의 값이다. 0.242.0 에서 창
|
|
4210
|
+
* 제목이 `<h2>` 가 되면서, 제목 있는 `Panel` 을 품은 창마다 둘이 **같은 급**으로 서게
|
|
4211
|
+
* 됐다(말귀 실측: 훈련 묶음 추가 창에 창 제목 말고 `h2` 가 둘). 머리글로 훑으면 판이
|
|
4212
|
+
* 창의 아래가 아니라 **옆**으로 읽힌다 — 창 안에서는 3 으로 내린다.
|
|
4213
|
+
* `0` 이면 머리글을 안 세운다(글자 없는 제목은 0.235.0 부터 애초에 안 선다). */
|
|
4214
|
+
titleLevel?: 0 | 2 | 3 | 4 | 5 | 6;
|
|
4197
4215
|
title?: ReactNode;
|
|
4198
4216
|
/**
|
|
4199
4217
|
* 제목 앞에 서는 lucide 아이콘 이름. Section 의 `icon` 과 같은 자리다.
|
|
@@ -6,6 +6,16 @@ import type { FilterPanelSection } from '../types/filterPanel';
|
|
|
6
6
|
/** @public */
|
|
7
7
|
export interface FilterPanelProps {
|
|
8
8
|
title?: ReactNode;
|
|
9
|
+
/** 제목을 **머리글 몇 급**으로 그릴지(말귀 요청 (57), 2026-10-02).
|
|
10
|
+
*
|
|
11
|
+
* 전에는 `<span class="h4">` 였다 — `h4` 는 글자 꼴이지 머리글이 아니다. 그래서 판이
|
|
12
|
+
* 셋 서는 화면(단어표 만들기)에 **머리글이 `h1` 하나뿐**이고 그 아래 조작 백예순다섯이
|
|
13
|
+
* 평평하게 깔렸다. "단어표 설정으로 건너뛰기" 도 "지금 어느 판 안인지" 도 알 길이
|
|
14
|
+
* 없었다 — 자모 격자만 백 개가 넘는 자리라 더 그렇다.
|
|
15
|
+
*
|
|
16
|
+
* 기본 2 — 쪽 제목(`h1`) 바로 아래 서는 자리가 대부분이다. 창 안이면 3 으로 내린다
|
|
17
|
+
* (창 제목이 `h2` 다). `0` 이면 전과 같이 `span` 으로 그린다. */
|
|
18
|
+
titleLevel?: 0 | 2 | 3 | 4 | 5 | 6;
|
|
9
19
|
headerAction?: ReactNode;
|
|
10
20
|
/** 머리 오른쪽에 **접혀도 남는** 자리 — 접힌 판이 제 속을 한 줄로 말하게 한다("등급 전체 · 품사 전체" 같은 요약).
|
|
11
21
|
*
|
|
@@ -49,4 +59,4 @@ export interface FilterPanelProps {
|
|
|
49
59
|
className?: string;
|
|
50
60
|
}
|
|
51
61
|
/** @public */
|
|
52
|
-
export default function FilterPanel({ title, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): import("react").JSX.Element;
|
|
62
|
+
export default function FilterPanel({ title, titleLevel, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { cx } from '../utils/cx';
|
|
4
|
-
import { Fragment, useMemo, useState } from 'react';
|
|
4
|
+
import { useId, Fragment, useMemo, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import './FilterPanel.css';
|
|
7
7
|
import Button from './Button';
|
|
@@ -14,7 +14,7 @@ import FilterSectionInfo from './FilterSectionInfo';
|
|
|
14
14
|
import { devWarnOnce } from '../utils/devWarn';
|
|
15
15
|
import { buildFilterSummary } from '../utils/filterPanelSummary';
|
|
16
16
|
/** @public */
|
|
17
|
-
export default function FilterPanel({ title, headerAction, headerSummary, collapsible = false, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected = {}, onSelect, align, sectionAlign, className, }) {
|
|
17
|
+
export default function FilterPanel({ title, titleLevel = 2, headerAction, headerSummary, collapsible = false, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected = {}, onSelect, align, sectionAlign, className, }) {
|
|
18
18
|
// align 우선. 없으면 구 prop(sectionAlign) 매핑, 그것도 없으면 inline 기본.
|
|
19
19
|
const isRow = (align ?? (sectionAlign === 'vertical' ? 'newline' : sectionAlign === 'horizontal' ? 'inline' : 'inline')) === 'inline';
|
|
20
20
|
const { t } = useTranslation();
|
|
@@ -47,7 +47,14 @@ export default function FilterPanel({ title, headerAction, headerSummary, collap
|
|
|
47
47
|
const 기본요약 = useMemo(() => (headerSummary !== undefined || !collapsed ? '' : buildFilterSummary(sections, selected, t('common.all', '전체'))), [headerSummary, collapsed, sections, selected, t]);
|
|
48
48
|
const summaryNode = headerSummary ?? (기본요약 ? _jsx("span", { className: "filter-panel__header-summary body-3", children: 기본요약 }) : null);
|
|
49
49
|
const showHeader = title || headerAction || headerSummary || collapsible;
|
|
50
|
-
|
|
50
|
+
const 제목id = useId();
|
|
51
|
+
const 제목태그 = titleLevel ? `h${titleLevel}` : 'span';
|
|
52
|
+
return (
|
|
53
|
+
/* **제목이 이 판의 이름이 된다**(말귀 요청 (57), 2026-10-02). 이름 없는 판은 지역으로
|
|
54
|
+
안 잡혀, 셋이 나란히 서도 "지금 어느 판 안인지" 를 알 길이 없었다 — (51)·(54) 에서
|
|
55
|
+
`ChartSection`·`TrainingCardGroup` 에 한 것과 같은 자다.
|
|
56
|
+
**글자 제목일 때만 건다** — 마디를 가리키므로 글이 아닌 제목이면 이름이 안 선다. */
|
|
57
|
+
_jsxs("div", { className: cx('filter-panel', collapsible ? 'filter-panel--collapsible' : '', collapsed ? 'filter-panel--collapsed' : '', className), role: typeof title === 'string' && title ? 'group' : undefined, "aria-labelledby": typeof title === 'string' && title ? 제목id : undefined, children: [showHeader && (_jsxs("div", { className: "filter-panel__header", onClick: collapsible ? 머리누름 : undefined, children: [_jsx("div", { className: "filter-panel__title-row", children: title && _jsx(제목태그, { id: 제목id, className: "filter-panel__title h4", children: title }) }), _jsxs("div", { className: "filter-panel__header-actions", children: [collapsed ? summaryNode : headerAction, collapsible && (_jsx(Button, { htmlType: "button", size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: collapsed ? 'chevron-down' : 'chevron-up',
|
|
51
58
|
/* Button은 aria-label을 자기 ariaLabel prop으로 덮어쓴다({...rest} 뒤에
|
|
52
59
|
aria-label={ariaLabelString}을 다시 쓴다) — kebab으로 넘기면 undefined가
|
|
53
60
|
되어 이름이 통째로 사라진다. 저장소의 다른 호출부는 모두 ariaLabel을 쓴다.
|
|
@@ -2,6 +2,14 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
import './Panel.css';
|
|
3
3
|
type PanelSize = 'small' | 'medium' | 'large';
|
|
4
4
|
interface PanelProps {
|
|
5
|
+
/** 제목을 **머리글 몇 급**으로 그릴지(말귀 요청 (59), 2026-10-02).
|
|
6
|
+
*
|
|
7
|
+
* 기본 2 는 전과 같다 — **창 제목이 머리글이 아니던 때**의 값이다. 0.242.0 에서 창
|
|
8
|
+
* 제목이 `<h2>` 가 되면서, 제목 있는 `Panel` 을 품은 창마다 둘이 **같은 급**으로 서게
|
|
9
|
+
* 됐다(말귀 실측: 훈련 묶음 추가 창에 창 제목 말고 `h2` 가 둘). 머리글로 훑으면 판이
|
|
10
|
+
* 창의 아래가 아니라 **옆**으로 읽힌다 — 창 안에서는 3 으로 내린다.
|
|
11
|
+
* `0` 이면 머리글을 안 세운다(글자 없는 제목은 0.235.0 부터 애초에 안 선다). */
|
|
12
|
+
titleLevel?: 0 | 2 | 3 | 4 | 5 | 6;
|
|
5
13
|
title?: ReactNode;
|
|
6
14
|
/**
|
|
7
15
|
* 제목 앞에 서는 lucide 아이콘 이름. Section 의 `icon` 과 같은 자리다.
|
|
@@ -54,7 +62,7 @@ interface PanelProps {
|
|
|
54
62
|
empty?: ReactNode;
|
|
55
63
|
children?: ReactNode;
|
|
56
64
|
}
|
|
57
|
-
export default function Panel({ title, icon, disc, actions, size, minHeight, maxWidth, className, scrollable, scrollbarGutter, compactRows, useBorder, useBg, useRowBorder, labelWidth, form, loading, empty, children }: PanelProps): import("react").JSX.Element;
|
|
65
|
+
export default function Panel({ title, titleLevel, icon, disc, actions, size, minHeight, maxWidth, className, scrollable, scrollbarGutter, compactRows, useBorder, useBg, useRowBorder, labelWidth, form, loading, empty, children }: PanelProps): import("react").JSX.Element;
|
|
58
66
|
/** @public */
|
|
59
67
|
export declare function PanelGrid({ cols, children }: {
|
|
60
68
|
cols?: number;
|
|
@@ -5,7 +5,7 @@ import { PanelRow } from './PanelRow';
|
|
|
5
5
|
import { Children } from 'react';
|
|
6
6
|
import Icon from './Icon';
|
|
7
7
|
import './Panel.css';
|
|
8
|
-
export default function Panel({ title, icon, disc, actions, size = 'medium', minHeight, maxWidth, className, scrollable, scrollbarGutter = 'auto', compactRows = false, useBorder = true, useBg = true, useRowBorder = true, labelWidth, form = false, loading, empty, children }) {
|
|
8
|
+
export default function Panel({ title, titleLevel = 2, icon, disc, actions, size = 'medium', minHeight, maxWidth, className, scrollable, scrollbarGutter = 'auto', compactRows = false, useBorder = true, useBg = true, useRowBorder = true, labelWidth, form = false, loading, empty, children }) {
|
|
9
9
|
const cls = cx('panel', `panel--${size}`, form ? 'panel--form' : '',
|
|
10
10
|
/* minHeight 패널은 마지막 행 아래 빈 공간이 생길 수 있으므로 행 하단 구분선 사용 */
|
|
11
11
|
minHeight !== undefined ? 'panel--fill' : '', !useBorder ? 'panel--no-border' : '', !useBg ? 'panel--no-bg' : '', !useRowBorder ? 'panel--no-row-border' : '', compactRows ? 'panel--compact-rows' : '', className);
|
|
@@ -16,7 +16,8 @@ export default function Panel({ title, icon, disc, actions, size = 'medium', min
|
|
|
16
16
|
style.maxWidth = typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth;
|
|
17
17
|
if (labelWidth !== undefined)
|
|
18
18
|
style['--panel-label-width'] = typeof labelWidth === 'number' ? `${labelWidth}px` : labelWidth;
|
|
19
|
-
|
|
19
|
+
const 제목태그 = titleLevel ? `h${titleLevel}` : 'div';
|
|
20
|
+
return (_jsxs("div", { className: cls, style: Object.keys(style).length ? style : undefined, children: [(!!title || disc != null) && (_jsxs("div", { className: cx('panel__header', disc != null ? 'panel__header--with-disc' : ''), children: [icon && (_jsx(Icon, { icon: icon, label: typeof title === 'string' ? title : undefined, className: "panel__icon" })), _jsxs("div", { className: "panel__title-group", children: [!!title && (_jsx(제목태그, { className: `panel__title ${disc != null ? 'body-3b' : 'h5b'}`, children: title })), disc != null && _jsx("p", { className: "panel__disc caption-1", children: disc })] }), actions && _jsx("div", { className: "panel__actions", children: actions })] })), loading ? (_jsx(PanelRow, { variant: "loading" })
|
|
20
21
|
/* 비었는지는 React 가 그리는 것으로 센다 — `{조건 && 행들}` 이 false 면 children 이 false 라 빈 상태 문구가 안 떴다 */
|
|
21
22
|
) : empty != null && Children.toArray(children).length === 0 ? (_jsx(PanelRow, { variant: "empty", children: empty })) : scrollable ? (_jsx("div", { className: cx('panel__body--scroll', scrollbarGutter === 'stable' && 'panel__body--gutter-stable'), children: children })) : children] }));
|
|
22
23
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
3
4
|
import { cx } from '../../utils/cx';
|
|
4
5
|
import { useCallback, useRef, useState, useEffect, useLayoutEffect, useMemo } from "react";
|
|
5
6
|
import InfoTooltip from "../Tooltip/InfoTooltip";
|
|
@@ -7,6 +8,7 @@ import Icon from '../Icon';
|
|
|
7
8
|
import "./CategoryIcon.css";
|
|
8
9
|
/** @public */
|
|
9
10
|
export default function CategoryIcon({ icon, label, tooltip, wrapperStyle, color, bgColor, useHover = true, iconSize = 'medium' }) {
|
|
11
|
+
const { t } = useTranslation();
|
|
10
12
|
const isSmallIcon = iconSize === 'small';
|
|
11
13
|
const cls = cx('category-icon', isSmallIcon ? 'category-icon--small' : '');
|
|
12
14
|
const svgSize = isSmallIcon ? 16 : 20;
|
|
@@ -75,5 +77,5 @@ export default function CategoryIcon({ icon, label, tooltip, wrapperStyle, color
|
|
|
75
77
|
setHover(v => !v);
|
|
76
78
|
}
|
|
77
79
|
}, []);
|
|
78
|
-
return (_jsxs("span", { ref: ref, className: "category-icon__wrap", onPointerEnter: handleHoverOn, onPointerLeave: handleHoverOff, onFocus: handleHoverOn, onBlur: handleHoverOff, onKeyDown: handleKeyDown, role: "button", tabIndex: 0, "aria-label": typeof tipText === 'string' ? tipText : undefined, "aria-haspopup": !!tipText, "aria-expanded": hover, "data-tooltip-ignore": true, children: [_jsx(Icon, { icon: icon, label: label, className: cls, imgClassName: "category-icon__image", maskClassName: "category-icon__mask", svgClassName: "category-icon__svg", wrapperStyle: resolvedWrapperStyle, size: svgSize }), hover && tipText && (_jsx(InfoTooltip, { anchorRect: anchorRect, text: typeof tipText === 'string' ? tipText : undefined, offset: 8, onClose: handleTooltipClose, className: "category-icon__tooltip", ariaLabel: typeof tipText === 'string' ? tipText : undefined }))] }));
|
|
80
|
+
return (_jsxs("span", { ref: ref, className: "category-icon__wrap", onPointerEnter: handleHoverOn, onPointerLeave: handleHoverOff, onFocus: handleHoverOn, onBlur: handleHoverOff, onKeyDown: handleKeyDown, role: "button", tabIndex: 0, "aria-label": typeof tipText === 'string' ? t('common.descriptionFor', { name: tipText, defaultValue: `${tipText} 설명` }) : undefined, "aria-haspopup": !!tipText, "aria-expanded": hover, "data-tooltip-ignore": true, children: [_jsx(Icon, { icon: icon, label: label, className: cls, imgClassName: "category-icon__image", maskClassName: "category-icon__mask", svgClassName: "category-icon__svg", wrapperStyle: resolvedWrapperStyle, size: svgSize }), hover && tipText && (_jsx(InfoTooltip, { anchorRect: anchorRect, text: typeof tipText === 'string' ? tipText : undefined, offset: 8, onClose: handleTooltipClose, className: "category-icon__tooltip", ariaLabel: typeof tipText === 'string' ? tipText : undefined }))] }));
|
|
79
81
|
}
|