@ihabdevteam/core 0.162.0 → 0.164.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 +14 -0
- package/dist/components.cjs.js +194 -179
- package/dist/components.esm.css +1 -1
- package/dist/hooks.cjs.js +1 -1
- package/dist/i18n.cjs.js +1 -1
- package/dist/ihabdevteam-core.cjs.js +199 -184
- package/dist/ihabdevteam-core.d.ts +9 -1
- package/dist/packages/core/src/components/BarChart.js +24 -16
- package/dist/packages/core/src/components/Tooltip/MenuTooltip.css +7 -0
- package/dist/packages/core/src/components/Tooltip/MenuTooltip.d.ts +3 -0
- package/dist/packages/core/src/components/Tooltip/MenuTooltip.js +11 -11
- package/dist/packages/core/src/components/barChartData.d.ts +5 -0
- package/dist/packages/core/src/components/barChartData.js +2 -0
- package/dist/packages/core/src/utils/lucideIconSet.js +8 -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
|
@@ -458,6 +458,9 @@ export declare interface BarData {
|
|
|
458
458
|
isToday?: boolean;
|
|
459
459
|
parts?: number[];
|
|
460
460
|
partsB?: number[];
|
|
461
|
+
/** 아직 오지 않은 칸 — 막대를 그리지 않고 글씨를 흐리게, 평균·합계에서 뺀다.
|
|
462
|
+
* `a === -1` 이 같은 뜻의 옛 표시인데, **음수가 값인 차트**(baseline)에서는 −1 이 진짜 값일 수 있어 이 칸을 쓴다. */
|
|
463
|
+
future?: boolean;
|
|
461
464
|
}
|
|
462
465
|
|
|
463
466
|
/**
|
|
@@ -1013,6 +1016,8 @@ export declare interface ChartPoint {
|
|
|
1013
1016
|
b: number;
|
|
1014
1017
|
parts?: number[];
|
|
1015
1018
|
partsB?: number[];
|
|
1019
|
+
/** 아직 오지 않은 날 — `a === -1` 과 같은 뜻이고, 음수를 값으로 쓰는 차트(baseline)에서는 이쪽을 쓴다. */
|
|
1020
|
+
future?: boolean;
|
|
1016
1021
|
}
|
|
1017
1022
|
|
|
1018
1023
|
/**
|
|
@@ -1500,7 +1505,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1500
1505
|
*
|
|
1501
1506
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1502
1507
|
*/
|
|
1503
|
-
export declare const CORE_VERSION = "0.
|
|
1508
|
+
export declare const CORE_VERSION = "0.164.0";
|
|
1504
1509
|
|
|
1505
1510
|
/** @public */
|
|
1506
1511
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -7023,6 +7028,9 @@ export declare type TooltipMenuColumnItem = {
|
|
|
7023
7028
|
disabled?: boolean;
|
|
7024
7029
|
/** 비활성 사유 등 hover 설명(title 속성). */
|
|
7025
7030
|
tooltip?: string;
|
|
7031
|
+
/** 이 항목 **앞** 에 가로 구분선을 그린다 — 성격이 다른 묶음을 가를 때(보기 고르개 항목들 ↔ 창을 여는 항목).
|
|
7032
|
+
* 선은 그리기만 할 뿐 항목이 아니라, 키보드 차례·낭독기 목록에 끼지 않는다(`role="separator"`). 첫 항목에 주면 무시한다. */
|
|
7033
|
+
separatorBefore?: boolean;
|
|
7026
7034
|
/** 우측 원형 배지 숫자(알림 개수 등). 라벨에 "(5)"를 붙이는 대신 SideNav와 같은
|
|
7027
7035
|
* Badge type="dot"으로 표시한다 — 두 메뉴가 같은 항목을 다르게 보여주지 않도록. */
|
|
7028
7036
|
badgeCount?: number;
|
|
@@ -16,24 +16,31 @@ import { useBarChartControl } from './useBarChartControl';
|
|
|
16
16
|
// ─── 막대 렌더링 원시 컴포넌트 ───────────────────────────────────
|
|
17
17
|
/** 막대 값 — 수가 아니면(undefined·NaN·Infinity) 0 */
|
|
18
18
|
const 수 = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
19
|
+
/** 아직 오지 않은 칸인가 — 옛 표시(a === -1)와 새 표시(future)를 함께 본다.
|
|
20
|
+
* 음수가 값인 차트(baseline)에서는 −1 이 진짜 값일 수 있어 `future` 를 쓴다. */
|
|
21
|
+
function 미래칸(d, 옛표시 = true) {
|
|
22
|
+
/* `baseline` 을 쓰는 차트는 −1 이 진짜 값이라 옛 표시를 보지 않는다 — 부르는 쪽이 옛표시=false 로 끈다 */
|
|
23
|
+
return d.future === true || (옛표시 && d.a === -1);
|
|
24
|
+
}
|
|
19
25
|
function BarChartCanvas({ data, colorA, colorB, labelA, labelB, barWidth = 10, barGap = 1, height = 140, maxOverride, tightMax, seriesColors, seriesLabels, rateLabels, grouped = false, baseline }) {
|
|
20
26
|
// colorB 미지정 시 단일 계열(A) 모드
|
|
21
27
|
const single = !colorB;
|
|
22
28
|
const stacked = !!(seriesColors && seriesColors.length > 0);
|
|
23
29
|
/* 값이 비었거나(undefined) 수가 아니면 0 으로 친다. 두 계열인데 한 항목의 `b` 가 비면 Math.max 가 NaN 이 되고
|
|
24
30
|
`NaN || 10` 으로 y축이 데이터와 상관없이 10 에 박혀 막대가 판 위로 넘쳤다(ihab-core-96 이 짚음). */
|
|
31
|
+
/* 기준선 모드에서는 옛 미래 표시(a === -1)를 보지 않는다 — −1 이 진짜 값이다 */
|
|
32
|
+
const 기준 = baseline;
|
|
25
33
|
const maxVal = useMemo(() => stacked
|
|
26
|
-
? Math.max(0, ...data.filter(d => d
|
|
34
|
+
? Math.max(0, ...data.filter(d => !미래칸(d, 기준 == null)).map(d => grouped
|
|
27
35
|
/* 나란히 서면 가장 높은 **낱 막대**가 위를 정한다 — 합으로 잡으면 막대가 판의 몇 분의 일로 눌린다 */
|
|
28
36
|
? Math.max(0, ...(d.parts ?? []).map(수))
|
|
29
37
|
: (d.parts ?? []).reduce((s, v) => s + Math.max(0, 수(v)), 0)))
|
|
30
|
-
: Math.max(0, ...data.filter(d => d
|
|
38
|
+
: Math.max(0, ...data.filter(d => !미래칸(d, 기준 == null)).flatMap(d => single ? [수(d.a)] : [수(d.a), 수(d.b)])), [data, single, stacked, grouped]);
|
|
31
39
|
/* 기준선 모드: 0 선에서 얼마나 떨어졌는지(|값 − 기준|)가 축을 정한다 — 위아래가 대칭이라야 같은 크기의 오름·내림이 같은 길이로 읽힌다 */
|
|
32
|
-
const 기준 = baseline;
|
|
33
40
|
const 대칭최대 = useMemo(() => {
|
|
34
41
|
if (기준 == null)
|
|
35
42
|
return 0;
|
|
36
|
-
const 값들 = data.filter(d => d
|
|
43
|
+
const 값들 = data.filter(d => !미래칸(d, 기준 == null)).flatMap(d => stacked ? (d.parts ?? []).map(수) : (single ? [수(d.a)] : [수(d.a), 수(d.b)]));
|
|
37
44
|
return Math.max(0, ...값들.map(v => Math.abs(v - 기준)));
|
|
38
45
|
}, [data, 기준, stacked, single]);
|
|
39
46
|
const padded = useMemo(() => {
|
|
@@ -91,7 +98,7 @@ function BarChartCanvas({ data, colorA, colorB, labelA, labelB, barWidth = 10, b
|
|
|
91
98
|
return (_jsxs("div", { className: "manager-bar-chart-wrap", style: 묶음간격, children: [_jsx("div", { className: "manager-bar-chart-inner", children: _jsx("div", { className: "manager-bar-chart-scroll", children: _jsxs("div", { className: "manager-bar-chart-area", children: [_jsxs("div", { className: "manager-bar-chart-plot", style: { height: h }, children: [ticks.slice(0, -1).map((t, i) => (
|
|
92
99
|
/* 눈금선은 **적힌 값의 높이**에 둔다 — 가운데 값을 반올림(최대 3 → '2')하면서 자리는 절반(1.5)에 두어 선과 숫자가 어긋났다 */
|
|
93
100
|
_jsx("div", { className: `manager-bar-chart-gridline${기준 != null && t === 기준 ? ' manager-bar-chart-gridline--baseline' : ''}`, style: { top: 눈금자리(t) * h }, children: _jsx("span", { className: "manager-bar-chart-gridline__label caption-1", children: t }) }, i))), data.map((d, i) => {
|
|
94
|
-
const isFuture = d
|
|
101
|
+
const isFuture = 미래칸(d, 기준 == null);
|
|
95
102
|
/* 판 높이를 넘지 않는다 — maxOverride(진행률 100)보다 큰 값이 와도 막대가 눈금 위로 삐져나가지 않게 */
|
|
96
103
|
/* 기준선 모드는 반쪽(h/2)을 쓴다 — 0 선에서 위나 아래로 자란다 */
|
|
97
104
|
const 반 = h / 2;
|
|
@@ -159,7 +166,7 @@ function BarChartCanvas({ data, colorA, colorB, labelA, labelB, barWidth = 10, b
|
|
|
159
166
|
});
|
|
160
167
|
})() })) : (기준 != null ? (_jsx(_Fragment, { children: [{ v: 수(d.a), c: colorA }, ...(single ? [] : [{ v: 수(d.b), c: colorB }])].map(({ v, c }, k) => (_jsxs("div", { className: "manager-bar-chart-slot", style: 막대굵기, children: [_jsx("div", { className: "manager-bar-chart-slot__half manager-bar-chart-slot__half--up", children: v >= 기준 && _jsx("div", { className: "manager-bar-chart-bar manager-bar-chart-bar--full", style: { height: 높이(v), background: c } }) }), _jsx("div", { className: "manager-bar-chart-slot__half manager-bar-chart-slot__half--down", children: v < 기준 && _jsx("div", { className: "manager-bar-chart-bar manager-bar-chart-bar--full manager-bar-chart-bar--down", style: { height: 높이(v), background: c } }) })] }, k))) })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "manager-bar-chart-bar", style: { ...막대굵기, height: ha, background: colorA } }), !single && _jsx("div", { className: "manager-bar-chart-bar", style: { ...막대굵기, height: hb, background: colorB } })] })))) }, i));
|
|
161
168
|
})] }), _jsx("div", { className: "manager-bar-chart-x-axis", children: data.map((d, i) => {
|
|
162
|
-
const isFuture = d
|
|
169
|
+
const isFuture = 미래칸(d, 기준 == null); // 아직 도래하지 않은 날 — 글씨 흐리게
|
|
163
170
|
return (_jsxs("span", { className: `manager-bar-chart-x-axis__label${isFuture ? ' manager-bar-chart-x-axis__label--future' : ''}`, children: [_jsxs("span", { className: "caption-1 manager-bar-chart-x-axis__main", children: [d.isToday && _jsx("span", { className: "manager-bar-chart-x-axis__today-dot", style: { background: colorA } }), d.label] }), d.sublabel && _jsx("span", { className: "caption-1", children: d.sublabel })] }, i));
|
|
164
171
|
}) })] }) }) }), tooltip && (_jsx(DescriptionTooltip, { anchorRect: tooltip.rect, title: tooltip.title, items: tooltip.items, showIndex: false, closeOnOutsideClick: false }))] }));
|
|
165
172
|
}
|
|
@@ -180,18 +187,18 @@ export function BarChartControls({ control }) {
|
|
|
180
187
|
* 주 보기의 막대·합계와 월 보기 완료율 주 막대가 **이 한 벌**을 쓴다 — 같은 주가 두 보기에서
|
|
181
188
|
* 다른 수로 서지 않게(2026-09-12 말귀 검수: 주 보기 25% · 월 보기 38%).
|
|
182
189
|
*/
|
|
183
|
-
function 주의날들(data, monday, now) {
|
|
190
|
+
function 주의날들(data, monday, now, 옛표시 = true) {
|
|
184
191
|
const 오늘 = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
185
192
|
return Array.from({ length: 7 }, (_, i) => {
|
|
186
193
|
const dt = new Date(monday);
|
|
187
194
|
dt.setDate(monday.getDate() + i);
|
|
188
195
|
const entry = (data[`${dt.getFullYear()}년 ${dt.getMonth() + 1}월`]?.daily ?? [])[dt.getDate() - 1];
|
|
189
|
-
const isFuture = entry
|
|
196
|
+
const isFuture = (entry != null && 미래칸(entry, 옛표시)) || dt > 오늘; // 미래 날 — 평균/막대에서 제외
|
|
190
197
|
return { dt, entry, isFuture };
|
|
191
198
|
});
|
|
192
199
|
}
|
|
193
200
|
/** control(기간)에 따라 data를 막대 배열로 변환 */
|
|
194
|
-
function barsFromControl(data, control, t, lang) {
|
|
201
|
+
function barsFromControl(data, control, t, lang, 옛표시 = true) {
|
|
195
202
|
const { period, monthRange, weekRange, weekRanges, DOW } = control;
|
|
196
203
|
const isKo = lang?.startsWith('ko');
|
|
197
204
|
const now = getTodayKstDate(); // 칸은 KST 달력 — 오늘·이번 주 표시도 KST 로
|
|
@@ -220,19 +227,20 @@ function barsFromControl(data, control, t, lang) {
|
|
|
220
227
|
// 시작일이 오늘 이후(또는 미래 달)면 아직 도래하지 않은 주 → 흐리게/막대 미표시
|
|
221
228
|
isFutureWeek = year > nowY || (year === nowY && (month > nowM || (month === nowM && startDay > nowD)));
|
|
222
229
|
}
|
|
223
|
-
return { ...d, label, sublabel: isKo ? `${KO_ORD[n - 1] ?? n}주` : `Week ${n}`, isToday, a: isFutureWeek ? -1 : d.a, b: isFutureWeek ? 0 : d.b };
|
|
230
|
+
return { ...d, label, sublabel: isKo ? `${KO_ORD[n - 1] ?? n}주` : `Week ${n}`, isToday, future: (d.future === true || isFutureWeek) || undefined, a: isFutureWeek ? -1 : d.a, b: isFutureWeek ? 0 : d.b };
|
|
224
231
|
});
|
|
225
232
|
}
|
|
226
233
|
const opt = weekRanges.find(o => o.value === weekRange);
|
|
227
234
|
if (!opt)
|
|
228
235
|
return [];
|
|
229
236
|
const monday = new Date(opt.year, opt.month - 1, firstMondayOfMonth(opt.year, opt.month - 1) + opt.week * 7);
|
|
230
|
-
return 주의날들(data, monday, now).map(({ dt, entry, isFuture }) => {
|
|
237
|
+
return 주의날들(data, monday, now, 옛표시).map(({ dt, entry, isFuture }) => {
|
|
231
238
|
const dM = dt.getMonth() + 1, dD = dt.getDate(), dY = dt.getFullYear();
|
|
232
239
|
const sameMonth = dM === opt.month && dY === opt.year;
|
|
233
240
|
return {
|
|
234
241
|
label: sameMonth ? `${dD}` : `${dM}/${dD}`,
|
|
235
242
|
sublabel: DOW[dt.getDay()] ?? '',
|
|
243
|
+
future: isFuture || undefined,
|
|
236
244
|
a: isFuture ? -1 : (entry && entry.a > 0 ? entry.a : 0),
|
|
237
245
|
b: isFuture ? 0 : (entry && entry.b > 0 ? entry.b : 0),
|
|
238
246
|
isToday: dY === nowY && dM === nowM && dD === nowD,
|
|
@@ -259,7 +267,7 @@ export default function BarChart({ size = 'medium', barWidth, titleKey, data, co
|
|
|
259
267
|
// 외부 control 미지정 시 내부 컨트롤 사용 (단독 차트 하위 호환)
|
|
260
268
|
const internalControl = useBarChartControl(data);
|
|
261
269
|
const control = controlProp ?? internalControl;
|
|
262
|
-
const rawBars = useMemo(() => barsFromControl(data, control, t, i18n.language), [data, control, t, i18n.language]);
|
|
270
|
+
const rawBars = useMemo(() => barsFromControl(data, control, t, i18n.language, baseline == null), [data, control, t, i18n.language, baseline]);
|
|
263
271
|
// 완료율 모드: a = 진행률(a/b×100). labelBKey 지정 시 b = 정답률(parts[0]/parts[1]×100) 2계열.
|
|
264
272
|
// 월 보기의 주 막대는 **그 주 경과한 날의 일별 진행률 평균(미기록일 0%)** — 아래 total 과
|
|
265
273
|
// 주 보기가 쓰는 같은 셈. 주 합 a/b 로 두면 기록 없는 날(b=0)이 빠져 같은 주가 두 보기에서
|
|
@@ -299,7 +307,7 @@ export default function BarChart({ size = 'medium', barWidth, titleKey, data, co
|
|
|
299
307
|
};
|
|
300
308
|
return rawBars.map((d, i) => {
|
|
301
309
|
let a = d.a === -1 ? -1 : (d.b > 0 ? Math.round((d.a / d.b) * 100) : 0);
|
|
302
|
-
if (월 && d
|
|
310
|
+
if (월 && !미래칸(d)) {
|
|
303
311
|
const y = Number(월[1]), m0 = Number(월[2]) - 1;
|
|
304
312
|
const 지난날 = 주의날들(data, new Date(y, m0, firstMondayOfMonth(y, m0) + i * 7), now).filter(x => !x.isFuture);
|
|
305
313
|
if (rateAgg === 'weighted') {
|
|
@@ -319,7 +327,7 @@ export default function BarChart({ size = 'medium', barWidth, titleKey, data, co
|
|
|
319
327
|
}
|
|
320
328
|
}
|
|
321
329
|
let 계열 = null;
|
|
322
|
-
if (나란히 && d
|
|
330
|
+
if (나란히 && !미래칸(d)) {
|
|
323
331
|
let 날들;
|
|
324
332
|
if (월) {
|
|
325
333
|
const y = Number(월[1]), m0 = Number(월[2]) - 1;
|
|
@@ -373,7 +381,7 @@ export default function BarChart({ size = 'medium', barWidth, titleKey, data, co
|
|
|
373
381
|
/* 기준선 모드는 부호를 살려 더한다 — 오르내림의 **순변화** 가 합계다(양수만 더하면 내린 주가 사라진다).
|
|
374
382
|
미래 표시(a === -1)는 여기서도 뺀다 — 기준선 모드에서도 -1 은 '아직 오지 않은 칸' 이다(실제 -1 이 나올 수 있으면 그 칸을 자료에서 뺀다). */
|
|
375
383
|
if (baseline != null) {
|
|
376
|
-
return 합칠것.reduce((s, d) => s + (d
|
|
384
|
+
return 합칠것.reduce((s, d) => s + (미래칸(d, false) ? 0 : 수(d.a) + (labelBKey ? 수(d.b) : 0)), 0);
|
|
377
385
|
}
|
|
378
386
|
return 합칠것.reduce((s, d) => s + (d.a > 0 ? d.a : 0) + (labelBKey && d.b > 0 ? d.b : 0), 0);
|
|
379
387
|
}, [bars, rawBars, rate, rateAgg, avgMode, baseline, labelBKey, control.period, control.monthRange, data]);
|
|
@@ -389,7 +397,7 @@ export default function BarChart({ size = 'medium', barWidth, titleKey, data, co
|
|
|
389
397
|
const isLarge = size === 'large';
|
|
390
398
|
const sectionClassName = [isSmall ? 'manager-section--compact' : isLarge ? 'manager-section--full' : undefined, className]
|
|
391
399
|
.filter(Boolean).join(' ') || undefined;
|
|
392
|
-
return (_jsx(ChartSection, { className: sectionClassName, inGrid: inGrid, title: t(titleKey), titleClassName: isSmall ? 'h5 chart-section-title--small' : undefined, headRight: isSmall ? (headExtra ?? undefined) : _jsxs(_Fragment, { children: [!hideControls && _jsx(BarChartControls, { control: control }), headExtra, onDetail && (_jsx("span", { className: "manager-bar-chart-detail-btn", children: _jsx(Button, { htmlType: "button", size: detailSize, type: "dot", icon: "list", ariaLabel: t('trainingStatus.detail', '상세보기'), onClick: onDetail }) }))] }), subRow: _jsxs(_Fragment, { children: [showTotal ? ((rate || avgMode || (!!totalLabelKey && !!totalFormat)) ? (
|
|
400
|
+
return (_jsx(ChartSection, { className: sectionClassName, inGrid: inGrid, title: t(titleKey), titleClassName: isSmall ? 'h5 chart-section-title--small' : undefined, headRight: isSmall ? (headExtra ?? undefined) : _jsxs(_Fragment, { children: [!hideControls && _jsx(BarChartControls, { control: control }), headExtra, onDetail && (_jsx("span", { className: "manager-bar-chart-detail-btn", children: _jsx(Button, { htmlType: "button", size: detailSize, type: "dot", icon: "list", ariaLabel: t('trainingStatus.detail', '상세보기'), showTooltipInfo: t('trainingStatus.detail', '상세보기'), onClick: onDetail }) }))] }), subRow: _jsxs(_Fragment, { children: [showTotal ? ((rate || avgMode || (!!totalLabelKey && !!totalFormat)) ? (
|
|
393
401
|
// 진행률/평균 모드 또는 totalLabelKey+totalFormat 조합: 라벨(윗줄) + 값(아랫줄). size='small'은 라벨 생략
|
|
394
402
|
_jsx(ChartTotal, { label: isSmall ? undefined : `${t(control.period === 'week' ? 'manager.chart.weeklyPrefix' : 'manager.chart.monthlyPrefix')} ${t(totalLabelKey ?? labelAKey)}`, children: totalFormat ? (
|
|
395
403
|
// 숫자는 h0, 단위(시간/분/초 등)는 h5로 분리
|
|
@@ -13,6 +13,9 @@ export type TooltipMenuColumnItem = {
|
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
/** 비활성 사유 등 hover 설명(title 속성). */
|
|
15
15
|
tooltip?: string;
|
|
16
|
+
/** 이 항목 **앞** 에 가로 구분선을 그린다 — 성격이 다른 묶음을 가를 때(보기 고르개 항목들 ↔ 창을 여는 항목).
|
|
17
|
+
* 선은 그리기만 할 뿐 항목이 아니라, 키보드 차례·낭독기 목록에 끼지 않는다(`role="separator"`). 첫 항목에 주면 무시한다. */
|
|
18
|
+
separatorBefore?: boolean;
|
|
16
19
|
/** 우측 원형 배지 숫자(알림 개수 등). 라벨에 "(5)"를 붙이는 대신 SideNav와 같은
|
|
17
20
|
* Badge type="dot"으로 표시한다 — 두 메뉴가 같은 항목을 다르게 보여주지 않도록. */
|
|
18
21
|
badgeCount?: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { Fragment, useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import Button from '../Button';
|
|
6
6
|
import Badge from '../Badge';
|
|
@@ -81,14 +81,14 @@ export default function MenuTooltip({ columns = [], onClose, className = '', siz
|
|
|
81
81
|
}
|
|
82
82
|
catch (err) { }
|
|
83
83
|
}, [onClose, 보이는깊이]);
|
|
84
|
-
return (_jsxs("div", { className: `tooltip-menu tooltip-menu--${size} ${className}`, children: [(stack.length > 1 || current.title) && (_jsxs("div", { className: "tooltip-menu__header", children: [stack.length > 1 ? (_jsx(Button, { htmlType: "button", size: itemSize, type: "dot", icon: "chevron-left", ariaLabel: t('common.back', '뒤로'), onClick: goBack, variant: "transparent-gray", useBorder: false })) : null, _jsx("div", { className: "tooltip-menu__title button-1", children: current.title })] })), _jsx("div", { className: "tooltip-menu__columns", ref: itemsWrapRef, children: current.columns && current.columns.map((col, ci) => (_jsxs("div", { className: "tooltip-menu__column", children: [col.title ? _jsx("div", { className: "tooltip-menu__column-title body-3", children: col.title }) : null, _jsx("div", { className: "tooltip-menu__items", children: (col.items || []).map((item, idx) => (_jsx(Button, { htmlType: "button", className: "tooltip-menu__item", size: itemSize, type: item.label ? 'bar' : 'dot', icon: item.selected ? 'check' : (item.icon || 'none'), rightIcon: Array.isArray(item.children) && item.children.length
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
return (_jsxs("div", { className: `tooltip-menu tooltip-menu--${size} ${className}`, children: [(stack.length > 1 || current.title) && (_jsxs("div", { className: "tooltip-menu__header", children: [stack.length > 1 ? (_jsx(Button, { htmlType: "button", size: itemSize, type: "dot", icon: "chevron-left", ariaLabel: t('common.back', '뒤로'), onClick: goBack, variant: "transparent-gray", useBorder: false })) : null, _jsx("div", { className: "tooltip-menu__title button-1", children: current.title })] })), _jsx("div", { className: "tooltip-menu__columns", ref: itemsWrapRef, children: current.columns && current.columns.map((col, ci) => (_jsxs("div", { className: "tooltip-menu__column", children: [col.title ? _jsx("div", { className: "tooltip-menu__column-title body-3", children: col.title }) : null, _jsx("div", { className: "tooltip-menu__items", children: (col.items || []).map((item, idx) => (_jsxs(Fragment, { children: [item.separatorBefore && idx > 0 && _jsx("div", { className: "tooltip-menu__separator", role: "separator" }), _jsx(Button, { htmlType: "button", className: "tooltip-menu__item", size: itemSize, type: item.label ? 'bar' : 'dot', icon: item.selected ? 'check' : (item.icon || 'none'), rightIcon: Array.isArray(item.children) && item.children.length
|
|
85
|
+
? 'chevron-right'
|
|
86
|
+
: item.badgeCount
|
|
87
|
+
? _jsx(Badge, { type: "dot", position: "inline", number: Math.min(item.badgeCount, 99), size: 24, "aria-hidden": true })
|
|
88
|
+
: undefined, label: item.label,
|
|
89
|
+
/* 개수는 이름에 붙인다 — ariaLabel 이 글자뿐이라 배지 수가 이름에서 빠져 "알림" 으로만 읽혔다
|
|
90
|
+
(SideNav·BottomNav 와 같은 nav.badgeLabel "알림, 5개"). */
|
|
91
|
+
ariaLabel: item.label && item.badgeCount && item.badgeCount > 0
|
|
92
|
+
? t('nav.badgeLabel', { label: item.label, count: item.badgeCount, defaultValue: '{{label}}, {{count}}개' })
|
|
93
|
+
: item.label, onClick: () => handleClick(item, ci, idx), selected: item.selected, disabled: item.disabled, showTooltipInfo: item.tooltip || false, useBorder: false, align: "left" })] }, idx))) })] }, ci))) })] }));
|
|
94
94
|
}
|
|
@@ -11,6 +11,9 @@ export interface BarData {
|
|
|
11
11
|
isToday?: boolean;
|
|
12
12
|
parts?: number[];
|
|
13
13
|
partsB?: number[];
|
|
14
|
+
/** 아직 오지 않은 칸 — 막대를 그리지 않고 글씨를 흐리게, 평균·합계에서 뺀다.
|
|
15
|
+
* `a === -1` 이 같은 뜻의 옛 표시인데, **음수가 값인 차트**(baseline)에서는 −1 이 진짜 값일 수 있어 이 칸을 쓴다. */
|
|
16
|
+
future?: boolean;
|
|
14
17
|
}
|
|
15
18
|
/** 한 구간(월 등)의 주별/일별 막대 데이터 묶음 */
|
|
16
19
|
export type MonthBarData = {
|
|
@@ -26,6 +29,8 @@ export interface ChartPoint {
|
|
|
26
29
|
b: number;
|
|
27
30
|
parts?: number[];
|
|
28
31
|
partsB?: number[];
|
|
32
|
+
/** 아직 오지 않은 날 — `a === -1` 과 같은 뜻이고, 음수를 값으로 쓰는 차트(baseline)에서는 이쪽을 쓴다. */
|
|
33
|
+
future?: boolean;
|
|
29
34
|
}
|
|
30
35
|
/** 해당 월(0-based)의 첫 월요일 날짜(1~7). 예: 2026-05(금요일 시작) → 4 */
|
|
31
36
|
export declare function firstMondayOfMonth(year: number, month0: number): number;
|
|
@@ -113,6 +113,8 @@ export function buildMonthChartData(points) {
|
|
|
113
113
|
const isFuture = isCurMonth && d > today;
|
|
114
114
|
daily.push({
|
|
115
115
|
label: String(d),
|
|
116
|
+
/* 미래는 두 표시를 함께 단다 — 음수를 값으로 쓰는 차트(baseline)는 -1 을 진짜 값으로 읽으므로 future 를 본다 */
|
|
117
|
+
...(isFuture && dayA[d] === 0 && dayB[d] === 0 ? { future: true } : {}),
|
|
116
118
|
a: isFuture && dayA[d] === 0 && dayB[d] === 0 ? -1 : dayA[d],
|
|
117
119
|
b: dayB[d],
|
|
118
120
|
...(partsLen ? { parts: dayParts[d] } : {}),
|
|
@@ -167,6 +167,9 @@ import CalendarIcon from 'lucide-react/dist/esm/icons/calendar.mjs';
|
|
|
167
167
|
import CarIcon from 'lucide-react/dist/esm/icons/car.mjs';
|
|
168
168
|
import CoffeeIcon from 'lucide-react/dist/esm/icons/coffee.mjs';
|
|
169
169
|
import Gamepad2Icon from 'lucide-react/dist/esm/icons/gamepad-2.mjs';
|
|
170
|
+
import ClockIcon from 'lucide-react/dist/esm/icons/clock.mjs';
|
|
171
|
+
import TargetIcon from 'lucide-react/dist/esm/icons/target.mjs';
|
|
172
|
+
import PercentIcon from 'lucide-react/dist/esm/icons/percent.mjs';
|
|
170
173
|
import InboxIcon from 'lucide-react/dist/esm/icons/inbox.mjs';
|
|
171
174
|
import ListTreeIcon from 'lucide-react/dist/esm/icons/list-tree.mjs';
|
|
172
175
|
import MessageSquareMoreIcon from 'lucide-react/dist/esm/icons/message-square-more.mjs';
|
|
@@ -254,6 +257,11 @@ export const STATIC_LUCIDE_ICONS = {
|
|
|
254
257
|
'folder-plus': FolderPlusIcon,
|
|
255
258
|
'gamepad': GamepadIcon,
|
|
256
259
|
'gamepad-2': Gamepad2Icon,
|
|
260
|
+
/* 시각·시간을 뜻하는 이름이 하나도 없었다(말귀 '⋯' 메뉴의 '시간' 항목) */
|
|
261
|
+
'clock': ClockIcon,
|
|
262
|
+
/* 정답률·목표 — target 과 percent 를 함께 둔다(쓰는 쪽이 뜻에 맞는 것을 고른다) */
|
|
263
|
+
'target': TargetIcon,
|
|
264
|
+
'percent': PercentIcon,
|
|
257
265
|
'gavel': GavelIcon,
|
|
258
266
|
'git-merge': GitMergeIcon,
|
|
259
267
|
'globe': GlobeIcon,
|