@ihabdevteam/core 0.204.0 → 0.206.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 +81 -4
- package/dist/components.cjs.js +14 -14
- package/dist/components.esm.css +2 -2
- package/dist/hooks.cjs.js +1 -1
- package/dist/i18n.cjs.js +1 -1
- package/dist/ihabdevteam-core.cjs.js +20 -20
- package/dist/ihabdevteam-core.d.ts +71 -5
- package/dist/packages/core/src/components/FilterPanel.css +12 -0
- package/dist/packages/core/src/components/FilterPanel.d.ts +15 -2
- package/dist/packages/core/src/components/FilterPanel.js +19 -4
- package/dist/packages/core/src/components/Word/TableFilterPanel.d.ts +10 -0
- package/dist/packages/core/src/components/Word/TableFilterPanel.js +5 -3
- package/dist/packages/core/src/components/Word/WordSearchFilterPanel.d.ts +13 -1
- package/dist/packages/core/src/components/Word/WordSearchFilterPanel.js +4 -2
- package/dist/packages/core/src/components/Word/WordSearchResultBlock.css +40 -1
- package/dist/packages/core/src/components/Word/WordSearchResultBlock.js +6 -1
- package/dist/packages/core/src/components/Word/WordTableBlock.css +15 -0
- package/dist/packages/core/src/components/Word/WordTableBlock.d.ts +1 -1
- package/dist/packages/core/src/components/Word/WordTableBlock.js +3 -3
- package/dist/packages/core/src/components/Word/wordTableBlockTypes.d.ts +31 -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
|
@@ -1626,7 +1626,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1626
1626
|
*
|
|
1627
1627
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1628
1628
|
*/
|
|
1629
|
-
export declare const CORE_VERSION = "0.
|
|
1629
|
+
export declare const CORE_VERSION = "0.206.0";
|
|
1630
1630
|
|
|
1631
1631
|
/** @public */
|
|
1632
1632
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -2350,7 +2350,7 @@ export declare const FILTER_ALL_VALUE = "\uC804\uCCB4";
|
|
|
2350
2350
|
export declare function filterContrastCandidates(answer: string, candidates: Iterable<string>, opts: ContrastFilterOpts): string[];
|
|
2351
2351
|
|
|
2352
2352
|
/** @public */
|
|
2353
|
-
export declare function FilterPanel({ title, headerAction, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): JSX.Element;
|
|
2353
|
+
export declare function FilterPanel({ title, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): JSX.Element;
|
|
2354
2354
|
|
|
2355
2355
|
/** @public */
|
|
2356
2356
|
export declare interface FilterPanelItem {
|
|
@@ -2372,7 +2372,20 @@ export declare interface FilterPanelItem {
|
|
|
2372
2372
|
export declare interface FilterPanelProps {
|
|
2373
2373
|
title?: ReactNode;
|
|
2374
2374
|
headerAction?: ReactNode;
|
|
2375
|
-
/**
|
|
2375
|
+
/** 머리 오른쪽에 **접혀도 남는** 자리 — 접힌 판이 제 속을 한 줄로 말하게 한다("등급 전체 · 품사 전체" 같은 요약).
|
|
2376
|
+
*
|
|
2377
|
+
* `headerAction` 은 펼쳤을 때만 서고 접으면 사라진다(본문을 다루는 단추라 접힌 판에서는 할 일이 없다).
|
|
2378
|
+
* 그래서 접힌 판이 무엇을 담고 있는지 말할 자리가 `title` 밖에 없었고, 소비자가 제목 안에 요약을 싣고
|
|
2379
|
+
* CSS 로 오른쪽 끝까지 미는 억지를 썼다(말귀 단어표 만들기 2026-09-29).
|
|
2380
|
+
* **펼치면 그 자리를 `headerAction` 이 쓴다** — 둘은 같은 칸을 번갈아 쓴다.
|
|
2381
|
+
*
|
|
2382
|
+
* 요약 **글월을 짓는 셈은 부르는 쪽** 에 있다. 코어는 자리만 낸다 —
|
|
2383
|
+
* 여러 조각을 넣을 때는 묶음에 `role="group"` 과 한 줄짜리 `aria-label` 을 주고 낱개는 `aria-hidden` 으로
|
|
2384
|
+
* 숨기는 편이 낫다(낭독기가 조각마다 끊어 읽지 않게). */
|
|
2385
|
+
headerSummary?: ReactNode;
|
|
2386
|
+
/** 헤더 우측에 접기/펼치기 토글 버튼을 노출하고, 접으면 본문(섹션)을 숨긴다.
|
|
2387
|
+
* **머리 줄 전체가 손잡이다** — 머리 아무 데나 눌러도 접힌다(사용자 지시 2026-09-29).
|
|
2388
|
+
* `headerAction`·`headerSummary` 안의 단추·입력칸을 누른 것은 접지 않는다. */
|
|
2376
2389
|
collapsible?: boolean;
|
|
2377
2390
|
/** collapsible일 때 초기 접힘 상태 */
|
|
2378
2391
|
defaultCollapsed?: boolean;
|
|
@@ -7265,6 +7278,16 @@ export declare function TableFilterPanel(props: TableFilterPanelProps): JSX.Elem
|
|
|
7265
7278
|
declare interface TableFilterPanelBaseProps {
|
|
7266
7279
|
/** 패널 제목. 미지정 시 i18n(wordTable.filterTitle) 기본값 사용. */
|
|
7267
7280
|
title?: ReactNode;
|
|
7281
|
+
/** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다(`FilterPanel` 의 것). */
|
|
7282
|
+
collapsible?: boolean;
|
|
7283
|
+
/** 처음에 접힌 채로 연다. */
|
|
7284
|
+
defaultCollapsed?: boolean;
|
|
7285
|
+
/** 접힘을 바깥에서 쥔다(controlled). */
|
|
7286
|
+
collapsed?: boolean;
|
|
7287
|
+
/** 접힘이 바뀔 때 알린다. */
|
|
7288
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
7289
|
+
/** 접혀도 남는 머리 오른쪽 자리 — 요약. */
|
|
7290
|
+
headerSummary?: ReactNode;
|
|
7268
7291
|
className?: string;
|
|
7269
7292
|
}
|
|
7270
7293
|
|
|
@@ -10223,7 +10246,7 @@ export declare interface WordRow {
|
|
|
10223
10246
|
* [단어 선정] 패널이 원 출처(섹션 구성은 다르지만 같은 셸을 공용).
|
|
10224
10247
|
* @public
|
|
10225
10248
|
*/
|
|
10226
|
-
export declare function WordSearchFilterPanel({ title, sections, selected, onSelect, className }: WordSearchFilterPanelProps): JSX.Element;
|
|
10249
|
+
export declare function WordSearchFilterPanel({ title, sections, selected, onSelect, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary, headerAction, className, }: WordSearchFilterPanelProps): JSX.Element;
|
|
10227
10250
|
|
|
10228
10251
|
/** @public */
|
|
10229
10252
|
export declare interface WordSearchFilterPanelProps {
|
|
@@ -10237,6 +10260,18 @@ export declare interface WordSearchFilterPanelProps {
|
|
|
10237
10260
|
sections: FilterPanelSection[];
|
|
10238
10261
|
selected: Record<string, string | string[]>;
|
|
10239
10262
|
onSelect: (value: string, sectionLabel: string) => void;
|
|
10263
|
+
/** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다 — `FilterPanel` 의 것을 그대로 넘긴다. */
|
|
10264
|
+
collapsible?: boolean;
|
|
10265
|
+
/** 처음에 접힌 채로 연다(`collapsible` 과 함께). */
|
|
10266
|
+
defaultCollapsed?: boolean;
|
|
10267
|
+
/** 접힘을 바깥에서 쥔다(controlled). */
|
|
10268
|
+
collapsed?: boolean;
|
|
10269
|
+
/** 접힘이 바뀔 때 알린다. */
|
|
10270
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
10271
|
+
/** 접혀도 남는 머리 오른쪽 자리 — 접힌 판이 제 속을 말하는 요약(`FilterPanel headerSummary`). */
|
|
10272
|
+
headerSummary?: ReactNode;
|
|
10273
|
+
/** 펼쳤을 때 머리 오른쪽에 서는 조작부(`FilterPanel headerAction`). */
|
|
10274
|
+
headerAction?: ReactNode;
|
|
10240
10275
|
className?: string;
|
|
10241
10276
|
}
|
|
10242
10277
|
|
|
@@ -10326,12 +10361,24 @@ export declare interface WordSearchResultWord {
|
|
|
10326
10361
|
*/
|
|
10327
10362
|
export declare function WordTable({ title, titleIcon, disc, className, orientation, contentTypes, cutLine, showOutline, showDivLine, items, editCells, count, activeCell, onActivateCell, onRemoveCell, onRemoveItem, onReorder, renderCellAction, showFillerAdd, contentRef, buttonSize, pageNumber, pageNumberPosition, fitHeight, scoring, marks, onMarkClick, scoreIndexOffset, }: WordTableProps): JSX.Element;
|
|
10328
10363
|
|
|
10329
|
-
export declare function WordTableBlock({ title, titleIcon, disc, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction }: WordTableBlockProps): JSX.Element;
|
|
10364
|
+
export declare function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterHeaderSummary, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }: WordTableBlockProps): JSX.Element;
|
|
10330
10365
|
|
|
10331
10366
|
/** @public */
|
|
10332
10367
|
export declare interface WordTableBlockProps {
|
|
10333
10368
|
title?: ReactNode;
|
|
10334
10369
|
titleIcon?: string;
|
|
10370
|
+
/** 안쪽 **표 설정 판의 제목** — 안 주면 코어 기본값(`wordTable.filterTitle`, '표 설정').
|
|
10371
|
+
*
|
|
10372
|
+
* 화면마다 달라야 한다(단어표는 '단어표 설정', 대립쌍표는 그 화면 말) — 그래서 코어 기본값을 바꾸는 것으로는 안 되고,
|
|
10373
|
+
* 앱 사전으로 코어 열쇠를 덮는 것은 말귀의 `guards/i18nOwnership` 이 막는다(옳은 규칙이다).
|
|
10374
|
+
* **부르는 쪽이 제 열쇠로 지어 넘긴다**(말귀 2026-09-29). */
|
|
10375
|
+
filterTitle?: ReactNode;
|
|
10376
|
+
/** 안쪽 표 설정 판을 접을 수 있게 한다 — 머리 줄 전체가 손잡이다. */
|
|
10377
|
+
filterCollapsible?: boolean;
|
|
10378
|
+
/** 그 판을 처음에 접힌 채로 연다(`filterCollapsible` 과 함께). */
|
|
10379
|
+
filterDefaultCollapsed?: boolean;
|
|
10380
|
+
/** 그 판이 접혀도 머리 오른쪽에 남는 자리 — 요약. */
|
|
10381
|
+
filterHeaderSummary?: ReactNode;
|
|
10335
10382
|
disc?: ReactNode;
|
|
10336
10383
|
/** 현재 표의 저장된 이름(불러온 표 등). 있으면 인쇄/PDF 저장 시 파일명으로 사용, 없으면 "오늘 날짜 + 단어표/대립쌍표" */
|
|
10337
10384
|
tableName?: string;
|
|
@@ -10421,6 +10468,25 @@ export declare interface WordTableBlockProps {
|
|
|
10421
10468
|
* **표시 모드에만 간다** — 편집 모드(칸을 손으로 채우는 꼴)에는 얹을 항목이 없다.
|
|
10422
10469
|
*/
|
|
10423
10470
|
renderCellAction?: (index: number, item: WordSearchResultItem) => ReactNode;
|
|
10471
|
+
/**
|
|
10472
|
+
* **액션 줄 왼쪽 무리에 부르는 쪽이 덧붙이는 것** — '모두 선택 해제' 다음, '불러오기' 앞에 선다.
|
|
10473
|
+
*
|
|
10474
|
+
* 그 줄 왼쪽은 **표를 손보는 단추들**이고 뒤쪽(불러오기 · 새 창으로 보기)은 **표를 드나드는
|
|
10475
|
+
* 단추들**이다. 덧붙는 것이 앞 무리에 드는 것은 그 까닭이다 — '모두 다른 단어로' 처럼 표
|
|
10476
|
+
* 내용을 바꾸는 단추가 '표를 나가는' 단추들 사이에 끼면 무리가 안 읽힌다(말귀 요청 ㉕,
|
|
10477
|
+
* 2026-09-28). `onReset` 을 안 주면 맨 앞에 선다.
|
|
10478
|
+
*
|
|
10479
|
+
* **동작이 아니라 그릴 것을 받는다.** 화면마다 무엇을 얹을지 다르고(한쪽은 '모두 다른 단어로',
|
|
10480
|
+
* 다른 쪽은 '모두 다른 대립쌍으로'), 코어가 아는 것은 **어디에 놓느냐**뿐이다. 코어가 세우는
|
|
10481
|
+
* 단추와 같아 보이려면 `<Button size={40} type="bar">` 로 지으면 된다.
|
|
10482
|
+
*
|
|
10483
|
+
* `saveOnly` 에서는 안 그린다 — 그때 이 줄 왼쪽은 통째로 빈다.
|
|
10484
|
+
*
|
|
10485
|
+
* 이 자리가 나기 전에는 앱이 `.word-table-block__actions-left` 를 찾아 포털로 얹고 CSS
|
|
10486
|
+
* `order` 로 차례를 맞추고 있었다. 남의 마디에 얹는 것이라 **코어가 class 이름이나 마디를
|
|
10487
|
+
* 바꾸면 조용히 어긋난다** — 그 길을 닫으려고 낸 자리다.
|
|
10488
|
+
*/
|
|
10489
|
+
actionsLeftExtra?: ReactNode;
|
|
10424
10490
|
}
|
|
10425
10491
|
|
|
10426
10492
|
/**
|
|
@@ -26,6 +26,18 @@
|
|
|
26
26
|
.filter-panel--collapsible .filter-panel__header {
|
|
27
27
|
min-height: 60px;
|
|
28
28
|
flex-shrink: 0;
|
|
29
|
+
/* 머리 줄 전체가 손잡이다(사용자 지시 2026-09-29) — 소비자가 감싸개에 onClick 을 달아
|
|
30
|
+
머리 안을 눌렀는지 보던 자리다. 안의 단추·입력칸을 누른 것은 컴포넌트가 걸러 낸다. */
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
}
|
|
33
|
+
/* 접기 손잡이는 **아이콘만** 보인다 — 단추의 바탕·네모 티를 지운다(사용자 지시).
|
|
34
|
+
포커스 테두리는 남긴다: 키보드 쓰는 사람에게 그 자리가 어디인지 알리는 유일한 표시다. */
|
|
35
|
+
.filter-panel__collapse-toggle,
|
|
36
|
+
.filter-panel__collapse-toggle:hover,
|
|
37
|
+
.filter-panel__collapse-toggle:active {
|
|
38
|
+
background: transparent;
|
|
39
|
+
border: none;
|
|
40
|
+
box-shadow: none;
|
|
29
41
|
}
|
|
30
42
|
.filter-panel--collapsible .filter-panel__sections {
|
|
31
43
|
padding-bottom: var(--spacing-m);
|
|
@@ -7,7 +7,20 @@ import type { FilterPanelSection } from '../types/filterPanel';
|
|
|
7
7
|
export interface FilterPanelProps {
|
|
8
8
|
title?: ReactNode;
|
|
9
9
|
headerAction?: ReactNode;
|
|
10
|
-
/**
|
|
10
|
+
/** 머리 오른쪽에 **접혀도 남는** 자리 — 접힌 판이 제 속을 한 줄로 말하게 한다("등급 전체 · 품사 전체" 같은 요약).
|
|
11
|
+
*
|
|
12
|
+
* `headerAction` 은 펼쳤을 때만 서고 접으면 사라진다(본문을 다루는 단추라 접힌 판에서는 할 일이 없다).
|
|
13
|
+
* 그래서 접힌 판이 무엇을 담고 있는지 말할 자리가 `title` 밖에 없었고, 소비자가 제목 안에 요약을 싣고
|
|
14
|
+
* CSS 로 오른쪽 끝까지 미는 억지를 썼다(말귀 단어표 만들기 2026-09-29).
|
|
15
|
+
* **펼치면 그 자리를 `headerAction` 이 쓴다** — 둘은 같은 칸을 번갈아 쓴다.
|
|
16
|
+
*
|
|
17
|
+
* 요약 **글월을 짓는 셈은 부르는 쪽** 에 있다. 코어는 자리만 낸다 —
|
|
18
|
+
* 여러 조각을 넣을 때는 묶음에 `role="group"` 과 한 줄짜리 `aria-label` 을 주고 낱개는 `aria-hidden` 으로
|
|
19
|
+
* 숨기는 편이 낫다(낭독기가 조각마다 끊어 읽지 않게). */
|
|
20
|
+
headerSummary?: ReactNode;
|
|
21
|
+
/** 헤더 우측에 접기/펼치기 토글 버튼을 노출하고, 접으면 본문(섹션)을 숨긴다.
|
|
22
|
+
* **머리 줄 전체가 손잡이다** — 머리 아무 데나 눌러도 접힌다(사용자 지시 2026-09-29).
|
|
23
|
+
* `headerAction`·`headerSummary` 안의 단추·입력칸을 누른 것은 접지 않는다. */
|
|
11
24
|
collapsible?: boolean;
|
|
12
25
|
/** collapsible일 때 초기 접힘 상태 */
|
|
13
26
|
defaultCollapsed?: boolean;
|
|
@@ -31,4 +44,4 @@ export interface FilterPanelProps {
|
|
|
31
44
|
className?: string;
|
|
32
45
|
}
|
|
33
46
|
/** @public */
|
|
34
|
-
export default function FilterPanel({ title, headerAction, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): import("react").JSX.Element;
|
|
47
|
+
export default function FilterPanel({ title, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): import("react").JSX.Element;
|
|
@@ -13,7 +13,7 @@ import { FILTER_ALL_VALUE } from '../types/filterPanel';
|
|
|
13
13
|
import FilterSectionInfo from './FilterSectionInfo';
|
|
14
14
|
import { devWarnOnce } from '../utils/devWarn';
|
|
15
15
|
/** @public */
|
|
16
|
-
export default function FilterPanel({ title, headerAction, collapsible = false, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected = {}, onSelect, align, sectionAlign, className, }) {
|
|
16
|
+
export default function FilterPanel({ title, headerAction, headerSummary, collapsible = false, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected = {}, onSelect, align, sectionAlign, className, }) {
|
|
17
17
|
// align 우선. 없으면 구 prop(sectionAlign) 매핑, 그것도 없으면 inline 기본.
|
|
18
18
|
const isRow = (align ?? (sectionAlign === 'vertical' ? 'newline' : sectionAlign === 'horizontal' ? 'inline' : 'inline')) === 'inline';
|
|
19
19
|
const { t } = useTranslation();
|
|
@@ -24,13 +24,28 @@ export default function FilterPanel({ title, headerAction, collapsible = false,
|
|
|
24
24
|
setInternalCollapsed(next);
|
|
25
25
|
onCollapsedChange?.(next);
|
|
26
26
|
};
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/* 머리 아무 데나 눌러도 접힌다. 다만 **그 안의 조작부를 누른 것은 아니다** —
|
|
28
|
+
`headerAction` 의 단추(초기화 등)를 누르면 그 일만 하고 판은 그대로 둔다.
|
|
29
|
+
chevron 단추는 제 `onClick` 으로 접으므로 여기서 한 번 더 접지 않는다(두 번 접히면 제자리다). */
|
|
30
|
+
const 머리누름 = (e) => {
|
|
31
|
+
if (!collapsible)
|
|
32
|
+
return;
|
|
33
|
+
const el = e.target;
|
|
34
|
+
if (!el || typeof el.closest !== 'function')
|
|
35
|
+
return;
|
|
36
|
+
if (el.closest('.filter-panel__collapse-toggle'))
|
|
37
|
+
return;
|
|
38
|
+
if (el.closest('button, a, input, select, textarea, label, [role="radiogroup"], [role="button"], [role="menu"]'))
|
|
39
|
+
return;
|
|
40
|
+
toggleCollapsed();
|
|
41
|
+
};
|
|
42
|
+
const showHeader = title || headerAction || headerSummary || collapsible;
|
|
43
|
+
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: [_jsxs("div", { className: "filter-panel__title-row", children: [collapsible && (_jsx(Button, { htmlType: "button", size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: collapsed ? 'chevron-down' : 'chevron-up',
|
|
29
44
|
/* Button은 aria-label을 자기 ariaLabel prop으로 덮어쓴다({...rest} 뒤에
|
|
30
45
|
aria-label={ariaLabelString}을 다시 쓴다) — kebab으로 넘기면 undefined가
|
|
31
46
|
되어 이름이 통째로 사라진다. 저장소의 다른 호출부는 모두 ariaLabel을 쓴다.
|
|
32
47
|
문구도 이 파일만 한국어로 박혀 있었다(키는 이미 있다). */
|
|
33
|
-
ariaLabel: collapsed ? t('common.expand', '펼치기') : t('common.collapse', '접기'), "aria-expanded": !collapsed, onClick: toggleCollapsed })), title && _jsx("span", { className: "filter-panel__title h4", children: title })] }), _jsx("div", { className: "filter-panel__header-actions", children:
|
|
48
|
+
className: "filter-panel__collapse-toggle", ariaLabel: collapsed ? t('common.expand', '펼치기') : t('common.collapse', '접기'), "aria-expanded": !collapsed, onClick: toggleCollapsed })), title && _jsx("span", { className: "filter-panel__title h4", children: title })] }), _jsx("div", { className: "filter-panel__header-actions", children: collapsed ? headerSummary : headerAction })] })), !collapsed && (_jsxs(_Fragment, { children: [_jsx("div", { className: cx('filter-panel__sections', isRow ? 'filter-panel__sections--row' : ''), children: sections.map(section => {
|
|
34
49
|
const sel = selected[section.label];
|
|
35
50
|
const isAllSelected = sel === undefined || (Array.isArray(sel) ? sel.length === 0 : false);
|
|
36
51
|
// section.items에 빈 항목(null/undefined)이 섞여 들어오면 item.value 접근에서
|
|
@@ -5,6 +5,16 @@ import { type TableSettingsSetters } from './wordTableSettings';
|
|
|
5
5
|
interface TableFilterPanelBaseProps {
|
|
6
6
|
/** 패널 제목. 미지정 시 i18n(wordTable.filterTitle) 기본값 사용. */
|
|
7
7
|
title?: ReactNode;
|
|
8
|
+
/** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다(`FilterPanel` 의 것). */
|
|
9
|
+
collapsible?: boolean;
|
|
10
|
+
/** 처음에 접힌 채로 연다. */
|
|
11
|
+
defaultCollapsed?: boolean;
|
|
12
|
+
/** 접힘을 바깥에서 쥔다(controlled). */
|
|
13
|
+
collapsed?: boolean;
|
|
14
|
+
/** 접힘이 바뀔 때 알린다. */
|
|
15
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
16
|
+
/** 접혀도 남는 머리 오른쪽 자리 — 요약. */
|
|
17
|
+
headerSummary?: ReactNode;
|
|
8
18
|
className?: string;
|
|
9
19
|
}
|
|
10
20
|
/**
|
|
@@ -16,9 +16,11 @@ function isShellProps(props) {
|
|
|
16
16
|
*/
|
|
17
17
|
export default function TableFilterPanel(props) {
|
|
18
18
|
const { t } = useTranslation();
|
|
19
|
-
const { title, className } = props;
|
|
19
|
+
const { title, className, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary } = props;
|
|
20
|
+
/* 두 갈래(껍데기 모드·표 설정 모드)가 같은 접기 값을 쓴다 — 한쪽만 넘기면 화면마다 다르게 접힌다 */
|
|
21
|
+
const 접기 = { collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary };
|
|
20
22
|
if (isShellProps(props)) {
|
|
21
|
-
return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: props.sections, selected: props.selected, onSelect: props.onSelect, align: "inline" }));
|
|
23
|
+
return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: props.sections, selected: props.selected, onSelect: props.onSelect, ...접기, align: "inline" }));
|
|
22
24
|
}
|
|
23
25
|
const { hasImage, hasColloquial, contentTypes, orientation, showOutline, cutLine, showDivLine, setters, showCutLine = false, showDivLine2 = false, middleSections = [], extraSelected = {}, onExtraSelect, } = props;
|
|
24
26
|
const sections = [
|
|
@@ -38,5 +40,5 @@ export default function TableFilterPanel(props) {
|
|
|
38
40
|
return;
|
|
39
41
|
onExtraSelect?.(value, sectionLabel);
|
|
40
42
|
};
|
|
41
|
-
return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: sections, selected: selected, onSelect: onSelect, sectionAlign: "horizontal" }));
|
|
43
|
+
return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: sections, selected: selected, onSelect: onSelect, ...접기, sectionAlign: "horizontal" }));
|
|
42
44
|
}
|
|
@@ -12,6 +12,18 @@ export interface WordSearchFilterPanelProps {
|
|
|
12
12
|
sections: FilterPanelSection[];
|
|
13
13
|
selected: Record<string, string | string[]>;
|
|
14
14
|
onSelect: (value: string, sectionLabel: string) => void;
|
|
15
|
+
/** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다 — `FilterPanel` 의 것을 그대로 넘긴다. */
|
|
16
|
+
collapsible?: boolean;
|
|
17
|
+
/** 처음에 접힌 채로 연다(`collapsible` 과 함께). */
|
|
18
|
+
defaultCollapsed?: boolean;
|
|
19
|
+
/** 접힘을 바깥에서 쥔다(controlled). */
|
|
20
|
+
collapsed?: boolean;
|
|
21
|
+
/** 접힘이 바뀔 때 알린다. */
|
|
22
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
23
|
+
/** 접혀도 남는 머리 오른쪽 자리 — 접힌 판이 제 속을 말하는 요약(`FilterPanel headerSummary`). */
|
|
24
|
+
headerSummary?: ReactNode;
|
|
25
|
+
/** 펼쳤을 때 머리 오른쪽에 서는 조작부(`FilterPanel headerAction`). */
|
|
26
|
+
headerAction?: ReactNode;
|
|
15
27
|
className?: string;
|
|
16
28
|
}
|
|
17
29
|
/**
|
|
@@ -20,4 +32,4 @@ export interface WordSearchFilterPanelProps {
|
|
|
20
32
|
* [단어 선정] 패널이 원 출처(섹션 구성은 다르지만 같은 셸을 공용).
|
|
21
33
|
* @public
|
|
22
34
|
*/
|
|
23
|
-
export default function WordSearchFilterPanel({ title, sections, selected, onSelect, className }: WordSearchFilterPanelProps): import("react").JSX.Element;
|
|
35
|
+
export default function WordSearchFilterPanel({ title, sections, selected, onSelect, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary, headerAction, className, }: WordSearchFilterPanelProps): import("react").JSX.Element;
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import FilterPanel from '../FilterPanel';
|
|
5
|
+
/* 이 넷이 없어서 소비자가 **껍데기를 건너뛰고 `FilterPanel` 을 직접** 쓰고 있었다 —
|
|
6
|
+
그러면 이 껍데기가 쥔 두 줄(i18n 제목·`align="inline"`)이 소비자 쪽에 복사된다(말귀 2026-09-29). */
|
|
5
7
|
/**
|
|
6
8
|
* 단어를 어떤 조건으로 좁힐지 고르는 FilterPanel 셸(shell) — 등급/품사/어종/음절 수/선택 섹션 자체는
|
|
7
9
|
* 호출부가 구성해 넘긴다. WordSearchPage의 [단어 검색 설정] 패널과 WordTrainingFilterPanels의
|
|
8
10
|
* [단어 선정] 패널이 원 출처(섹션 구성은 다르지만 같은 셸을 공용).
|
|
9
11
|
* @public
|
|
10
12
|
*/
|
|
11
|
-
export default function WordSearchFilterPanel({ title, sections, selected, onSelect, className }) {
|
|
13
|
+
export default function WordSearchFilterPanel({ title, sections, selected, onSelect, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary, headerAction, className, }) {
|
|
12
14
|
const { t } = useTranslation();
|
|
13
|
-
return (_jsx(FilterPanel, { className: className, title: title ?? t('wordSearch.filterTitle', '단어 검색 설정'), sections: sections, selected: selected, onSelect: onSelect, align: "inline" }));
|
|
15
|
+
return (_jsx(FilterPanel, { className: className, title: title ?? t('wordSearch.filterTitle', '단어 검색 설정'), sections: sections, selected: selected, onSelect: onSelect, collapsible: collapsible, defaultCollapsed: defaultCollapsed, collapsed: collapsed, onCollapsedChange: onCollapsedChange, headerSummary: headerSummary, headerAction: headerAction, align: "inline" }));
|
|
14
16
|
}
|
|
@@ -20,7 +20,46 @@
|
|
|
20
20
|
border: 1px solid var(--color-darkWhite);
|
|
21
21
|
background: var(--color-white);
|
|
22
22
|
border-radius: var(--radius-m);
|
|
23
|
-
|
|
23
|
+
/* 옆에 서는 `FilterPanel` 과 같은 여백(사용자 지시 2026-09-29) */
|
|
24
|
+
padding: var(--spacing-l);
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
gap: var(--spacing-m);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* 상자 안 머리 — `FilterPanel` 의 머리와 같은 결(제목 h4 · 부제 body-3 회색).
|
|
31
|
+
설명은 한 줄을 통째로 쓴다(제목 옆에 두면 조작부를 다음 줄로 밀어낸다). */
|
|
32
|
+
.word-search-result-block__head {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
flex-wrap: wrap;
|
|
36
|
+
gap: var(--spacing-xs);
|
|
37
|
+
}
|
|
38
|
+
.word-search-result-block__head-main {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: var(--spacing-xs);
|
|
42
|
+
min-width: 0;
|
|
43
|
+
}
|
|
44
|
+
.word-search-result-block__head-icon {
|
|
45
|
+
flex-shrink: 0;
|
|
46
|
+
color: var(--color-gray);
|
|
47
|
+
}
|
|
48
|
+
.word-search-result-block__title {
|
|
49
|
+
text-align: left;
|
|
50
|
+
min-width: 0;
|
|
51
|
+
}
|
|
52
|
+
.word-search-result-block__head-actions {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: var(--spacing-xs);
|
|
56
|
+
margin-left: auto;
|
|
57
|
+
}
|
|
58
|
+
.word-search-result-block__head-disc {
|
|
59
|
+
flex: 0 0 100%;
|
|
60
|
+
min-width: 0;
|
|
61
|
+
color: var(--color-gray);
|
|
62
|
+
margin: 0;
|
|
24
63
|
}
|
|
25
64
|
|
|
26
65
|
/* 박스↔액션 간격은 '내용이 있는' 액션(더보기/선택초기화/인쇄)이 뒤에 올 때만 준다.
|
|
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
7
7
|
import './WordSearchResultBlock.css';
|
|
8
8
|
import Button from '../Button';
|
|
9
9
|
import Section from '../Section';
|
|
10
|
+
import Icon from '../Icon';
|
|
10
11
|
import Dropdown from '../Dropdown';
|
|
11
12
|
import Pagination from '../Pagination';
|
|
12
13
|
import WordImageTooltip from '../Tooltip/WordImageTooltip';
|
|
@@ -158,7 +159,11 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
|
|
|
158
159
|
const discNode = disc ?? (notice
|
|
159
160
|
? _jsxs(_Fragment, { children: [_jsx("span", { className: "word-search-result-block__notice", children: notice }), discLabel ? _jsx("span", { className: "word-search-result-block__disc", children: discLabel }) : null] })
|
|
160
161
|
: discLabel);
|
|
161
|
-
return (
|
|
162
|
+
return (
|
|
163
|
+
/* 제목·부제·조작부가 **상자 안** 에 선다(사용자 지시 2026-09-29) — 전에는 `Section` 의 머리가
|
|
164
|
+
상자 밖에 서서 제목이 테두리 위에 뜨고 좌우 여백도 옆의 `FilterPanel` 과 달라 보였다.
|
|
165
|
+
이제 여백(--spacing-l)·제목 등급(h4)·부제(body-3 회색)를 그 판과 맞춘다. */
|
|
166
|
+
_jsxs(Section, { type: sectionType, className: cx('word-search-result-block', className), children: [_jsxs("div", { className: "word-search-result-block__inner", children: [(titleLabel || toolbar || discNode) && (_jsxs("div", { className: "word-search-result-block__head", children: [_jsxs("div", { className: "word-search-result-block__head-main", children: [titleIcon && _jsx(Icon, { icon: titleIcon, className: "word-search-result-block__head-icon" }), titleLabel && _jsx("span", { className: "word-search-result-block__title h4", children: titleLabel })] }), toolbar && _jsx("div", { className: "word-search-result-block__head-actions", children: toolbar }), discNode && _jsx("div", { className: "word-search-result-block__head-disc body-3", children: discNode })] })), (groups ? groups.length === 0 : items.length === 0) && (_jsxs("div", { className: "word-search-result-block__empty-wrap", children: [_jsx("p", { className: "word-search-result-block__empty body-3", children: t('wordSearch.emptyResult', '검색 결과가 없습니다.') }), emptyHint && _jsx("p", { className: "word-search-result-block__empty-hint body-3", children: emptyHint })] })), groups ? ((pagedGroups ?? []).map((group) => (_jsx("div", { className: "word-search-result-block__group", children: _jsx("div", { className: "word-search-result-block__items", children: group.items.map((item) => {
|
|
162
167
|
const isSelected = selected.includes(item.id);
|
|
163
168
|
return (_jsx("div", { onMouseEnter: e => {
|
|
164
169
|
openHover(e.currentTarget);
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
justify-content: space-between;
|
|
14
14
|
align-items: center;
|
|
15
15
|
gap: var(--spacing-xs);
|
|
16
|
+
/* 모자라면 아랫줄로 내린다 — 안 그러면 오른쪽 무리가 판 밖으로 밀려 **'저장하기' 가 잘린다**
|
|
17
|
+
(실측 2026-09-28: 판 오른끝 947px 인데 저장하기 오른끝이 1078px 이었다). 왼쪽 무리가
|
|
18
|
+
늘 수 있게 되면서(`actionsLeftExtra`) 닿기 쉬워졌다. 넉넉하면 아무것도 안 바뀐다. */
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* 아랫줄로 내려가도 주 단추는 **오른쪽 끝**에 선다 — 사람이 '저장하기' 를 찾는 자리다.
|
|
23
|
+
한 줄에 다 설 때는 `space-between` 이 이미 하는 일이라 아무 일도 안 한다. */
|
|
24
|
+
.word-table-block__actions-right {
|
|
25
|
+
margin-left: auto;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
.word-table-block__actions-left,
|
|
@@ -31,6 +41,11 @@
|
|
|
31
41
|
display: grid;
|
|
32
42
|
grid-template-columns: repeat(2, 1fr);
|
|
33
43
|
}
|
|
44
|
+
/* 세로로 세울 때는 `margin-left: auto` 를 거둔다 — 가로가 곧 엇축이라 그 자동 여백이
|
|
45
|
+
`align-items: stretch` 를 이겨, 오른쪽 무리만 제 속 너비로 쪼그라든다. */
|
|
46
|
+
.word-table-block__actions-right {
|
|
47
|
+
margin-left: 0;
|
|
48
|
+
}
|
|
34
49
|
/* min-width 해제 — !important 불필요: 이 결합 선택자(2클래스)가 버튼 사이즈 클래스
|
|
35
50
|
(.button40--bar 등, 1클래스)보다 특이도가 이미 높아 소스 순서와 무관하게 우선 적용됨.
|
|
36
51
|
(minWidth prop을 인라인으로 넘기는 경우는 여기 해당 없음) */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import './WordTableBlock.css';
|
|
2
2
|
import type { WordTableBlockProps } from './wordTableBlockTypes';
|
|
3
3
|
export type { WordEntry, WordTableBlockProps } from './wordTableBlockTypes';
|
|
4
|
-
export default function WordTableBlock({ title, titleIcon, disc, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction }: WordTableBlockProps): import("react").JSX.Element;
|
|
4
|
+
export default function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterHeaderSummary, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }: WordTableBlockProps): import("react").JSX.Element;
|
|
@@ -45,7 +45,7 @@ function 겹치지않는id(list) {
|
|
|
45
45
|
return id === it.id ? it : { ...it, id };
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
export default function WordTableBlock({ title, titleIcon, disc, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction }) {
|
|
48
|
+
export default function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterHeaderSummary, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }) {
|
|
49
49
|
const { t } = useTranslation();
|
|
50
50
|
const resolvedResetLabel = resetLabel ?? t('wordPrint.clearAllSelection', '모두 선택 해제');
|
|
51
51
|
const isEditable = items === undefined;
|
|
@@ -318,7 +318,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
|
|
|
318
318
|
setActiveCell(null);
|
|
319
319
|
},
|
|
320
320
|
onClose: handleCloseWordSearch,
|
|
321
|
-
}), !hideControls && (_jsx(TableFilterPanel, { hasImage: hasImage, hasColloquial: hasColloquial, contentTypes: contentTypes, orientation: orientation, showOutline: showOutline, cutLine: cutLine, showDivLine: showDivLine, setters: { setContentTypes, setOrientation, setShowOutline, setCutLine, setShowDivLine }, showCutLine: (pagesEnabled ? itemsPerPage : count) > 1, showDivLine2: hasPairs, middleSections: middleSections, extraSelected: middleSelected, onExtraSelect: handleMiddleSelect })), isPaginated && pages ? (_jsx("div", { className: "word-table-block__pages", children: pages.map((page, i) => (_jsx(WordTable, { orientation: orientation, contentTypes: contentTypes, cutLine: cutLine, showOutline: showOutline, showDivLine: showDivLine, count: itemsPerPage, items: page, onRemoveItem: onRemoveItem,
|
|
321
|
+
}), !hideControls && (_jsx(TableFilterPanel, { title: filterTitle, collapsible: filterCollapsible, defaultCollapsed: filterDefaultCollapsed, headerSummary: filterHeaderSummary, hasImage: hasImage, hasColloquial: hasColloquial, contentTypes: contentTypes, orientation: orientation, showOutline: showOutline, cutLine: cutLine, showDivLine: showDivLine, setters: { setContentTypes, setOrientation, setShowOutline, setCutLine, setShowDivLine }, showCutLine: (pagesEnabled ? itemsPerPage : count) > 1, showDivLine2: hasPairs, middleSections: middleSections, extraSelected: middleSelected, onExtraSelect: handleMiddleSelect })), isPaginated && pages ? (_jsx("div", { className: "word-table-block__pages", children: pages.map((page, i) => (_jsx(WordTable, { orientation: orientation, contentTypes: contentTypes, cutLine: cutLine, showOutline: showOutline, showDivLine: showDivLine, count: itemsPerPage, items: page, onRemoveItem: onRemoveItem,
|
|
322
322
|
/* 쪽이 나뉘면 끌어 옮기기가 통째로 꺼졌다(말귀 단어 찾기: 9개부터). 쪽 안의 자리를 전체 자리로 옮겨 넘긴다 —
|
|
323
323
|
쪽을 넘는 이동은 하지 않는다. */
|
|
324
324
|
onReorder: onReorder && ((from, to) => onReorder(from + i * itemsPerPage, to + i * itemsPerPage)),
|
|
@@ -341,5 +341,5 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
|
|
|
341
341
|
e.preventDefault();
|
|
342
342
|
onDescriptionChange?.(descriptionPlaceholder ?? '');
|
|
343
343
|
}
|
|
344
|
-
} }) })] }))] }), !hideControls && (_jsxs("div", { className: "word-table-block__actions", children: [_jsxs("div", { className: "word-table-block__actions-left", children: [!saveOnly && onReset && (_jsx(Button, { htmlType: "button", size: 40, type: "bar", label: resolvedResetLabel, onClick: onReset })), !saveOnly && onLoad && (_jsx(Button, { htmlType: "button", size: 40, type: "bar", label: t('wordTable.load', '불러오기'), onClick: onLoad })), !saveOnly && _jsx(Button, { htmlType: "button", size: 40, type: "bar", label: t('wordTable.openNewWindow', '새 창으로 보기'), onClick: handleOpenNewWindow })] }), _jsxs("div", { className: "word-table-block__actions-right", children: [onCancel && _jsx(Button, { htmlType: "button", size: "56", type: "bar", label: t('common.cancel', '취소'), onClick: onCancel }), !saveOnly && _jsx(Button, { htmlType: "button", size: "56", type: "bar", label: t('wordTable.print', '인쇄하기'), onClick: handlePrint }), secondarySaveButtonLabel && (_jsx(Button, { htmlType: "button", size: "56", type: "bar", label: secondarySaveButtonLabel, onClick: () => inlineNameFields ? openSaveAsPopup() : handleSave(true), disabled: noItems, onDisabledClick: () => { showWarning(t('wordTable.emptyItems', '단어를 1개 이상 추가해주세요.')); } })), _jsx(Button, { htmlType: "button", size: "56", type: "bar", variant: "main", label: saveButtonLabel ?? t('wordTable.saveButton', '저장하기'), onClick: () => handleSave(false), disabled: mainSaveDisabled, onDisabledClick: handleMainDisabledClick })] })] })), _jsx(Toast, { message: toastMessage, type: toastType, onClose: clearToast })] }));
|
|
344
|
+
} }) })] }))] }), !hideControls && (_jsxs("div", { className: "word-table-block__actions", children: [_jsxs("div", { className: "word-table-block__actions-left", children: [!saveOnly && onReset && (_jsx(Button, { htmlType: "button", size: 40, type: "bar", label: resolvedResetLabel, onClick: onReset })), !saveOnly && actionsLeftExtra, !saveOnly && onLoad && (_jsx(Button, { htmlType: "button", size: 40, type: "bar", label: t('wordTable.load', '불러오기'), onClick: onLoad })), !saveOnly && _jsx(Button, { htmlType: "button", size: 40, type: "bar", label: t('wordTable.openNewWindow', '새 창으로 보기'), onClick: handleOpenNewWindow })] }), _jsxs("div", { className: "word-table-block__actions-right", children: [onCancel && _jsx(Button, { htmlType: "button", size: "56", type: "bar", label: t('common.cancel', '취소'), onClick: onCancel }), !saveOnly && _jsx(Button, { htmlType: "button", size: "56", type: "bar", label: t('wordTable.print', '인쇄하기'), onClick: handlePrint }), secondarySaveButtonLabel && (_jsx(Button, { htmlType: "button", size: "56", type: "bar", label: secondarySaveButtonLabel, onClick: () => inlineNameFields ? openSaveAsPopup() : handleSave(true), disabled: noItems, onDisabledClick: () => { showWarning(t('wordTable.emptyItems', '단어를 1개 이상 추가해주세요.')); } })), _jsx(Button, { htmlType: "button", size: "56", type: "bar", variant: "main", label: saveButtonLabel ?? t('wordTable.saveButton', '저장하기'), onClick: () => handleSave(false), disabled: mainSaveDisabled, onDisabledClick: handleMainDisabledClick })] })] })), _jsx(Toast, { message: toastMessage, type: toastType, onClose: clearToast })] }));
|
|
345
345
|
}
|
|
@@ -18,6 +18,18 @@ export interface WordEntry {
|
|
|
18
18
|
export interface WordTableBlockProps {
|
|
19
19
|
title?: ReactNode;
|
|
20
20
|
titleIcon?: string;
|
|
21
|
+
/** 안쪽 **표 설정 판의 제목** — 안 주면 코어 기본값(`wordTable.filterTitle`, '표 설정').
|
|
22
|
+
*
|
|
23
|
+
* 화면마다 달라야 한다(단어표는 '단어표 설정', 대립쌍표는 그 화면 말) — 그래서 코어 기본값을 바꾸는 것으로는 안 되고,
|
|
24
|
+
* 앱 사전으로 코어 열쇠를 덮는 것은 말귀의 `guards/i18nOwnership` 이 막는다(옳은 규칙이다).
|
|
25
|
+
* **부르는 쪽이 제 열쇠로 지어 넘긴다**(말귀 2026-09-29). */
|
|
26
|
+
filterTitle?: ReactNode;
|
|
27
|
+
/** 안쪽 표 설정 판을 접을 수 있게 한다 — 머리 줄 전체가 손잡이다. */
|
|
28
|
+
filterCollapsible?: boolean;
|
|
29
|
+
/** 그 판을 처음에 접힌 채로 연다(`filterCollapsible` 과 함께). */
|
|
30
|
+
filterDefaultCollapsed?: boolean;
|
|
31
|
+
/** 그 판이 접혀도 머리 오른쪽에 남는 자리 — 요약. */
|
|
32
|
+
filterHeaderSummary?: ReactNode;
|
|
21
33
|
disc?: ReactNode;
|
|
22
34
|
/** 현재 표의 저장된 이름(불러온 표 등). 있으면 인쇄/PDF 저장 시 파일명으로 사용, 없으면 "오늘 날짜 + 단어표/대립쌍표" */
|
|
23
35
|
tableName?: string;
|
|
@@ -107,6 +119,25 @@ export interface WordTableBlockProps {
|
|
|
107
119
|
* **표시 모드에만 간다** — 편집 모드(칸을 손으로 채우는 꼴)에는 얹을 항목이 없다.
|
|
108
120
|
*/
|
|
109
121
|
renderCellAction?: (index: number, item: WordSearchResultItem) => ReactNode;
|
|
122
|
+
/**
|
|
123
|
+
* **액션 줄 왼쪽 무리에 부르는 쪽이 덧붙이는 것** — '모두 선택 해제' 다음, '불러오기' 앞에 선다.
|
|
124
|
+
*
|
|
125
|
+
* 그 줄 왼쪽은 **표를 손보는 단추들**이고 뒤쪽(불러오기 · 새 창으로 보기)은 **표를 드나드는
|
|
126
|
+
* 단추들**이다. 덧붙는 것이 앞 무리에 드는 것은 그 까닭이다 — '모두 다른 단어로' 처럼 표
|
|
127
|
+
* 내용을 바꾸는 단추가 '표를 나가는' 단추들 사이에 끼면 무리가 안 읽힌다(말귀 요청 ㉕,
|
|
128
|
+
* 2026-09-28). `onReset` 을 안 주면 맨 앞에 선다.
|
|
129
|
+
*
|
|
130
|
+
* **동작이 아니라 그릴 것을 받는다.** 화면마다 무엇을 얹을지 다르고(한쪽은 '모두 다른 단어로',
|
|
131
|
+
* 다른 쪽은 '모두 다른 대립쌍으로'), 코어가 아는 것은 **어디에 놓느냐**뿐이다. 코어가 세우는
|
|
132
|
+
* 단추와 같아 보이려면 `<Button size={40} type="bar">` 로 지으면 된다.
|
|
133
|
+
*
|
|
134
|
+
* `saveOnly` 에서는 안 그린다 — 그때 이 줄 왼쪽은 통째로 빈다.
|
|
135
|
+
*
|
|
136
|
+
* 이 자리가 나기 전에는 앱이 `.word-table-block__actions-left` 를 찾아 포털로 얹고 CSS
|
|
137
|
+
* `order` 로 차례를 맞추고 있었다. 남의 마디에 얹는 것이라 **코어가 class 이름이나 마디를
|
|
138
|
+
* 바꾸면 조용히 어긋난다** — 그 길을 닫으려고 낸 자리다.
|
|
139
|
+
*/
|
|
140
|
+
actionsLeftExtra?: ReactNode;
|
|
110
141
|
}
|
|
111
142
|
export declare const CELL_COUNTS: number[];
|
|
112
143
|
/** 표시(인쇄) 모드에서 선택 가능한 "페이지당 개수"(단어/대립쌍 수) — 이 값 그대로 페이지당 칸 수가 된다 */
|