@ihabdevteam/core 0.247.0 → 0.248.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 +40 -0
- package/dist/components.cjs.js +179 -179
- package/dist/hooks.cjs.js +1 -1
- package/dist/i18n.cjs.js +1 -1
- package/dist/ihabdevteam-core.cjs.js +184 -184
- package/dist/ihabdevteam-core.d.ts +23 -4
- package/dist/packages/core/src/components/BarChart.d.ts +1 -1
- package/dist/packages/core/src/components/BarChart.js +2 -2
- package/dist/packages/core/src/components/Calendar.js +10 -3
- package/dist/packages/core/src/components/PanelRow.d.ts +6 -1
- package/dist/packages/core/src/components/PanelRow.js +3 -1
- package/dist/packages/core/src/components/RingChart.d.ts +4 -2
- package/dist/packages/core/src/components/RingChart.js +2 -2
- package/dist/packages/core/src/components/TextField.d.ts +6 -0
- package/dist/packages/core/src/components/TextField.js +4 -2
- package/dist/packages/core/src/components/barChartTypes.d.ts +7 -1
- 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
|
@@ -310,7 +310,7 @@ export declare type BadgeType = 'bar' | 'dot';
|
|
|
310
310
|
* - 주(week): 'YYYY년 M월 N째주' 드롭다운 + 그 주의 요일(월~일) 막대
|
|
311
311
|
* 원시 렌더링은 내부 BarChartCanvas가 담당한다.
|
|
312
312
|
*/
|
|
313
|
-
export declare function BarChart({ focusableBars, size, barWidth, titleKey, data, colorA, colorB, labelAKey, labelBKey, showTotal, totalUnitKey, totalLabelKey, control: controlProp, hideControls, rate, rateAgg, tightMax, avgMode, totalFormat, series, seriesMode, loading, onDetail, headExtra, headSegment, icon, disc, onClick, cardAriaLabel, cardTooltip, detailSize, barGap, baseline, className, inGrid, }: BarChartProps): JSX.Element;
|
|
313
|
+
export declare function BarChart({ focusableBars, size, barWidth, titleKey, titleLevel, data, colorA, colorB, labelAKey, labelBKey, showTotal, totalUnitKey, totalLabelKey, control: controlProp, hideControls, rate, rateAgg, tightMax, avgMode, totalFormat, series, seriesMode, loading, onDetail, headExtra, headSegment, icon, disc, onClick, cardAriaLabel, cardTooltip, detailSize, barGap, baseline, className, inGrid, }: BarChartProps): JSX.Element;
|
|
314
314
|
|
|
315
315
|
/** @public */
|
|
316
316
|
export declare interface BarChartControl {
|
|
@@ -376,6 +376,12 @@ export declare interface BarChartProps {
|
|
|
376
376
|
barWidth?: number;
|
|
377
377
|
/** 섹션 제목 i18n 키 */
|
|
378
378
|
titleKey: string;
|
|
379
|
+
/** 제목을 머리글 몇 급으로 그릴지 — 그대로 `ChartSection` 에 넘긴다(말귀 요청 (60)③, 2026-10-02).
|
|
380
|
+
*
|
|
381
|
+
* `ChartSection` 은 (51) 에서 급을 열었는데 **막대·링은 그 문을 안 달았다.** 그래서 칸을
|
|
382
|
+
* 부르는 쪽이 급을 못 정하고 늘 기본 3 이 나갔다 — 대시보드 제목이 `h1` 이면 층이
|
|
383
|
+
* h1 → h3 로 한 급 비어 읽힌다. 자세한 뜻은 `ChartSectionProps.titleLevel` 참고. */
|
|
384
|
+
titleLevel?: ChartSectionProps['titleLevel'];
|
|
379
385
|
/** 구간(월) 키 → 주별/일별 데이터 */
|
|
380
386
|
data: Record<string, MonthBarData>;
|
|
381
387
|
colorA: string;
|
|
@@ -1645,7 +1651,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1645
1651
|
*
|
|
1646
1652
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1647
1653
|
*/
|
|
1648
|
-
export declare const CORE_VERSION = "0.
|
|
1654
|
+
export declare const CORE_VERSION = "0.248.0";
|
|
1649
1655
|
|
|
1650
1656
|
/** @public */
|
|
1651
1657
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -4359,7 +4365,12 @@ declare type PanelRowInput = Base & {
|
|
|
4359
4365
|
disabled?: boolean;
|
|
4360
4366
|
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
|
|
4361
4367
|
autoComplete?: string;
|
|
4362
|
-
|
|
4368
|
+
/** 칸 아래 안내 한 줄 — `aria-describedby` 로 **칸과 이어진다**(말귀 요청 (60)①, 2026-10-02).
|
|
4369
|
+
*
|
|
4370
|
+
* `ReactNode` 다 — 앱이 `<span role="status">` 로 감싸 **값에 따라 바뀌는 안내**를
|
|
4371
|
+
* 그 자리에서 알릴 수 있게 한 것이다. `error` 로 보내면 될 일이 아니다: `error` 는
|
|
4372
|
+
* `aria-invalid` 까지 세워 "틀린 값" 이라고 말하는데, 안내는 틀렸다는 말이 아니다. */
|
|
4373
|
+
helperText?: ReactNode;
|
|
4363
4374
|
/** 최대 글자 수 — 설정 시 입력 제한 + 박스 안쪽 글자수 카운터 표시 */
|
|
4364
4375
|
maxLength?: number;
|
|
4365
4376
|
/** 글자수 카운터 표시 여부 (maxLength 설정 시, 기본 true) */
|
|
@@ -6067,7 +6078,7 @@ export declare function reviewTableName(original: string | ReviewSource, today:
|
|
|
6067
6078
|
* 미지정 시에는 헤더 없는 순수 링만 렌더된다(크기/색상 데모 등).
|
|
6068
6079
|
* @public
|
|
6069
6080
|
*/
|
|
6070
|
-
export declare function RingChart({ titleKey, headRight, icon, showTotal, totalLabelKey, legend, loading, className, inGrid, onClick, ariaLabel, tooltip, ...ringProps }: RingChartProps): JSX.Element;
|
|
6081
|
+
export declare function RingChart({ titleKey, titleLevel, headRight, icon, showTotal, totalLabelKey, legend, loading, className, inGrid, onClick, ariaLabel, tooltip, ...ringProps }: RingChartProps): JSX.Element;
|
|
6071
6082
|
|
|
6072
6083
|
/** @public */
|
|
6073
6084
|
export declare interface RingChartProps {
|
|
@@ -6104,6 +6115,8 @@ export declare interface RingChartProps {
|
|
|
6104
6115
|
* 감싼 카드로 렌더. 미지정 시 헤더 없는 순수 링만 렌더(기존 동작과 동일).
|
|
6105
6116
|
*/
|
|
6106
6117
|
titleKey?: string;
|
|
6118
|
+
/** 제목 머리글 급 — 그대로 `ChartSection` 에 넘긴다(`titleKey` 지정 시에만). `BarChartProps.titleLevel` 과 같은 뜻 */
|
|
6119
|
+
titleLevel?: ChartSectionProps['titleLevel'];
|
|
6107
6120
|
/** 헤더 우측 영역 — 기간 네비게이션, 상세보기 버튼 등 (titleKey 지정 시에만 사용) */
|
|
6108
6121
|
headRight?: ReactNode;
|
|
6109
6122
|
/** 제목 옆 아이콘(옅게 표시, StatChart와 동일한 톤) — 지정 시 headRight 자리에 렌더(titleKey 지정 시에만 사용) */
|
|
@@ -7588,6 +7601,12 @@ export declare interface TextFieldProps {
|
|
|
7588
7601
|
label?: string;
|
|
7589
7602
|
/** 화면에 보이는 라벨 없이(placeholder만 쓸 때) 스크린리더용 이름을 지정. label이 있으면 무시됨 */
|
|
7590
7603
|
ariaLabel?: string;
|
|
7604
|
+
/** **바깥에서 그린 설명**의 id — 이 칸 안의 `helperText`·`error` id 와 **함께** 걸린다(덮지 않는다).
|
|
7605
|
+
*
|
|
7606
|
+
* `PanelRow` 처럼 감싸는 쪽이 도움말을 제 자리에 그리는 자리가 있다. 전에는 그 글이 칸과
|
|
7607
|
+
* 이어지지 않아, 보이기만 하고 낭독기에는 영 안 갔다(말귀 요청 (60)①, 2026-10-02).
|
|
7608
|
+
* 빈칸 띄움으로 여러 개도 된다 — `aria-describedby` 는 id 목록이다. */
|
|
7609
|
+
ariaDescribedBy?: string;
|
|
7591
7610
|
/** 필수 항목 표시 (*) */
|
|
7592
7611
|
required?: boolean;
|
|
7593
7612
|
/** placeholder 텍스트 */
|
|
@@ -13,4 +13,4 @@ import type { BarChartProps } from './barChartTypes';
|
|
|
13
13
|
* - 주(week): 'YYYY년 M월 N째주' 드롭다운 + 그 주의 요일(월~일) 막대
|
|
14
14
|
* 원시 렌더링은 내부 BarChartCanvas가 담당한다.
|
|
15
15
|
*/
|
|
16
|
-
export default function BarChart({ focusableBars, size, barWidth, titleKey, data, colorA, colorB, labelAKey, labelBKey, showTotal, totalUnitKey, totalLabelKey, control: controlProp, hideControls, rate, rateAgg, tightMax, avgMode, totalFormat, series, seriesMode, loading, onDetail, headExtra, headSegment, icon, disc, onClick, cardAriaLabel, cardTooltip, detailSize, barGap, baseline, className, inGrid, }: BarChartProps): import("react").JSX.Element;
|
|
16
|
+
export default function BarChart({ focusableBars, size, barWidth, titleKey, titleLevel, data, colorA, colorB, labelAKey, labelBKey, showTotal, totalUnitKey, totalLabelKey, control: controlProp, hideControls, rate, rateAgg, tightMax, avgMode, totalFormat, series, seriesMode, loading, onDetail, headExtra, headSegment, icon, disc, onClick, cardAriaLabel, cardTooltip, detailSize, barGap, baseline, className, inGrid, }: BarChartProps): import("react").JSX.Element;
|
|
@@ -290,7 +290,7 @@ const BAR_SIZE_PRESET = {
|
|
|
290
290
|
* - 주(week): 'YYYY년 M월 N째주' 드롭다운 + 그 주의 요일(월~일) 막대
|
|
291
291
|
* 원시 렌더링은 내부 BarChartCanvas가 담당한다.
|
|
292
292
|
*/
|
|
293
|
-
export default function BarChart({ focusableBars, size = 'medium', barWidth, titleKey, data, colorA, colorB, labelAKey, labelBKey, showTotal = false, totalUnitKey, totalLabelKey, control: controlProp, hideControls = false, rate = false, rateAgg = 'dayMean', tightMax = false, avgMode = false, totalFormat, series, seriesMode = 'stack', loading = false, onDetail, headExtra, headSegment, icon, disc, onClick, cardAriaLabel, cardTooltip, detailSize = 32, barGap, baseline, className, inGrid = false, }) {
|
|
293
|
+
export default function BarChart({ focusableBars, size = 'medium', barWidth, titleKey, titleLevel, data, colorA, colorB, labelAKey, labelBKey, showTotal = false, totalUnitKey, totalLabelKey, control: controlProp, hideControls = false, rate = false, rateAgg = 'dayMean', tightMax = false, avgMode = false, totalFormat, series, seriesMode = 'stack', loading = false, onDetail, headExtra, headSegment, icon, disc, onClick, cardAriaLabel, cardTooltip, detailSize = 32, barGap, baseline, className, inGrid = false, }) {
|
|
294
294
|
const { t, i18n } = useTranslation();
|
|
295
295
|
const sizePreset = BAR_SIZE_PRESET[size];
|
|
296
296
|
// 외부 control 미지정 시 내부 컨트롤 사용 (단독 차트 하위 호환)
|
|
@@ -428,7 +428,7 @@ export default function BarChart({ focusableBars, size = 'medium', barWidth, tit
|
|
|
428
428
|
.filter(Boolean).join(' ') || undefined;
|
|
429
429
|
return (_jsx(ChartSection, { className: sectionClassName, inGrid: inGrid, icon: icon, disc: disc, onClick: onClick, ariaLabel: cardAriaLabel, tooltip: cardTooltip, headSegment: headSegment,
|
|
430
430
|
/* 좁아 고르개가 '⋯' 로 접히면 상세보기도 그 메뉴로 들어간다 — 머리에 단추 둘이 남지 않게 */
|
|
431
|
-
headMenuItems: onDetail ? [{ label: t('trainingStatus.detail', '상세보기'), icon: 'list', onClick: onDetail, closeOnSelect: true }] : undefined, title: t(titleKey), titleClassName: isSmall ? 'h5 chart-section-title--small' : undefined, headRight: isSmall ? (headExtra ?? undefined) : ({ collapsed }) => _jsxs(_Fragment, { children: [!hideControls && _jsx(BarChartControls, { control: control }), headExtra, onDetail && !collapsed && (_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)) ? (
|
|
431
|
+
headMenuItems: onDetail ? [{ label: t('trainingStatus.detail', '상세보기'), icon: 'list', onClick: onDetail, closeOnSelect: true }] : undefined, title: t(titleKey), titleLevel: titleLevel, titleClassName: isSmall ? 'h5 chart-section-title--small' : undefined, headRight: isSmall ? (headExtra ?? undefined) : ({ collapsed }) => _jsxs(_Fragment, { children: [!hideControls && _jsx(BarChartControls, { control: control }), headExtra, onDetail && !collapsed && (_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)) ? (
|
|
432
432
|
// 진행률/평균 모드 또는 totalLabelKey+totalFormat 조합: 라벨(윗줄) + 값(아랫줄). size='small'은 라벨 생략
|
|
433
433
|
_jsx(ChartTotal, { label: isSmall ? undefined : `${t(control.period === 'week' ? 'manager.chart.weeklyPrefix' : 'manager.chart.monthlyPrefix')} ${t(totalLabelKey ?? labelAKey)}`, children: totalFormat ? (
|
|
434
434
|
// 숫자는 h0, 단위(시간/분/초 등)는 h5로 분리
|
|
@@ -4,14 +4,14 @@ import { useCallback, useMemo, useState, useRef } from 'react';
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import Icon from './Icon';
|
|
6
6
|
import { formatTimeHM } from '../utils/format';
|
|
7
|
-
import { todayYmdOr } from '../utils/date';
|
|
7
|
+
import { todayYmdOr, formatDateLocale } from '../utils/date';
|
|
8
8
|
import './Calendar.css';
|
|
9
9
|
import { pressable } from '../utils/pressable';
|
|
10
10
|
function daysInMonth(year, month) { return new Date(year, month + 1, 0).getDate(); }
|
|
11
11
|
function firstDayOfMonth(year, month) { return new Date(year, month, 1).getDay(); }
|
|
12
12
|
/** @public */
|
|
13
13
|
export default function Calendar({ visits = [], onVisitClick, onVisitMove, onDayClick, today, }) {
|
|
14
|
-
const { t } = useTranslation();
|
|
14
|
+
const { t, i18n } = useTranslation();
|
|
15
15
|
const DOW = useMemo(() => t('calendar.dow', { returnObjects: true }), [t]);
|
|
16
16
|
/* '오늘' 은 KST 의 날 — 방문 날짜가 KST 달력(toKstYmd)이라, 기기 달력으로 열면 해외 기기에서 오늘 방문이 안 보이는 달에 있고
|
|
17
17
|
오늘 표시가 다른 날에 붙었다(해외 기기 공식 지원) */
|
|
@@ -61,6 +61,8 @@ export default function Calendar({ visits = [], onVisitClick, onVisitMove, onDay
|
|
|
61
61
|
const day = d;
|
|
62
62
|
const dateKey = fmtDate(day);
|
|
63
63
|
const dayVisits = visitsByDate[dateKey] ?? [];
|
|
64
|
+
/* 낭독기에 나가는 '며칠' — 보는 locale 을 따라 글자꼴만 바뀌고 가리키는 날은 KST 하나다(utils/date 머리글) */
|
|
65
|
+
const 날짜말 = formatDateLocale(dateKey, i18n.language);
|
|
64
66
|
const isDragOver = dragOverDate === dateKey;
|
|
65
67
|
return (_jsxs("div", { className: `calendar-cell${isToday(day) ? ' today' : ''}${isDragOver ? ' drag-over' : ''}${onDayClick ? ' calendar-cell--clickable' : ''}`, role: onDayClick ? 'button' : undefined, tabIndex: onDayClick ? 0 : undefined, "aria-label": onDayClick ? t('calendar.addOnDate', { date: dateKey }) : undefined, "aria-current": isToday(day) ? 'date' : undefined, onClick: onDayClick ? () => onDayClick(dateKey) : undefined,
|
|
66
68
|
/* `<div role="button">` 은 브라우저가 Enter·Space 를 눌림으로 바꿔 주지 않는다 */
|
|
@@ -83,13 +85,18 @@ export default function Calendar({ visits = [], onVisitClick, onVisitMove, onDay
|
|
|
83
85
|
dragVisitId.current = null;
|
|
84
86
|
}
|
|
85
87
|
setDragOverDate(null);
|
|
86
|
-
} : undefined, children: [_jsx("div", { className: "calendar-cell__number body-3", children: day }), dayVisits.length > 0 && (_jsxs("span", { className: "calendar-cell__visit-count caption-1", "aria-label": t('calendar.visitCount', { count: dayVisits.length, defaultValue: '방문 {{count}}건' })
|
|
88
|
+
} : undefined, children: [_jsx("div", { className: "calendar-cell__number body-3", children: day }), dayVisits.length > 0 && (_jsxs("span", { className: "calendar-cell__visit-count caption-1", "aria-label": `${날짜말} ${t('calendar.visitCount', { count: dayVisits.length, defaultValue: '방문 {{count}}건' })}`, children: [_jsx("span", { className: "calendar-cell__visit-dot", "aria-hidden": "true" }), dayVisits.length] })), _jsx("div", { className: "calendar-cell__badges", children: dayVisits.map((v, vi) => (_jsxs("div", { draggable: !!onVisitMove, className: `calendar-visit-badge${v.status === 'noVisit' ? ' calendar-visit-badge--no-visit' : v.status === 'scheduled' ? ' calendar-visit-badge--scheduled' : ''}${onVisitClick ? ' calendar-visit-badge--clickable' : ''}${onVisitMove ? ' calendar-visit-badge--draggable' : ''}`, onDragStart: onVisitMove ? (e) => {
|
|
87
89
|
dragVisitId.current = v.id;
|
|
88
90
|
e.dataTransfer.effectAllowed = 'move';
|
|
89
91
|
} : undefined, onDragEnd: onVisitMove ? () => {
|
|
90
92
|
dragVisitId.current = null;
|
|
91
93
|
setDragOverDate(null);
|
|
92
94
|
} : undefined, onClick: onVisitClick ? (e) => { e.stopPropagation(); onVisitClick(v); } : undefined, ...pressable(onVisitClick ? () => onVisitClick(v) : undefined), "aria-label": [
|
|
95
|
+
// **며칠**이 빠져 있었다 — 칸 번호(1~31)는 `aria-hidden` 도 아니지만 이름표의
|
|
96
|
+
// 이름을 대체하므로 낭독기에는 "12시 55분 지한 예약" 만 가고, 어느 날 것인지는
|
|
97
|
+
// 안 간다. 격자 꼴(`gridcell`)로 날을 함께 읽히는 길도 있지만 그건 화살표
|
|
98
|
+
// 이동까지 약속하는 것이라, 지금은 이름 안에 날을 적는다(말귀 요청 (60)②).
|
|
99
|
+
날짜말,
|
|
93
100
|
v.time ? formatTimeHM(v.time, t) : null,
|
|
94
101
|
v.customer,
|
|
95
102
|
t(v.status === 'noVisit' ? 'calendar.legend.noVisit'
|
|
@@ -99,7 +99,12 @@ export type PanelRowInput = Base & {
|
|
|
99
99
|
disabled?: boolean;
|
|
100
100
|
type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url';
|
|
101
101
|
autoComplete?: string;
|
|
102
|
-
|
|
102
|
+
/** 칸 아래 안내 한 줄 — `aria-describedby` 로 **칸과 이어진다**(말귀 요청 (60)①, 2026-10-02).
|
|
103
|
+
*
|
|
104
|
+
* `ReactNode` 다 — 앱이 `<span role="status">` 로 감싸 **값에 따라 바뀌는 안내**를
|
|
105
|
+
* 그 자리에서 알릴 수 있게 한 것이다. `error` 로 보내면 될 일이 아니다: `error` 는
|
|
106
|
+
* `aria-invalid` 까지 세워 "틀린 값" 이라고 말하는데, 안내는 틀렸다는 말이 아니다. */
|
|
107
|
+
helperText?: ReactNode;
|
|
103
108
|
/** 최대 글자 수 — 설정 시 입력 제한 + 박스 안쪽 글자수 카운터 표시 */
|
|
104
109
|
maxLength?: number;
|
|
105
110
|
/** 글자수 카운터 표시 여부 (maxLength 설정 시, 기본 true) */
|
|
@@ -74,6 +74,8 @@ export function PanelRow(props) {
|
|
|
74
74
|
const toggleId = variant === 'toggle' ? `panel-toggle-${uid}` : undefined;
|
|
75
75
|
// 오류 문구 id — 드롭다운 행은 트리거가 이 문구를 설명으로 잇는다(aria-invalid 만으로는 무엇이 틀렸는지 안 들린다)
|
|
76
76
|
const errorId = error ? `panel-error-${uid}` : undefined;
|
|
77
|
+
/* 도움말은 variant 와 무관하게 한 자리에서 짓는다 — id 는 그릴 때와 걸 때 **같아야** 한다 */
|
|
78
|
+
const helperId = `panel-helper-${uid}`;
|
|
77
79
|
if (variant === 'toggle') {
|
|
78
80
|
const p = props;
|
|
79
81
|
right = _jsx(Toggle, { checked: p.checked, onChange: p.onChange, id: toggleId });
|
|
@@ -111,7 +113,7 @@ export function PanelRow(props) {
|
|
|
111
113
|
}
|
|
112
114
|
else if (variant === 'input') {
|
|
113
115
|
const p = props;
|
|
114
|
-
right = (_jsxs("div", { className: `panel-row__input-wrap${useFullWidth ? ' panel-row__input-wrap--full' : ''}`, children: [_jsx(TextField, { value: p.value, onChange: p.onChange ?? noop, onKeyDown: p.onKeyDown, onEnter: p.onEnter, placeholder: p.placeholder, ghostText: p.ghostText, ghostHint: p.ghostHint, inputMode: p.inputMode, readOnly: p.readOnly, disabled: p.disabled, type: p.type, autoComplete: p.autoComplete, maxLength: p.maxLength, showCount: p.showCount, multiline: p.multiline, rows: p.rows, fullWidth: useFullWidth, error: error, ariaLabel: label, required: required }), p.helperText && _jsx("div", { className: "panel-row__helper caption-1", children: p.helperText })] }));
|
|
116
|
+
right = (_jsxs("div", { className: `panel-row__input-wrap${useFullWidth ? ' panel-row__input-wrap--full' : ''}`, children: [_jsx(TextField, { value: p.value, onChange: p.onChange ?? noop, onKeyDown: p.onKeyDown, onEnter: p.onEnter, placeholder: p.placeholder, ghostText: p.ghostText, ghostHint: p.ghostHint, inputMode: p.inputMode, readOnly: p.readOnly, disabled: p.disabled, type: p.type, autoComplete: p.autoComplete, maxLength: p.maxLength, showCount: p.showCount, multiline: p.multiline, rows: p.rows, fullWidth: useFullWidth, error: error, ariaLabel: label, required: required, ariaDescribedBy: p.helperText ? helperId : undefined }), p.helperText && _jsx("div", { id: helperId, className: "panel-row__helper caption-1", children: p.helperText })] }));
|
|
115
117
|
}
|
|
116
118
|
else if (variant === 'custom') {
|
|
117
119
|
const p = props;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './RingChart.css';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import type { ChartLegendItem } from './ChartSection';
|
|
3
|
+
import type { ChartLegendItem, ChartSectionProps } from './ChartSection';
|
|
4
4
|
/** 시맨틱 사이즈 → 지름(px)·링 두께(px) 프리셋 */
|
|
5
5
|
export type RingChartSize = 'small' | 'medium' | 'large';
|
|
6
6
|
/** @public */
|
|
@@ -44,6 +44,8 @@ export interface RingChartProps {
|
|
|
44
44
|
* 감싼 카드로 렌더. 미지정 시 헤더 없는 순수 링만 렌더(기존 동작과 동일).
|
|
45
45
|
*/
|
|
46
46
|
titleKey?: string;
|
|
47
|
+
/** 제목 머리글 급 — 그대로 `ChartSection` 에 넘긴다(`titleKey` 지정 시에만). `BarChartProps.titleLevel` 과 같은 뜻 */
|
|
48
|
+
titleLevel?: ChartSectionProps['titleLevel'];
|
|
47
49
|
/** 헤더 우측 영역 — 기간 네비게이션, 상세보기 버튼 등 (titleKey 지정 시에만 사용) */
|
|
48
50
|
headRight?: ReactNode;
|
|
49
51
|
/** 제목 옆 아이콘(옅게 표시, StatChart와 동일한 톤) — 지정 시 headRight 자리에 렌더(titleKey 지정 시에만 사용) */
|
|
@@ -73,4 +75,4 @@ export interface RingChartProps {
|
|
|
73
75
|
* 미지정 시에는 헤더 없는 순수 링만 렌더된다(크기/색상 데모 등).
|
|
74
76
|
* @public
|
|
75
77
|
*/
|
|
76
|
-
export default function RingChart({ titleKey, headRight, icon, showTotal, totalLabelKey, legend, loading, className, inGrid, onClick, ariaLabel, tooltip, ...ringProps }: RingChartProps): import("react").JSX.Element;
|
|
78
|
+
export default function RingChart({ titleKey, titleLevel, headRight, icon, showTotal, totalLabelKey, legend, loading, className, inGrid, onClick, ariaLabel, tooltip, ...ringProps }: RingChartProps): import("react").JSX.Element;
|
|
@@ -51,7 +51,7 @@ function RingChartCanvas({ done, total, segments, color = 'var(--color-main)', s
|
|
|
51
51
|
* 미지정 시에는 헤더 없는 순수 링만 렌더된다(크기/색상 데모 등).
|
|
52
52
|
* @public
|
|
53
53
|
*/
|
|
54
|
-
export default function RingChart({ titleKey, headRight, icon, showTotal, totalLabelKey, legend, loading, className, inGrid = false, onClick, ariaLabel, tooltip, ...ringProps }) {
|
|
54
|
+
export default function RingChart({ titleKey, titleLevel, headRight, icon, showTotal, totalLabelKey, legend, loading, className, inGrid = false, onClick, ariaLabel, tooltip, ...ringProps }) {
|
|
55
55
|
const { t } = useTranslation();
|
|
56
56
|
if (!titleKey)
|
|
57
57
|
return _jsx(RingChartCanvas, { ...ringProps });
|
|
@@ -66,7 +66,7 @@ export default function RingChart({ titleKey, headRight, icon, showTotal, totalL
|
|
|
66
66
|
.filter(Boolean).join(' ') || undefined;
|
|
67
67
|
// StatChart(.manager-stat-card__icon)와 동일한 톤 — headRight 자리에 렌더(둘 중 하나만 사용)
|
|
68
68
|
const iconNode = icon && (_jsx("span", { className: "chart-section-icon", children: _jsx(Icon, { icon: icon, className: "chart-section-icon-inner" }) }));
|
|
69
|
-
return (_jsx(ChartSection, { className: sectionClassName, inGrid: inGrid, onClick: onClick, ariaLabel: ariaLabel, tooltip: tooltip, title: t(titleKey), titleClassName: isSmall ? 'h5 chart-section-title--small' : undefined, headRight: iconNode || (isSmall ? undefined : headRight), subRow: (showTotal || legend) && _jsxs(_Fragment, { children: [showTotal ? (_jsxs(ChartTotal, { label: !isSmall && totalLabelKey ? t(totalLabelKey) : undefined, children: [_jsx("span", { className: "barchart-total__num h0", children: pct }), _jsx("span", { className: "barchart-total__unit h5", children: "%" })] })) : _jsx("span", {}), legend && _jsx(ChartLegend, { items: legend })] }), children: loading ? (
|
|
69
|
+
return (_jsx(ChartSection, { className: sectionClassName, inGrid: inGrid, onClick: onClick, ariaLabel: ariaLabel, tooltip: tooltip, title: t(titleKey), titleLevel: titleLevel, titleClassName: isSmall ? 'h5 chart-section-title--small' : undefined, headRight: iconNode || (isSmall ? undefined : headRight), subRow: (showTotal || legend) && _jsxs(_Fragment, { children: [showTotal ? (_jsxs(ChartTotal, { label: !isSmall && totalLabelKey ? t(totalLabelKey) : undefined, children: [_jsx("span", { className: "barchart-total__num h0", children: pct }), _jsx("span", { className: "barchart-total__unit h5", children: "%" })] })) : _jsx("span", {}), legend && _jsx(ChartLegend, { items: legend })] }), children: loading ? (
|
|
70
70
|
/* 링도 같은 병이 있었다 — 기다림 상자 186px, 그려진 링은 지름(medium 120px)이다.
|
|
71
71
|
**그려질 링과 같은 자리·같은 지름**의 뼈대를 세우고 그 안에서 기다린다
|
|
72
72
|
(BarChart 와 같은 결, 사용자 지시 2026-09-17 "뼈대 먼저, 그 안에서 로딩"). */
|
|
@@ -17,6 +17,12 @@ export interface TextFieldProps {
|
|
|
17
17
|
label?: string;
|
|
18
18
|
/** 화면에 보이는 라벨 없이(placeholder만 쓸 때) 스크린리더용 이름을 지정. label이 있으면 무시됨 */
|
|
19
19
|
ariaLabel?: string;
|
|
20
|
+
/** **바깥에서 그린 설명**의 id — 이 칸 안의 `helperText`·`error` id 와 **함께** 걸린다(덮지 않는다).
|
|
21
|
+
*
|
|
22
|
+
* `PanelRow` 처럼 감싸는 쪽이 도움말을 제 자리에 그리는 자리가 있다. 전에는 그 글이 칸과
|
|
23
|
+
* 이어지지 않아, 보이기만 하고 낭독기에는 영 안 갔다(말귀 요청 (60)①, 2026-10-02).
|
|
24
|
+
* 빈칸 띄움으로 여러 개도 된다 — `aria-describedby` 는 id 목록이다. */
|
|
25
|
+
ariaDescribedBy?: string;
|
|
20
26
|
/** 필수 항목 표시 (*) */
|
|
21
27
|
required?: boolean;
|
|
22
28
|
/** placeholder 텍스트 */
|
|
@@ -13,7 +13,7 @@ import { useIsoLayoutEffect } from '../hooks/useIsoLayoutEffect';
|
|
|
13
13
|
import './TextField.css';
|
|
14
14
|
/** @public */
|
|
15
15
|
export default function TextField(props) {
|
|
16
|
-
const { value, defaultValue, name, inputRef, onChange, label, ariaLabel, required, placeholder, ghostText, ghostHint, type = 'text', disabled = false, readOnly = false, error, helperText, maxLength, size = 48, variant = 'input', fullWidth = false, newline = false, multiline = false, rows = 3, leftIcon, rightIcon, clearable = false, list, showCount = true, className = '', onFocus, onBlur, onKeyDown, onEnter, inputMode, autoComplete, autoFocus, suggestions: suggestionsProp, filterSuggestions, onSelectSuggestion, autoActiveFirst, openOnFocus = false, } = props;
|
|
16
|
+
const { value, defaultValue, name, inputRef, onChange, label, ariaLabel, ariaDescribedBy, required, placeholder, ghostText, ghostHint, type = 'text', disabled = false, readOnly = false, error, helperText, maxLength, size = 48, variant = 'input', fullWidth = false, newline = false, multiline = false, rows = 3, leftIcon, rightIcon, clearable = false, list, showCount = true, className = '', onFocus, onBlur, onKeyDown, onEnter, inputMode, autoComplete, autoFocus, suggestions: suggestionsProp, filterSuggestions, onSelectSuggestion, autoActiveFirst, openOnFocus = false, } = props;
|
|
17
17
|
// 읽기 전용·꺼진 칸은 후보로 값을 바꾸지 못한다 — 전에는 ↓·Enter·후보 누름으로 onChange 가 나갔다
|
|
18
18
|
const suggestions = readOnly || disabled ? undefined : suggestionsProp;
|
|
19
19
|
const { t } = useTranslation();
|
|
@@ -164,7 +164,9 @@ export default function TextField(props) {
|
|
|
164
164
|
'aria-label': !label && ariaLabel ? ariaLabel : undefined,
|
|
165
165
|
'aria-required': required,
|
|
166
166
|
'aria-invalid': !!error,
|
|
167
|
-
|
|
167
|
+
/* 안의 것(helperText·error)과 바깥의 것(ariaDescribedBy)을 **잇는다** — 둘 중 하나로
|
|
168
|
+
덮으면 오류가 뜬 순간 바깥 도움말이 사라지거나, 반대로 오류가 안 읽힌다. */
|
|
169
|
+
'aria-describedby': [(helperText || error) ? helperId : null, ariaDescribedBy].filter(Boolean).join(' ') || undefined,
|
|
168
170
|
// aria-expanded·aria-controls·aria-activedescendant 는 콤보박스 속성이라 role 없이는
|
|
169
171
|
// 스크린리더가 그냥 입력칸으로 읽고 "후보 n개"를 알리지 않는다 — 역할을 함께 준다.
|
|
170
172
|
role: suggestions ? 'combobox' : undefined,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { ChartHeadSegment } from './ChartSection';
|
|
2
|
+
import type { ChartHeadSegment, ChartSectionProps } from './ChartSection';
|
|
3
3
|
import type { MonthBarData } from './barChartData';
|
|
4
4
|
/** @public */
|
|
5
5
|
export interface BarChartControl {
|
|
@@ -61,6 +61,12 @@ export interface BarChartProps {
|
|
|
61
61
|
barWidth?: number;
|
|
62
62
|
/** 섹션 제목 i18n 키 */
|
|
63
63
|
titleKey: string;
|
|
64
|
+
/** 제목을 머리글 몇 급으로 그릴지 — 그대로 `ChartSection` 에 넘긴다(말귀 요청 (60)③, 2026-10-02).
|
|
65
|
+
*
|
|
66
|
+
* `ChartSection` 은 (51) 에서 급을 열었는데 **막대·링은 그 문을 안 달았다.** 그래서 칸을
|
|
67
|
+
* 부르는 쪽이 급을 못 정하고 늘 기본 3 이 나갔다 — 대시보드 제목이 `h1` 이면 층이
|
|
68
|
+
* h1 → h3 로 한 급 비어 읽힌다. 자세한 뜻은 `ChartSectionProps.titleLevel` 참고. */
|
|
69
|
+
titleLevel?: ChartSectionProps['titleLevel'];
|
|
64
70
|
/** 구간(월) 키 → 주별/일별 데이터 */
|
|
65
71
|
data: Record<string, MonthBarData>;
|
|
66
72
|
colorA: string;
|