@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.
@@ -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.245.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
- return (_jsxs("div", { className: cx('filter-panel', collapsible ? 'filter-panel--collapsible' : '', collapsed ? 'filter-panel--collapsed' : '', className), children: [showHeader && (_jsxs("div", { className: "filter-panel__header", onClick: collapsible ? 머리누름 : undefined, children: [_jsx("div", { className: "filter-panel__title-row", children: title && _jsx("span", { 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',
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
- 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("h2", { 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" })
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
  }
@@ -30,6 +30,7 @@
30
30
  "likeCount": "{{count}} likes",
31
31
  "selectedOfMax": "{{count}} of {{max}}",
32
32
  "downloadCount": "{{count}} downloads",
33
+ "descriptionFor": "About {{name}}",
33
34
  "search": "Search",
34
35
  "empty": "No data.",
35
36
  "yes": "Yes",
@@ -28,6 +28,7 @@
28
28
  "download": "다운로드",
29
29
  "likeCount": "좋아요 {{count}}",
30
30
  "downloadCount": "가져간 수 {{count}}",
31
+ "descriptionFor": "{{name}} 설명",
31
32
  "search": "검색",
32
33
  "empty": "데이터가 없습니다.",
33
34
  "yes": "예",
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export declare const CORE_VERSION = "0.245.0";
49
+ export declare const CORE_VERSION = "0.247.0";
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export const CORE_VERSION = '0.245.0';
49
+ export const CORE_VERSION = '0.247.0';