@ihabdevteam/core 0.224.0 → 0.226.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 +100 -0
- package/dist/components.cjs.js +46 -46
- package/dist/components.esm.css +2 -2
- package/dist/hooks.cjs.js +3 -3
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +16 -16
- package/dist/ihabdevteam-core.d.ts +1 -1
- package/dist/packages/core/src/components/DatePicker.css +11 -3
- package/dist/packages/core/src/components/DatePicker.js +20 -4
- package/dist/packages/core/src/components/Word/WordTableButton.css +14 -5
- package/dist/packages/core/src/components/Word/WordTableButton.js +1 -1
- package/dist/packages/core/src/components/WordTable.css +5 -1
- package/dist/packages/core/src/components/WordTable.js +9 -1
- package/dist/packages/core/src/hooks/useAnchoredMenu.js +33 -0
- package/dist/packages/core/src/i18n/locales/en.json +5 -0
- package/dist/packages/core/src/i18n/locales/ko.json +5 -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.226.0";
|
|
1630
1630
|
|
|
1631
1631
|
/** @public */
|
|
1632
1632
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -55,10 +55,18 @@
|
|
|
55
55
|
.date-picker--open .date-picker__icon { color: var(--color-main); }
|
|
56
56
|
|
|
57
57
|
/* Dropdown */
|
|
58
|
+
/* **자리는 인라인 스타일이 쥔다 — 여기서 `top`·`left`·`position` 을 적지 않는다.**
|
|
59
|
+
한때 `position: absolute; top: calc(100% + 6px); left: 0` 이 여기 있었다. 판이
|
|
60
|
+
칸 바로 아래 붙어 있던 시절의 값인데, 그 뒤 판은 `document.body` 로 포탈되어
|
|
61
|
+
`position: fixed` 로 자리를 받게 됐다. 그런데 이 `top` 만 남았다.
|
|
62
|
+
|
|
63
|
+
그래서 **위로 뒤집기가 그려지지 않았다.** 인라인이 `position`·`left`·`bottom` 은
|
|
64
|
+
덮지만 `top` 은 지우지 않는다 — 높이가 `auto` 인 요소에 `top` 과 `bottom` 이 둘 다
|
|
65
|
+
있으면 **`top` 이 자리를 정하고 높이는 그 둘 사이로 눌린다.** `fixed` 아래의
|
|
66
|
+
`calc(100% + 6px)` 은 창 높이의 100% 라, 창이 1278 이면 `top` 이 1284 — 화면 바로
|
|
67
|
+
아래다. 판은 거기 서고 높이는 음수로 눌려 26px 이 됐다(말귀 실측 2026-10-01).
|
|
68
|
+
셈은 내내 옳았고 그려지지 않았을 뿐이다. */
|
|
58
69
|
.date-picker__dropdown {
|
|
59
|
-
position: absolute;
|
|
60
|
-
top: calc(100% + 6px);
|
|
61
|
-
left: 0;
|
|
62
70
|
z-index: 200;
|
|
63
71
|
background: var(--color-white);
|
|
64
72
|
border: 1px solid var(--color-whiteLine);
|
|
@@ -65,10 +65,21 @@ export default function DatePicker({ value, onChange, size = 48, min, max, disab
|
|
|
65
65
|
const vw = window.innerWidth;
|
|
66
66
|
/* 판의 실제 크기 — 열린 뒤에 재므로 있다. 없으면(첫 계산 전) 어림값 */
|
|
67
67
|
const 판 = typeof document !== 'undefined' ? document.getElementById(panelId) : null;
|
|
68
|
-
|
|
68
|
+
/* **우리가 얹은 자리에 흔들리지 않는 높이를 쓴다.**
|
|
69
|
+
`offsetHeight` 만 보면 우리가 방금 얹은 `top`/`bottom` 이 상자를 눌렀을 때 그
|
|
70
|
+
눌린 값을 되읽는다 — 그 값으로 다시 자리를 잡고, 자리가 다시 높이를 바꾸어
|
|
71
|
+
두 꼴을 오간다(실측: 306 ↔ 26). `scrollHeight` 는 눌려도 **안엣것의 높이**를
|
|
72
|
+
그대로 내놓으므로 둘 중 큰 쪽을 쓴다. 판이 아직 없으면 어림값. */
|
|
73
|
+
const dropdownH = Math.max(판?.offsetHeight || 0, 판?.scrollHeight || 0) || 300;
|
|
69
74
|
const 판폭 = Math.max(rect.width, 판?.offsetWidth || 0, 260);
|
|
70
75
|
const spaceBelow = vh - rect.bottom;
|
|
71
|
-
const
|
|
76
|
+
const spaceAbove = rect.top;
|
|
77
|
+
/* **아래가 모자라면 넓은 쪽으로 편다**(말귀 요청 ㊵, 2026-10-01).
|
|
78
|
+
한때는 `rect.top > dropdownH`, 곧 **위가 통째로 남을 때만** 뒤집었다. 그래서 위도
|
|
79
|
+
아래도 다 모자라면 늘 아래로 폈고 — 칸이 창 바닥 줄에 있으면 거의 늘 그 꼴이다 —
|
|
80
|
+
아래로 삐져나갔다. 모자라는 것이 정해졌다면 **덜 모자라는 쪽**이 낫다.
|
|
81
|
+
`6` 과 `여백` 은 아래 자리 잡기와 같은 값이다. */
|
|
82
|
+
const above = spaceBelow < dropdownH && spaceAbove > spaceBelow;
|
|
72
83
|
/* **창 안으로 조인다** — 스크롤을 따라 다시 잴 때 트리거가 화면 밖으로 나가면 달력이 창 위·아래 밖까지 따라가
|
|
73
84
|
고를 수 없었다. 판이 창보다 크면 위 여백에 붙인다. */
|
|
74
85
|
const 여백 = 8;
|
|
@@ -78,9 +89,14 @@ export default function DatePicker({ value, onChange, size = 48, min, max, disab
|
|
|
78
89
|
left: 조이기(rect.left, vw - 판폭 - 여백),
|
|
79
90
|
width: rect.width,
|
|
80
91
|
minWidth: 260,
|
|
92
|
+
/* **둘 다 적는다 — 쓰는 쪽만 적으면 안 된다.**
|
|
93
|
+
안 쓰는 쪽을 비워 두면 스타일시트에 남은 `top`(또는 `bottom`)이 그대로 살아
|
|
94
|
+
자리를 가로챈다. 높이가 `auto` 인 요소에 `top` 과 `bottom` 이 둘 다 있으면
|
|
95
|
+
`top` 이 이기고 높이는 그 둘 사이로 눌린다 — 판이 26px 으로 쪼그라들어
|
|
96
|
+
화면 밖에 섰다(말귀 실측 2026-10-01). `auto` 로 **지운다.** */
|
|
81
97
|
...(above
|
|
82
|
-
? { bottom: 조이기(vh - rect.top + 6, vh - dropdownH - 여백) }
|
|
83
|
-
: { top: 조이기(rect.bottom + 6, vh - dropdownH - 여백) }),
|
|
98
|
+
? { top: 'auto', bottom: 조이기(vh - rect.top + 6, vh - dropdownH - 여백) }
|
|
99
|
+
: { bottom: 'auto', top: 조이기(rect.bottom + 6, vh - dropdownH - 여백) }),
|
|
84
100
|
zIndex: anchoredMenuZIndex(trigger),
|
|
85
101
|
};
|
|
86
102
|
}, [panelId]);
|
|
@@ -114,7 +114,9 @@
|
|
|
114
114
|
min-width: 0;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/* 오른쪽 덩이 —
|
|
117
|
+
/* 오른쪽 덩이 — 상태 배지와 표 번호. **번호가 뒤(오른쪽 끝)** 다.
|
|
118
|
+
한때 번호를 배지보다 앞에 두었다(㊱ 첫 판) — "배지는 상태고 번호는 이름이니 이름이
|
|
119
|
+
먼저" 라는 셈이었는데, 화면을 보고 사용자가 번호를 맨 끝으로 정했다(2026-10-01).
|
|
118
120
|
줄어들지 않는다: 좁으면 왼쪽의 등록일이 먼저 말줄임으로 양보한다. */
|
|
119
121
|
.word-table-button__meta-right {
|
|
120
122
|
display: flex;
|
|
@@ -128,10 +130,17 @@
|
|
|
128
130
|
등록일보다 색을 올려 둔다: 둘 다 회색이면 한 줄에 같은 무게로 놓여 훑을 때 안 걸린다.
|
|
129
131
|
줄이지 않는다 — 번호는 잘리면 뜻이 없다. 좁으면 등록일이 먼저 양보한다. */
|
|
130
132
|
.word-table-button__code {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
/* **곁의 글과 같은 글꼴이다 — 고정폭을 쓰지 않는다**(사용자 지시 2026-10-01).
|
|
134
|
+
한때 `--font-mono` 였다. 까닭은 "종이에 찍힌 번호와 화면의 번호를 눈으로 견줄 때
|
|
135
|
+
자릿수가 맞으라고" 였는데, 카드 바닥 줄에서 곁의 등록일과 글꼴이 달라 **짧은 번호
|
|
136
|
+
한 토막이 딴 데서 온 글처럼** 보였다. 그리고 눈으로 견주는 일은 이제 고르개의 찾는
|
|
137
|
+
칸이 대신한다 — 번호를 치면 그 표만 남는다.
|
|
138
|
+
`caption-1` 이 쥔 글꼴(프리텐다드)을 그대로 받도록 **아무것도 안 적는다.** */
|
|
139
|
+
/* **등록일과 같은 회색이다**(말귀 요청 ㊳, 2026-10-01). 한때 `--color-text-primary` 로
|
|
140
|
+
한 단 올려 두었다 — "둘 다 회색이면 한 줄에 같은 무게로 놓여 훑을 때 안 걸린다" 는
|
|
141
|
+
까닭이었는데, 번호가 줄 오른쪽 끝으로 가면서(㊱) 등록일과 떨어져 그 걱정이 없어졌다.
|
|
142
|
+
자리가 이미 구별을 하므로 색까지 세울 까닭이 없다. */
|
|
143
|
+
color: var(--color-gray);
|
|
135
144
|
flex-shrink: 0;
|
|
136
145
|
white-space: nowrap;
|
|
137
146
|
}
|
|
@@ -27,5 +27,5 @@ export default function WordTableButton({ items, name, description, createdAt, c
|
|
|
27
27
|
return (_jsxs("div", { className: cx('word-table-button', selected ? 'word-table-button--selected' : '', !hasStoreFooter ? 'word-table-button--no-footer' : '', className), role: "button", tabIndex: 0, "aria-label": code ? `${name} (${code})` : name, onClick: renaming ? undefined : onClick, onKeyDown: handleKey, children: [isNew && _jsx("span", { className: "word-table-button__new-dot", "aria-label": t('myWordTable.newBadge', '새로 만든 표') }), _jsx(WordTableThumbnail, { items: items, className: "word-table-button__thumb" }), _jsxs("div", { className: "word-table-button__info", children: [_jsxs("div", { className: "word-table-button__info-text", children: [titleNode ?? _jsx("div", { className: "word-table-button__title h4", children: name }), description && _jsx("div", { className: "word-table-button__desc body-3", children: description })] }), onMore && !renaming && (_jsx(Button, { htmlType: "button", className: "word-table-button__more", size: 32, type: "dot", variant: "transparent-gray", useBorder: false, icon: "ellipsis",
|
|
28
28
|
/* 하드코딩 '더보기'였다(영어 화면에서도 한국어로 남았다). 목록에 표가
|
|
29
29
|
여러 개면 이름이 같은 버튼이 여러 개이기도 해서 이름을 함께 넣는다. */
|
|
30
|
-
ariaLabel: t('common.moreFor', { name }), onClick: (e) => { e.stopPropagation(); onMore(e); } }))] }), (code || createdAt || published || shared) && (_jsxs("div", { className: "word-table-button__meta-row", children: [_jsx("span", { className: "word-table-button__meta-left", children: _jsx("span", { className: "word-table-button__meta caption-1", children: createdAt ? formatRelativeTime(createdAt, t) : '' }) }), _jsxs("span", { className: "word-table-button__meta-right", children: [
|
|
30
|
+
ariaLabel: t('common.moreFor', { name }), onClick: (e) => { e.stopPropagation(); onMore(e); } }))] }), (code || createdAt || published || shared) && (_jsxs("div", { className: "word-table-button__meta-row", children: [_jsx("span", { className: "word-table-button__meta-left", children: _jsx("span", { className: "word-table-button__meta caption-1", children: createdAt ? formatRelativeTime(createdAt, t) : '' }) }), _jsxs("span", { className: "word-table-button__meta-right", children: [(published || shared) && (_jsxs("span", { className: "word-table-button__badges", children: [published && (_jsx(Badge, { className: "word-table-button__badge", size: "small", variant: "main", fill: "soft", iconLeft: "book-marked", label: publishedLabel ?? t('myWordTable.publishedBadge', '발행됨') })), shared && (_jsx(Badge, { className: "word-table-button__badge", size: "small", fill: "soft", iconLeft: "link", label: sharedLabel ?? t('myWordTable.sharedBadge', '공유 중') }))] })), code && _jsx("span", { className: "word-table-button__code caption-1", children: code })] })] })), hasStoreFooter && (_jsxs("div", { className: "word-table-button__footer", children: [_jsxs("span", { className: "word-table-button__store-by", children: [publisherName && (_jsx("button", { className: "training-card__publisher body-4", type: "button", onClick: (e) => { e.stopPropagation(); onPublisherClick?.(); }, children: publisherName })), publishedAt && _jsx("span", { className: "training-card__date body-4", children: formatRelativeTime(publishedAt, t) })] }), _jsxs("span", { className: "word-table-button__store-stats", children: [likeCount != null && _jsx(Badge, { size: "small", fill: "soft", variant: "like", iconLeft: "heart", label: String(likeCount) }), downloadCount != null && _jsx(Badge, { size: "small", fill: "soft", variant: "main", iconLeft: "download", label: String(downloadCount) })] })] }))] }));
|
|
31
31
|
}
|
|
@@ -125,7 +125,11 @@
|
|
|
125
125
|
|
|
126
126
|
띠 높이(36 / 28)와 로고(34 / 24)도 같은 까닭으로 옮겼다. 말귀가 종이로 뽑아 보고
|
|
127
127
|
`--wt-band-*` 로 덮어 쓰던 값이 그대로 기본이 됐다(사용자 지시 2026-10-01) — 두
|
|
128
|
-
저장소가 같은 수를 두 군데 적고 있을 까닭이 없다. **덮어 쓰던 쪽이 정답이었다.**
|
|
128
|
+
저장소가 같은 수를 두 군데 적고 있을 까닭이 없다. **덮어 쓰던 쪽이 정답이었다.**
|
|
129
|
+
|
|
130
|
+
`--wt-brand-logo-height` 는 **34 다.** 한때 말귀가 38 로 올려 쓰며 그 값을 청했는데,
|
|
131
|
+
사장님이 34 로 못박으셨다(2026-10-01). 36 → 32 → 34 → 38 로 오간 끝의 값이라
|
|
132
|
+
다시 흔들리기 쉬운 자리다 — 바꾸려면 종이에서 보고 바꾼다. */
|
|
129
133
|
.word-table__band--top {
|
|
130
134
|
top: 0;
|
|
131
135
|
height: var(--wt-band-top-height, 36px);
|
|
@@ -206,7 +206,15 @@ export default function WordTable({ title, titleIcon, disc, className, orientati
|
|
|
206
206
|
_jsx("div", { className: cx('word-table-cell__score', mark ? 'word-table-cell__score--marked' : ''),
|
|
207
207
|
/* CSS 의 가운데 맞춤(translateX(-50%))을 그대로 두고 되돌림만 얹는다. 바닥 가운데를
|
|
208
208
|
기준으로 키워야 칸 아래쪽에 붙은 자리가 흔들리지 않는다. */
|
|
209
|
-
style: 채점되돌림 > 1 ? { transform: `translateX(-50%) scale(${채점되돌림})`, transformOrigin: 'bottom center' } : undefined, children: MARKS.map(m => (_jsx("span", { className: cx('word-table-cell__score-slot', mark === m ? 'word-table-cell__score-slot--on' : ''), children: _jsx(Button, { htmlType: "button", size: buttonSize, type: "dot", icon: MARK_ICON[m], variant: mark === m ? MARK_VARIANT[m] : 'default', selected: mark === m, useBorder: false,
|
|
209
|
+
style: 채점되돌림 > 1 ? { transform: `translateX(-50%) scale(${채점되돌림})`, transformOrigin: 'bottom center' } : undefined, children: MARKS.map(m => (_jsx("span", { className: cx('word-table-cell__score-slot', mark === m ? 'word-table-cell__score-slot--on' : ''), children: _jsx(Button, { htmlType: "button", size: buttonSize, type: "dot", icon: MARK_ICON[m], variant: mark === m ? MARK_VARIANT[m] : 'default', selected: mark === m, useBorder: false,
|
|
210
|
+
/* **마우스를 올리면 이름이 뜬다**(말귀 요청 ㊴, 2026-10-01). 이 셋은 아이콘만
|
|
211
|
+
있어 낭독기에는 `낙지 맞음` 으로 제대로 읽히는데 **눈으로 보는 사람에게는
|
|
212
|
+
이름이 없었다** — 세모가 '애매' 인지 '반만 맞음' 인지 알 길이 없다.
|
|
213
|
+
띄우는 글에는 **낱말을 안 넣는다**: 낱말은 바로 위 칸에 적혀 있고, 손을
|
|
214
|
+
얹은 자리에서 알고 싶은 것은 이 표식이 무엇이냐 하나다.
|
|
215
|
+
`ariaLabel` 은 그대로 둔다 — 낭독기는 칸을 안 보고 지나므로 거기서는
|
|
216
|
+
낱말이 있어야 "어느 칸의 맞음" 인지 알 수 있다. 둘이 일부러 다르다. */
|
|
217
|
+
showTooltipInfo: t(`wordTable.scoring.markName.${m}`, { defaultValue: m }), ariaLabel: t(`wordTable.scoring.${m}`, { word: cellLabel, defaultValue: `${cellLabel} ${m}` }), onClick: (e) => { e.stopPropagation(); clickMark(cellIndex, m); } }) }, m))) }));
|
|
210
218
|
};
|
|
211
219
|
const { t } = useTranslation();
|
|
212
220
|
const isEdit = editCells !== undefined;
|
|
@@ -40,6 +40,38 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
|
|
|
40
40
|
if (!open)
|
|
41
41
|
return;
|
|
42
42
|
recalc();
|
|
43
|
+
/* **판이 선 뒤에 한 번 더 잰다 — 첫 셈은 판 높이를 모른다.**
|
|
44
|
+
위 `recalc()` 은 `open` 이 참이 된 직후다. 그 시점에 판이 아직 없으면
|
|
45
|
+
`computeStyle` 은 어림값(예: `DatePicker` 의 300)으로 세고, 그 어림값으로
|
|
46
|
+
**위로 펼지 아래로 펼지**와 **창 안으로 조이기**를 둘 다 정해 버린다. 어림이
|
|
47
|
+
실제보다 작으면 판이 창 밖으로 나가고, 아무도 다시 안 잰다.
|
|
48
|
+
그리고 판 높이는 그 뒤에도 바뀐다 — 달력은 한 달이 5 줄이냐 6 줄이냐로 한 줄
|
|
49
|
+
(약 40px)이 오르내린다. 높이가 바뀔 때마다 다시 센다.
|
|
50
|
+
**높이가 달라졌을 때만** 다시 센다 — 안 그러면 셈이 자리를 바꾸고 자리가 다시
|
|
51
|
+
셈을 불러 끝없이 돈다. */
|
|
52
|
+
let 잰높이 = menuRef.current?.offsetHeight ?? 0;
|
|
53
|
+
if (잰높이 > 0)
|
|
54
|
+
recalc();
|
|
55
|
+
/* **오가는 것을 끊는다.** 높이가 달라질 때만 세는 것으로는 두 꼴을 번갈아 오가는 것을
|
|
56
|
+
못 막는다(A 로 자리를 잡으면 높이가 B 가 되고, B 로 잡으면 다시 A 가 되는 꼴).
|
|
57
|
+
한 번 여는 동안 몇 번까지만 센다 — 제대로 된 다시 재기는 한두 번이면 끝난다. */
|
|
58
|
+
let 센횟수 = 0;
|
|
59
|
+
let 판관찰 = null;
|
|
60
|
+
if (typeof ResizeObserver !== 'undefined' && menuRef.current) {
|
|
61
|
+
판관찰 = new ResizeObserver(() => {
|
|
62
|
+
const h = menuRef.current?.offsetHeight ?? 0;
|
|
63
|
+
if (h === 잰높이)
|
|
64
|
+
return;
|
|
65
|
+
잰높이 = h;
|
|
66
|
+
if (++센횟수 > 4) {
|
|
67
|
+
판관찰?.disconnect();
|
|
68
|
+
판관찰 = null;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
recalc();
|
|
72
|
+
});
|
|
73
|
+
판관찰.observe(menuRef.current);
|
|
74
|
+
}
|
|
43
75
|
const handleOutsideClick = (e) => {
|
|
44
76
|
const target = e.target;
|
|
45
77
|
if (ignoreOutsideClickSelector && target.closest?.(ignoreOutsideClickSelector))
|
|
@@ -83,6 +115,7 @@ export function useAnchoredMenu({ computeStyle, onScroll = 'close', onResize = '
|
|
|
83
115
|
window.addEventListener('orientationchange', handleOrientation);
|
|
84
116
|
}
|
|
85
117
|
return () => {
|
|
118
|
+
판관찰?.disconnect();
|
|
86
119
|
document.removeEventListener('mousedown', handleOutsideClick);
|
|
87
120
|
document.removeEventListener('pointerdown', handleOutsideClick);
|
|
88
121
|
if (onScroll !== 'ignore')
|
|
@@ -806,6 +806,11 @@
|
|
|
806
806
|
"o": "{{word}} correct",
|
|
807
807
|
"triangle": "{{word}} partial",
|
|
808
808
|
"x": "{{word}} incorrect",
|
|
809
|
+
"markName": {
|
|
810
|
+
"o": "Correct",
|
|
811
|
+
"triangle": "Partial",
|
|
812
|
+
"x": "Incorrect"
|
|
813
|
+
},
|
|
809
814
|
"button": "Score",
|
|
810
815
|
"pickTitle": "Who are you scoring?",
|
|
811
816
|
"pickSubtitle": "Choose the customer who trained with “{{name}}”.",
|
|
@@ -804,6 +804,11 @@
|
|
|
804
804
|
"o": "{{word}} 맞음",
|
|
805
805
|
"triangle": "{{word}} 애매",
|
|
806
806
|
"x": "{{word}} 틀림",
|
|
807
|
+
"markName": {
|
|
808
|
+
"o": "맞음",
|
|
809
|
+
"triangle": "애매",
|
|
810
|
+
"x": "틀림"
|
|
811
|
+
},
|
|
807
812
|
"button": "채점하기",
|
|
808
813
|
"pickTitle": "누구를 채점할까요?",
|
|
809
814
|
"pickSubtitle": "‘{{name}}’ 표로 훈련한 고객을 고르세요.",
|