@ihabdevteam/core 0.324.0 → 0.325.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 +37 -0
- package/dist/components.cjs.js +182 -182
- package/dist/hooks.cjs.js +3 -3
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +185 -185
- package/dist/ihabdevteam-core.d.ts +1 -1
- package/dist/packages/core/src/components/Calendar.js +2 -1
- package/dist/packages/core/src/components/DatePicker.js +3 -2
- package/dist/packages/core/src/components/useBarChartControl.js +3 -2
- package/dist/packages/core/src/i18n/locales/en.json +0 -1
- package/dist/packages/core/src/i18n/locales/ko.json +0 -1
- package/dist/packages/core/src/utils/date.d.ts +27 -0
- package/dist/packages/core/src/utils/date.js +37 -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 +3 -3
- package/package.json +1 -1
|
@@ -1678,7 +1678,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1678
1678
|
*
|
|
1679
1679
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1680
1680
|
*/
|
|
1681
|
-
export declare const CORE_VERSION = "0.
|
|
1681
|
+
export declare const CORE_VERSION = "0.325.0";
|
|
1682
1682
|
|
|
1683
1683
|
/** @public */
|
|
1684
1684
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useMemo, useState, useRef } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { yearMonthLabel } from '../utils/date';
|
|
5
6
|
import Icon from './Icon';
|
|
6
7
|
import { formatTimeHM } from '../utils/format';
|
|
7
8
|
import { todayYmdOr, formatDateLocale } from '../utils/date';
|
|
@@ -55,7 +56,7 @@ export default function Calendar({ visits = [], onVisitClick, onVisitMove, onDay
|
|
|
55
56
|
setMonth(m => m + 1); }, [month]);
|
|
56
57
|
const fmtDate = (d) => `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
|
|
57
58
|
const isToday = (d) => fmtDate(d) === todayYmd;
|
|
58
|
-
return (_jsxs("div", { children: [_jsxs("div", { className: "calendar-nav", children: [_jsx("button", { type: "button", className: "calendar-nav__button", onClick: prev, "aria-label": t('calendar.prevMonth', '이전 달'), children: _jsx(Icon, { icon: "chevron-left", label: "", className: "", svgClassName: "calendar-nav__button-icon", size: 18 }) }), _jsx("span", { className: "calendar-nav__title h5", "aria-live": "polite", children: t
|
|
59
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "calendar-nav", children: [_jsx("button", { type: "button", className: "calendar-nav__button", onClick: prev, "aria-label": t('calendar.prevMonth', '이전 달'), children: _jsx(Icon, { icon: "chevron-left", label: "", className: "", svgClassName: "calendar-nav__button-icon", size: 18 }) }), _jsx("span", { className: "calendar-nav__title h5", "aria-live": "polite", children: yearMonthLabel(t, i18n.language, year, month + 1) }), _jsx("button", { type: "button", className: "calendar-nav__button", onClick: next, "aria-label": t('calendar.nextMonth', '다음 달'), children: _jsx(Icon, { icon: "chevron-right", label: "", className: "", svgClassName: "calendar-nav__button-icon", size: 18 }) }), _jsxs("div", { className: "calendar-nav__legend", children: [_jsx("span", { className: "calendar-nav__legend-dot calendar-nav__legend-dot--main" }), _jsx("span", { className: "caption-1", children: t('calendar.legend.visited', '방문') }), _jsx("span", { className: "calendar-nav__legend-dot calendar-nav__legend-dot--sub" }), _jsx("span", { className: "caption-1", children: t('calendar.legend.scheduled', '예약') }), _jsx("span", { className: "calendar-nav__legend-dot calendar-nav__legend-dot--disabled" }), _jsx("span", { className: "caption-1", children: t('calendar.legend.noVisit', '미방문') })] })] }), _jsxs("div", { className: "calendar-grid", children: [DOW.map(d => _jsx("div", { className: "calendar-day-header body-3", children: d }, d)), cells.map((d, i) => {
|
|
59
60
|
if (d === null)
|
|
60
61
|
return _jsx("div", { className: "calendar-cell empty" }, `empty-${i}`);
|
|
61
62
|
const day = d;
|
|
@@ -8,6 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
import { cx } from '../utils/cx';
|
|
9
9
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
|
+
import { yearMonthLabel } from '../utils/date';
|
|
11
12
|
import { createPortal } from 'react-dom';
|
|
12
13
|
import { todayYmdOr } from '../utils/date';
|
|
13
14
|
import Calendar from 'lucide-react/dist/esm/icons/calendar.mjs';
|
|
@@ -48,7 +49,7 @@ function buildYMOptions(viewY, ymLabel, min, max) {
|
|
|
48
49
|
return opts;
|
|
49
50
|
}
|
|
50
51
|
export default function DatePicker({ value, onChange, size = 48, min, max, disabled, placeholder: placeholderProp, className, today, }) {
|
|
51
|
-
const { t } = useTranslation();
|
|
52
|
+
const { t, i18n } = useTranslation();
|
|
52
53
|
/* 기본 안내 문구도 사전에서 — 예전에는 '날짜 선택' 이 박혀 있어 영어 화면에서도 한국어였다.
|
|
53
54
|
달력 판의 이름(`datePicker.chooseDate`)과 같은 말이라 같은 열쇠를 쓴다. */
|
|
54
55
|
const placeholder = placeholderProp ?? t('datePicker.chooseDate', '날짜 선택');
|
|
@@ -254,7 +255,7 @@ export default function DatePicker({ value, onChange, size = 48, min, max, disab
|
|
|
254
255
|
for (let d = 1; d <= daysInMonth(viewY, viewM); d++)
|
|
255
256
|
cells.push(d);
|
|
256
257
|
const iconSize = size === 64 ? 20 : size === 40 ? 16 : 18;
|
|
257
|
-
const ymOptions = buildYMOptions(viewY, (year, month) => t
|
|
258
|
+
const ymOptions = buildYMOptions(viewY, (year, month) => yearMonthLabel(t, i18n.language, year, month), min, max);
|
|
258
259
|
const ymValue = `${viewY}-${String(viewM + 1).padStart(2, '0')}`;
|
|
259
260
|
const dropdown = open ? createPortal(_jsxs("div", { ref: menuRef, id: panelId, className: "date-picker__dropdown", style: dropdownStyle, "data-tooltip-ignore": true, role: "dialog", "aria-label": t('datePicker.chooseDate', '날짜 선택'), tabIndex: -1, children: [_jsxs("div", { className: "date-picker__nav", children: [_jsx("button", { type: "button", className: "date-picker__nav-btn", onClick: prevMonth, "aria-label": t('calendar.prevMonth', '이전 달'), children: _jsx(ChevronLeft, { size: 16, strokeWidth: 2 }) }), _jsx(Dropdown, { className: "date-picker__nav-dropdown", ariaLabel: t('datePicker.selectYearMonth', '연월 선택'), options: ymOptions, value: ymValue, onChange: (v) => {
|
|
260
261
|
const [y, m] = v.split('-');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useState, useEffect, useMemo, useCallback } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { yearMonthLabel } from '../utils/date';
|
|
4
5
|
import { firstMondayOfMonth, todayWeekValue, monthOptionsNow, weekLabel } from './barChartData';
|
|
5
6
|
/**
|
|
6
7
|
* BarChart 기간 상태/옵션/네비게이션을 캡슐화한 훅.
|
|
@@ -8,7 +9,7 @@ import { firstMondayOfMonth, todayWeekValue, monthOptionsNow, weekLabel } from '
|
|
|
8
9
|
* @public
|
|
9
10
|
*/
|
|
10
11
|
export function useBarChartControl(data) {
|
|
11
|
-
const { t } = useTranslation();
|
|
12
|
+
const { t, i18n } = useTranslation();
|
|
12
13
|
const [period, setPeriod] = useState('week');
|
|
13
14
|
// 달 옵션·오늘은 자료가 바뀔 때 다시 센다 — 모듈 로드 때 한 번 세면 자정을 넘긴 탭에서 이번 달·오늘 주가 옛날에 굳었다
|
|
14
15
|
const DATA_MONTH_OPTIONS = useMemo(() => monthOptionsNow().filter(o => o.value in data), [data]);
|
|
@@ -25,7 +26,7 @@ export function useBarChartControl(data) {
|
|
|
25
26
|
return opts;
|
|
26
27
|
}, [DATA_MONTH_OPTIONS]);
|
|
27
28
|
const weekOptionLabel = useCallback((o) => {
|
|
28
|
-
const m = t
|
|
29
|
+
const m = yearMonthLabel(t, i18n.language, o.year, o.month);
|
|
29
30
|
return `${m} ${weekLabel(t, o.week)}`;
|
|
30
31
|
}, [t]);
|
|
31
32
|
const monthOptions = useMemo(() => DATA_MONTH_OPTIONS.map(o => ({
|
|
@@ -12,6 +12,33 @@ export declare function formatDateLongKo(iso: string): string;
|
|
|
12
12
|
* @param lang useTranslation()의 i18n.language
|
|
13
13
|
*/
|
|
14
14
|
export declare function formatDateLocale(iso: string, lang: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* **연·월 이름표** — `2026년 10월` · `October 2026`. @public
|
|
17
|
+
*
|
|
18
|
+
* 사장님 지시(2026-10-14). 전에는 사전 글귀(`calendar.yearMonth`)가 영어에서
|
|
19
|
+
* `{{month}}/{{year}}` 이라 **`10/2026`** 으로 떴다(말귀_1 이 훈련 현황 도구줄에서 봤다).
|
|
20
|
+
* 달 이름은 틀로는 못 만든다 — 열두 이름을 두 말로 적거나, 브라우저가 든 것을 쓰거나다.
|
|
21
|
+
* **브라우저 쪽을 쓴다**: 한 줄이고 두 말이 같이 맞고, 말이 늘어도 따라온다.
|
|
22
|
+
*
|
|
23
|
+
* `month` 는 **1 부터**다(화면이 쓰는 꼴). `Date` 의 0 부터와 헷갈리기 쉬워 여기서 받아
|
|
24
|
+
* 한 번만 내린다 — 부르는 쪽 셋이 저마다 `+1`·`-1` 을 하면 그중 하나가 어긋난다.
|
|
25
|
+
*
|
|
26
|
+
* **사전에 그 열쇠가 있으면 그쪽이 이긴다.** 그 글귀는 부르는 쪽이 덮으라고 열어 둔
|
|
27
|
+
* 자리다(`DatePicker` 머리글). 코어 사전에서는 뺐으므로, 안 덮으면 여기로 온다.
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatYearMonth(year: number, month: number, lang: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* **연·월 이름표 — 사전이 있으면 사전, 없으면 브라우저.** @public
|
|
32
|
+
*
|
|
33
|
+
* `calendar.yearMonth` 는 **부르는 쪽이 덮으라고 열어 둔 자리**다(`DatePicker` 머리글).
|
|
34
|
+
* 코어 사전에서는 뺐으므로 안 덮으면 `formatYearMonth` 로 온다.
|
|
35
|
+
*
|
|
36
|
+
* **이 가름을 한 자리에 둔다.** 쓰는 곳이 셋(`Calendar` 머리줄 · `DatePicker` 연월 고르개 ·
|
|
37
|
+
* 막대그래프 이름표)인데 저마다 적으면 그중 하나가 안 따라온다 — 이 저장소에서 여러 번 겪은
|
|
38
|
+
* 꼴이다. `defaultValue: ''` 로 묻는 까닭은 `exists()` 없이도 "사전에 있나" 를 가를 수
|
|
39
|
+
* 있어서다(없으면 빈 글자가 온다).
|
|
40
|
+
*/
|
|
41
|
+
export declare function yearMonthLabel(t: (key: string, opts?: Record<string, unknown>) => string, lang: string, year: number, month: number): string;
|
|
15
42
|
/** formatDateLongKo의 로케일 인식 버전 — en: "January 15, 2024, 03:30 PM" */
|
|
16
43
|
export declare function formatDateLongLocale(iso: string, lang: string): string;
|
|
17
44
|
/** "2024. 01. 15. 오후 03:30" (2-digit month + time) */
|
|
@@ -75,6 +75,43 @@ export function formatDateLocale(iso, lang) {
|
|
|
75
75
|
return '';
|
|
76
76
|
return d.toLocaleDateString(locale, { timeZone: KST, year: 'numeric', month: 'short', day: 'numeric' });
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* **연·월 이름표** — `2026년 10월` · `October 2026`. @public
|
|
80
|
+
*
|
|
81
|
+
* 사장님 지시(2026-10-14). 전에는 사전 글귀(`calendar.yearMonth`)가 영어에서
|
|
82
|
+
* `{{month}}/{{year}}` 이라 **`10/2026`** 으로 떴다(말귀_1 이 훈련 현황 도구줄에서 봤다).
|
|
83
|
+
* 달 이름은 틀로는 못 만든다 — 열두 이름을 두 말로 적거나, 브라우저가 든 것을 쓰거나다.
|
|
84
|
+
* **브라우저 쪽을 쓴다**: 한 줄이고 두 말이 같이 맞고, 말이 늘어도 따라온다.
|
|
85
|
+
*
|
|
86
|
+
* `month` 는 **1 부터**다(화면이 쓰는 꼴). `Date` 의 0 부터와 헷갈리기 쉬워 여기서 받아
|
|
87
|
+
* 한 번만 내린다 — 부르는 쪽 셋이 저마다 `+1`·`-1` 을 하면 그중 하나가 어긋난다.
|
|
88
|
+
*
|
|
89
|
+
* **사전에 그 열쇠가 있으면 그쪽이 이긴다.** 그 글귀는 부르는 쪽이 덮으라고 열어 둔
|
|
90
|
+
* 자리다(`DatePicker` 머리글). 코어 사전에서는 뺐으므로, 안 덮으면 여기로 온다.
|
|
91
|
+
*/
|
|
92
|
+
export function formatYearMonth(year, month, lang) {
|
|
93
|
+
/* 달이 범위 밖이면 `Date` 가 해를 넘겨 버린다(13월 → 다음 해 1월). 조용히 딴 해를
|
|
94
|
+
보여 주느니 받은 그대로 적는다. */
|
|
95
|
+
if (!Number.isFinite(year) || !Number.isFinite(month) || month < 1 || month > 12)
|
|
96
|
+
return `${year}-${month}`;
|
|
97
|
+
const locale = lang && lang.startsWith('ko') ? 'ko-KR' : lang || 'en-US';
|
|
98
|
+
return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long' }).format(new Date(year, month - 1, 1));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* **연·월 이름표 — 사전이 있으면 사전, 없으면 브라우저.** @public
|
|
102
|
+
*
|
|
103
|
+
* `calendar.yearMonth` 는 **부르는 쪽이 덮으라고 열어 둔 자리**다(`DatePicker` 머리글).
|
|
104
|
+
* 코어 사전에서는 뺐으므로 안 덮으면 `formatYearMonth` 로 온다.
|
|
105
|
+
*
|
|
106
|
+
* **이 가름을 한 자리에 둔다.** 쓰는 곳이 셋(`Calendar` 머리줄 · `DatePicker` 연월 고르개 ·
|
|
107
|
+
* 막대그래프 이름표)인데 저마다 적으면 그중 하나가 안 따라온다 — 이 저장소에서 여러 번 겪은
|
|
108
|
+
* 꼴이다. `defaultValue: ''` 로 묻는 까닭은 `exists()` 없이도 "사전에 있나" 를 가를 수
|
|
109
|
+
* 있어서다(없으면 빈 글자가 온다).
|
|
110
|
+
*/
|
|
111
|
+
export function yearMonthLabel(t, lang, year, month) {
|
|
112
|
+
const fromDict = t('calendar.yearMonth', { year, month, defaultValue: '' });
|
|
113
|
+
return fromDict || formatYearMonth(year, month, lang);
|
|
114
|
+
}
|
|
78
115
|
/** formatDateLongKo의 로케일 인식 버전 — en: "January 15, 2024, 03:30 PM" */
|
|
79
116
|
export function formatDateLongLocale(iso, lang) {
|
|
80
117
|
const locale = lang.startsWith('ko') ? 'ko-KR' : 'en-US';
|