@ihabdevteam/core 0.187.0 → 0.189.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 +59 -0
- package/dist/components.cjs.js +181 -181
- 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 +186 -186
- package/dist/ihabdevteam-core.d.ts +153 -15
- package/dist/packages/core/src/components/ChartSection.css +18 -3
- package/dist/packages/core/src/components/ChartSection.d.ts +12 -1
- package/dist/packages/core/src/components/ChartSection.js +3 -2
- package/dist/packages/core/src/components/CountTile.d.ts +16 -11
- package/dist/packages/core/src/components/CountTile.js +53 -23
- package/dist/packages/core/src/components/LineChart.css +52 -0
- package/dist/packages/core/src/components/LineChart.d.ts +77 -0
- package/dist/packages/core/src/components/LineChart.js +59 -0
- package/dist/packages/core/src/components/SectionHeadFloat.css +25 -0
- package/dist/packages/core/src/components/SectionHeadFloat.d.ts +28 -0
- package/dist/packages/core/src/components/SectionHeadFloat.js +54 -0
- package/dist/packages/core/src/components/WordTable.css +10 -0
- package/dist/packages/core/src/components/WordTable.d.ts +17 -2
- package/dist/packages/core/src/components/WordTable.js +13 -2
- package/dist/packages/core/src/components.d.ts +4 -0
- package/dist/packages/core/src/components.js +2 -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
|
@@ -1088,7 +1088,7 @@ export declare interface ChartPoint {
|
|
|
1088
1088
|
* 공유하도록 두 컴포넌트가 내부적으로 사용한다.
|
|
1089
1089
|
* @public
|
|
1090
1090
|
*/
|
|
1091
|
-
export declare function ChartSection({ title, titleClassName, icon, disc, headRight, headSegment, headMenuItems, collapseBelow, onClick, ariaLabel, tooltip, totalNum, totalUnit, totalLabel, subRow, subRowAlign, className, inGrid, children }: ChartSectionProps): JSX.Element;
|
|
1091
|
+
export declare function ChartSection({ title, titleClassName, icon, disc, headRight, headFloat, headSegment, headMenuItems, collapseBelow, onClick, ariaLabel, tooltip, totalNum, totalUnit, totalLabel, subRow, subRowAlign, className, inGrid, children }: ChartSectionProps): JSX.Element;
|
|
1092
1092
|
|
|
1093
1093
|
/** @public */
|
|
1094
1094
|
export declare interface ChartSectionProps {
|
|
@@ -1105,6 +1105,17 @@ export declare interface ChartSectionProps {
|
|
|
1105
1105
|
headRight?: ReactNode | ((state: {
|
|
1106
1106
|
collapsed: boolean;
|
|
1107
1107
|
}) => ReactNode);
|
|
1108
|
+
/**
|
|
1109
|
+
* 머리 오른쪽에 **띄우는** 단추 줄 — `headRight` 와 달리 흐름에서 빠진다.
|
|
1110
|
+
*
|
|
1111
|
+
* 40px 짜리 단추를 제목 줄에 함께 세우면 제목 줄이 그만큼 커져 카드마다 머리 높이가 달라진다.
|
|
1112
|
+
* 띄우면 제목은 제 높이로 서고, 제목·곁줄은 이 줄의 **잰 폭만큼** 자리를 비운다. 좁은 화면
|
|
1113
|
+
* (600px 이하)에서는 흐름으로 되돌아가 제목 아래에 선다. 자세한 것은 `SectionHeadFloat`.
|
|
1114
|
+
*
|
|
1115
|
+
* 고르개·상세보기처럼 **제목과 한 줄로 서야 하는 것**은 `headRight` 가 맞다. 이쪽은 '숙제 보내기'
|
|
1116
|
+
* 처럼 카드 전체에 대고 누르는 단추 자리다.
|
|
1117
|
+
*/
|
|
1118
|
+
headFloat?: ReactNode;
|
|
1108
1119
|
/** 머리의 보기 고르개. 카드가 좁으면 '⋯' 한 단추로 접히고, 그 메뉴에 칸들이 들어간다. */
|
|
1109
1120
|
headSegment?: ChartHeadSegment;
|
|
1110
1121
|
/** 접힌 '⋯' 메뉴에서 칸들 **뒤** 에 구분선과 함께 붙는 항목(상세보기 등) */
|
|
@@ -1606,7 +1617,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1606
1617
|
*
|
|
1607
1618
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1608
1619
|
*/
|
|
1609
|
-
export declare const CORE_VERSION = "0.
|
|
1620
|
+
export declare const CORE_VERSION = "0.189.0";
|
|
1610
1621
|
|
|
1611
1622
|
/** @public */
|
|
1612
1623
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -1690,11 +1701,11 @@ export declare type CoreScopeStorageConfig = {
|
|
|
1690
1701
|
* 72×72 정사각 타일. 좌상단에 아이콘, 우하단에 숫자를 배치한다.
|
|
1691
1702
|
* 훈련 카드의 타입별 문항 수 표시 등에 사용.
|
|
1692
1703
|
*
|
|
1693
|
-
* 마우스를
|
|
1694
|
-
* (지나가다 스치는 것에 번쩍이지 않게).
|
|
1704
|
+
* `plan` 을 주면 마우스를 얹었을 때 **담긴 훈련**(`TrainingTooltip`)이 뜬다 — 늦춤은 다른
|
|
1705
|
+
* 자리와 같은 상수를 쓴다(지나가다 스치는 것에 번쩍이지 않게).
|
|
1695
1706
|
* @public
|
|
1696
1707
|
*/
|
|
1697
|
-
export declare function CountTile({ icon, count, title, tooltip, className, onClick, onMouseEnter, onMouseLeave }: CountTileProps): JSX.Element;
|
|
1708
|
+
export declare function CountTile({ icon, count, title, plan, tooltip, className, onClick, onMouseEnter, onMouseLeave }: CountTileProps): JSX.Element;
|
|
1698
1709
|
|
|
1699
1710
|
/** @public */
|
|
1700
1711
|
export declare interface CountTileProps {
|
|
@@ -1703,21 +1714,25 @@ export declare interface CountTileProps {
|
|
|
1703
1714
|
/** 우측 하단 숫자 */
|
|
1704
1715
|
count: number | string;
|
|
1705
1716
|
/**
|
|
1706
|
-
* 갈래 이름 — 낭독기가 읽는
|
|
1717
|
+
* 갈래 이름 — 낭독기가 읽는 이름이다(`"이름 수"` 로 읽힌다).
|
|
1707
1718
|
*
|
|
1708
1719
|
* **필수다.** 72×72 안에는 아이콘과 수뿐이라, 이 값이 없으면 그 아이콘이 무슨 갈래인지
|
|
1709
|
-
* 알 길이
|
|
1710
|
-
* 예전에는 선택값이었고 `aria-label` 로만 나가, 마우스 쓰는 사람은 **아무것도 못 봤다**
|
|
1711
|
-
* (사용자 지시 2026-09-23: "호버시 tooltip 은 무조건 나와야 해").
|
|
1720
|
+
* 낭독기로는 알 길이 없다(말귀 '나의 단어훈련' 카드에 타일이 열 개 선다).
|
|
1712
1721
|
*/
|
|
1713
1722
|
title: string;
|
|
1714
1723
|
/**
|
|
1715
|
-
*
|
|
1724
|
+
* 호버하면 보여 줄 **담긴 훈련**.
|
|
1725
|
+
*
|
|
1726
|
+
* 주면 마우스를 얹었을 때 `TrainingTooltip` 이 뜬다 — 그 안에 훈련 블록이 서고, 블록의
|
|
1727
|
+
* 갈래 아이콘(`CategoryIcon`)에 다시 마우스를 얹으면 제 이름이 작은 툴팁으로 뜬다
|
|
1728
|
+
* (사용자 지시 2026-09-23). 안 주면 아무것도 안 뜬다 — 타일 이름만 띄우는 글자 툴팁은
|
|
1729
|
+
* **두지 않는다**(0.187.0 에 잠깐 있었고, 그건 요청을 잘못 읽은 것이었다).
|
|
1716
1730
|
*
|
|
1717
|
-
*
|
|
1718
|
-
*
|
|
1719
|
-
* 여기 기본 툴팁까지 뜨면 둘이 겹친다. 그 자리만 끈다.
|
|
1731
|
+
* 이 타일이 세는 그 갈래의 항목만 담은 `plan` 을 준다 — `TrainingTooltip` 은 받은 `plan.items`
|
|
1732
|
+
* 를 갈래별로 묶어 그리므로, 카드 전체의 `plan` 을 그대로 주면 타일마다 같은 것이 뜬다.
|
|
1720
1733
|
*/
|
|
1734
|
+
plan?: TrainingPlan;
|
|
1735
|
+
/** 호버 툴팁을 끈다(기본 켬) — 부르는 쪽이 제 툴팁을 띄우는 자리용 */
|
|
1721
1736
|
tooltip?: boolean;
|
|
1722
1737
|
className?: string;
|
|
1723
1738
|
/** 클릭 핸들러 — 설정 시 버튼처럼 동작 */
|
|
@@ -3161,6 +3176,86 @@ export declare const LEVEL_MAX = 9;
|
|
|
3161
3176
|
/** @public */
|
|
3162
3177
|
export declare const levelToPct: (level: number) => number;
|
|
3163
3178
|
|
|
3179
|
+
/** 점 위에 잠깐 머물면 뜨는 뜸 — 지나가는 마우스에 툴팁이 번쩍이지 않을 만큼만 짧게 @public */
|
|
3180
|
+
export declare const LINE_CHART_HOVER_DELAY_MS = 120;
|
|
3181
|
+
|
|
3182
|
+
/**
|
|
3183
|
+
* **작은 선 그래프** — 시간·조건에 따라 값이 어떻게 흐르는지를 그린다.
|
|
3184
|
+
* 세로는 0~100%로 고정이고, 가로 범위는 부르는 쪽이 준다.
|
|
3185
|
+
*
|
|
3186
|
+
* **제 상자를 재어 배율 1 로 그린다.** `viewBox` 를 상자보다 좁게 두면 SVG 가 통째로 늘어나
|
|
3187
|
+
* **글자까지 커진다**(말귀 실측 1.22배 — 사용자 지적 2026-09-17 "이 그래프가 폰트가 크게 보이는게
|
|
3188
|
+
* 스케일 되서 그런건가?"). 잰 픽셀을 그대로 쓰면 CSS 에 적은 12px 이 화면에서도 12px 이다.
|
|
3189
|
+
* 못 재는 자리(첫 렌더·ResizeObserver 없는 틀)에서는 `fallbackWidth` 로 그린다 — 늘어날 뿐 안 깨진다.
|
|
3190
|
+
*
|
|
3191
|
+
* **면 칠하기·짚는 자리처럼 그 화면에만 있는 말은 `overlay` 로 덧그린다** — 자(`x`·`y`)를 받으므로
|
|
3192
|
+
* 좌표 셈을 다시 하지 않는다.
|
|
3193
|
+
* @public
|
|
3194
|
+
*/
|
|
3195
|
+
export declare function LineChart({ points, range, ticks, axisTitle, color, height, fallbackWidth, insets, overlay, ariaLabel, className, }: LineChartProps): JSX.Element;
|
|
3196
|
+
|
|
3197
|
+
/** @public */
|
|
3198
|
+
export declare interface LineChartPoint {
|
|
3199
|
+
/** 가로 자리 — `range` 안의 값 */
|
|
3200
|
+
x: number;
|
|
3201
|
+
/** 세로 자리 — 0~100(%) */
|
|
3202
|
+
pct: number;
|
|
3203
|
+
/** 속을 비워 그린다(믿기 이른 값 등). 비운 점은 **선에서도 뺀다** */
|
|
3204
|
+
hollow?: boolean;
|
|
3205
|
+
/** 마우스를 얹으면 뜰 글. 없으면 그 점에는 안 뜬다 */
|
|
3206
|
+
description?: string;
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
/** @public */
|
|
3210
|
+
export declare interface LineChartProps {
|
|
3211
|
+
/** 그릴 점들 — 비면 축과 눈금만 선다 */
|
|
3212
|
+
points?: LineChartPoint[];
|
|
3213
|
+
/** 가로 범위 `[처음, 끝]` — 어떤 축인지(조건·주차·문항 수…)는 부르는 쪽이 정한다 */
|
|
3214
|
+
range: [number, number];
|
|
3215
|
+
/** 가로 눈금 — 안 주면 가로 글자가 없다 */
|
|
3216
|
+
ticks?: {
|
|
3217
|
+
x: number;
|
|
3218
|
+
label: string;
|
|
3219
|
+
}[];
|
|
3220
|
+
/** 세로축 이름. **재는 것이 정답률이 아니면 반드시 준다** — 이름이 없으면 보는 사람이 정답률로 읽는다 */
|
|
3221
|
+
axisTitle?: ReactNode;
|
|
3222
|
+
/** 선·점 색. 안 주면 주 색 */
|
|
3223
|
+
color?: string;
|
|
3224
|
+
/** 그림 높이(px) — 배율이 1 이라 이 수가 곧 화면 높이다 */
|
|
3225
|
+
height?: number;
|
|
3226
|
+
/** 제 상자를 **못 잴 때만** 쓰는 대비 폭(첫 렌더·시험 틀) */
|
|
3227
|
+
fallbackWidth?: number;
|
|
3228
|
+
/** 축 안쪽 여백 — 왼쪽은 "100%" 가 들어갈 만큼이 기본이다 */
|
|
3229
|
+
insets?: Partial<{
|
|
3230
|
+
left: number;
|
|
3231
|
+
right: number;
|
|
3232
|
+
top: number;
|
|
3233
|
+
bottom: number;
|
|
3234
|
+
}>;
|
|
3235
|
+
/** 그림 위에 덧그릴 것 — 자를 받아 SVG 조각을 돌려준다(면 칠하기·짚는 자리 같은 **그 화면의 말**) */
|
|
3236
|
+
overlay?: (scales: LineChartScales) => ReactNode;
|
|
3237
|
+
/** 낭독기가 읽을 글 — 안 주면 점들의 `description` 을 한 줄로 잇는다 */
|
|
3238
|
+
ariaLabel?: string;
|
|
3239
|
+
className?: string;
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
/** 그림 칸 안에서 값을 픽셀 자리로 옮기는 자 — `overlay` 가 이것으로 제 것을 덧그린다 @public */
|
|
3243
|
+
export declare interface LineChartScales {
|
|
3244
|
+
/** 가로 값 → x 픽셀 */
|
|
3245
|
+
x: (v: number) => number;
|
|
3246
|
+
/** 0~100 → y 픽셀 */
|
|
3247
|
+
y: (pct: number) => number;
|
|
3248
|
+
/** 그림이 그려지는 네모(축 안쪽) */
|
|
3249
|
+
plot: {
|
|
3250
|
+
left: number;
|
|
3251
|
+
right: number;
|
|
3252
|
+
top: number;
|
|
3253
|
+
bottom: number;
|
|
3254
|
+
width: number;
|
|
3255
|
+
height: number;
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3164
3259
|
/** @public */
|
|
3165
3260
|
export declare function ListenStep({ _stepIndex, _totalSteps, ...rest }: StepWrapperProps): JSX.Element;
|
|
3166
3261
|
|
|
@@ -5857,6 +5952,34 @@ export declare interface SectionHeaderProps {
|
|
|
5857
5952
|
/** @public */
|
|
5858
5953
|
export declare type SectionHeaderTone = 'dark' | 'light';
|
|
5859
5954
|
|
|
5955
|
+
/**
|
|
5956
|
+
* **카드 머리 오른쪽에 띄우는 단추 줄.**
|
|
5957
|
+
*
|
|
5958
|
+
* 흐름에서 빼는 까닭은 자리가 아니라 **높이**다 — 40px 짜리 단추를 제목 줄에 함께 세우면
|
|
5959
|
+
* 제목 줄이 그만큼 커져 카드마다 머리 높이가 달라진다. 띄우면 제목은 제 높이로 선다.
|
|
5960
|
+
*
|
|
5961
|
+
* 세 가지를 이 조각이 맡는다. 앱마다 손으로 하다 보니 같은 규칙이 여러 벌이 됐다(말귀 두 벌 —
|
|
5962
|
+
* `top/right: 19px` 라는 **카드 안쪽 여백**을 베껴 적고 있었다. 그 수를 아는 쪽은 카드다).
|
|
5963
|
+
*
|
|
5964
|
+
* 1. **자리** — 카드 안쪽 여백에 맞춰 띄운다(`--card-pad`, 기본 19px). 카드가 여백을 고치면 함께 움직인다.
|
|
5965
|
+
* 2. **좁은 화면(600px 이하)에서 흐름으로 되돌린다** — 띄운 채로 두면 제목과 그 아랫줄을 덮는다.
|
|
5966
|
+
* 문턱이 600 인 것은 이 줄이 대개 250px 안팎이라, 그 아래에서는 제목과 나란히 설 자리가 없어서다.
|
|
5967
|
+
* 3. **제 폭을 재어 알린다** — 띄운 줄은 흐름에서 빠져 제목·곁줄을 덮으므로 그만큼 비워 둬야 하는데,
|
|
5968
|
+
* 부르는 쪽이 눈대중으로 `padding-right: 260px` 같은 수를 적고 있었다. 잰 폭을 부모에
|
|
5969
|
+
* `--section-head-float-w` 로 적으므로, 비울 자리는 `padding-right: calc(var(--section-head-float-w, -12px) + 12px)`
|
|
5970
|
+
* 한 줄이면 된다(안 띄운 카드에서는 0 이 된다). 코어 카드(`manager-section`)는 이미 그렇게 해 둔다.
|
|
5971
|
+
*
|
|
5972
|
+
* 기준점은 **부모가 준다** — 카드에 `position: relative` 가 있어야 한다(코어 카드에는 있다).
|
|
5973
|
+
* @public
|
|
5974
|
+
*/
|
|
5975
|
+
export declare function SectionHeadFloat({ children, className }: SectionHeadFloatProps): JSX.Element;
|
|
5976
|
+
|
|
5977
|
+
/** @public */
|
|
5978
|
+
export declare interface SectionHeadFloatProps {
|
|
5979
|
+
children?: ReactNode;
|
|
5980
|
+
className?: string;
|
|
5981
|
+
}
|
|
5982
|
+
|
|
5860
5983
|
/** @public */
|
|
5861
5984
|
export declare interface SectionProps {
|
|
5862
5985
|
type?: SectionType;
|
|
@@ -9965,8 +10088,23 @@ export declare interface WordTableProps {
|
|
|
9965
10088
|
};
|
|
9966
10089
|
/** 페이지 번호 위치 — 'none'이면 미표시 (기본 'right') */
|
|
9967
10090
|
pageNumberPosition?: PageNumberPosition;
|
|
9968
|
-
/**
|
|
9969
|
-
|
|
10091
|
+
/**
|
|
10092
|
+
* 정해진 세로 자리에 맞춰 줄인다(세로 용지가 넘쳐 잘리는 것 방지).
|
|
10093
|
+
*
|
|
10094
|
+
* - **수(px)** — 그 높이의 **담는 틀을 표가 스스로** 두른다. 표는 그 안에서 가로·세로 가운데에
|
|
10095
|
+
* 선다. 부르는 쪽은 "이만큼 자리에 맞춰 줄여" 한 마디면 된다.
|
|
10096
|
+
* - **`{ wide, narrow }`** — 좁은 화면(767px 이하)에서는 `narrow` 를 쓴다. 좁으면 **폭이 먼저
|
|
10097
|
+
* 걸려** 표가 가로에 맞춰 줄고 상자 아래가 비므로, 상자를 그만큼 낮춰 빈 자리를 없앤다
|
|
10098
|
+
* (말귀 실측 375px: 320px 상자에 183px 표 — 137px 이 비었다).
|
|
10099
|
+
* - **`true`** — 틀을 두르지 않는다. **부모가 높이 제한 박스여야 한다**(예전 동작).
|
|
10100
|
+
*
|
|
10101
|
+
* 앱마다 이 틀을 손수 만들다 보니 같은 규칙이 여러 벌이 됐다(말귀 두 벌, 주석 문장까지 같았다)
|
|
10102
|
+
* — 틀을 여기로 들인 까닭이다(2026-09-24).
|
|
10103
|
+
*/
|
|
10104
|
+
fitHeight?: boolean | number | {
|
|
10105
|
+
wide: number;
|
|
10106
|
+
narrow: number;
|
|
10107
|
+
};
|
|
9970
10108
|
}
|
|
9971
10109
|
|
|
9972
10110
|
/**
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
/* 테두리 1px 만큼 padding 을 1px 줄인다(20 → 19). 전역 box-sizing:border-box 는
|
|
13
13
|
**정한 폭**에만 테두리를 넣어 줄 뿐 높이가 auto 인 카드는 위아래 2px 커지고,
|
|
14
14
|
안쪽 내용도 1px 씩 안으로 밀린다. 이렇게 두면 바깥 크기와 내용 자리가 전과 같다. */
|
|
15
|
-
|
|
15
|
+
/* 안쪽 여백을 변수로도 내보낸다 — 머리 오른쪽에 띄우는 줄(`SectionHeadFloat`)이 이 값에
|
|
16
|
+
맞춰 서야 제목 줄과 윗선이 맞는다. 부르는 쪽이 그 수를 베껴 적지 않게 한다(2026-09-24). */
|
|
17
|
+
--card-pad: 19px;
|
|
18
|
+
padding: var(--card-pad);
|
|
19
|
+
/* 머리 오른쪽에 띄우는 줄(`SectionHeadFloat`)의 기준점 — 없으면 창 구석으로 날아간다 */
|
|
20
|
+
position: relative;
|
|
16
21
|
margin-bottom: 14px;
|
|
17
22
|
/* 차트 카드 — inline 나열 가능하도록 최소 폭만 지정(최대 폭 제한 없음) */
|
|
18
23
|
min-width: 320px;
|
|
@@ -37,6 +42,13 @@
|
|
|
37
42
|
/* 머리 바로 아래가 합계 줄이라 12px 은 떠 보인다 — 한 눈금 줄인다 */
|
|
38
43
|
margin-bottom: var(--spacing-xs);
|
|
39
44
|
}
|
|
45
|
+
/* **띄운 줄이 있으면 그만큼 비워 둔다.** 띄운 줄은 흐름에서 빠져 제목·곁줄을 덮는다.
|
|
46
|
+
`SectionHeadFloat` 이 잰 제 폭을 카드에 적어 두므로 눈대중으로 수를 적을 일이 없다 —
|
|
47
|
+
안 띄운 카드에서는 -12 + 12 = 0 이라 아무 일도 안 일어난다(2026-09-24). */
|
|
48
|
+
.manager-section__title-row,
|
|
49
|
+
.manager-section__disc {
|
|
50
|
+
padding-right: calc(var(--section-head-float-w, -12px) + 12px);
|
|
51
|
+
}
|
|
40
52
|
/* 제목은 줄어들 수 있어야 한다 — min-width 기본값(auto)이면 글자 길이만큼 버텨 넘친다 */
|
|
41
53
|
.manager-section__title {
|
|
42
54
|
min-width: 0;
|
|
@@ -106,7 +118,9 @@
|
|
|
106
118
|
flex-wrap: nowrap;
|
|
107
119
|
}
|
|
108
120
|
.manager-section {
|
|
109
|
-
|
|
121
|
+
/* 띄운 줄도 이 값에 맞춰 선다 — 여백은 `--card-pad` 한 군데서 정한다 */
|
|
122
|
+
--card-pad: 17px; /* 18 − 테두리 1px */
|
|
123
|
+
padding: var(--card-pad);
|
|
110
124
|
/* 기본 규칙의 min-width:320px는 "차트 카드를 가로로 나란히 놓을 수 있게" 잡아둔
|
|
111
125
|
하한인데, 이 폭부터는 카드가 한 장씩 세로로 쌓여 그 이유가 사라진다. 그대로
|
|
112
126
|
두면 320px 기기에서 카드가 부모(280px)를 40px 넘쳐 오른쪽이 잘렸다.
|
|
@@ -207,7 +221,8 @@
|
|
|
207
221
|
패딩·헤더 높이·간격을 StatChart(.manager-stat-card)와 동일하게 맞춰, 같은 그리드 행에 나란히
|
|
208
222
|
놓였을 때 숫자 세로 위치가 어긋나지 않게 한다 */
|
|
209
223
|
.manager-section--compact {
|
|
210
|
-
|
|
224
|
+
--card-pad: 19px; /* 20 − 테두리 1px */
|
|
225
|
+
padding: var(--card-pad);
|
|
211
226
|
min-width: 160px;
|
|
212
227
|
}
|
|
213
228
|
.manager-section--compact .manager-section__head {
|
|
@@ -50,6 +50,17 @@ export interface ChartSectionProps {
|
|
|
50
50
|
headRight?: ReactNode | ((state: {
|
|
51
51
|
collapsed: boolean;
|
|
52
52
|
}) => ReactNode);
|
|
53
|
+
/**
|
|
54
|
+
* 머리 오른쪽에 **띄우는** 단추 줄 — `headRight` 와 달리 흐름에서 빠진다.
|
|
55
|
+
*
|
|
56
|
+
* 40px 짜리 단추를 제목 줄에 함께 세우면 제목 줄이 그만큼 커져 카드마다 머리 높이가 달라진다.
|
|
57
|
+
* 띄우면 제목은 제 높이로 서고, 제목·곁줄은 이 줄의 **잰 폭만큼** 자리를 비운다. 좁은 화면
|
|
58
|
+
* (600px 이하)에서는 흐름으로 되돌아가 제목 아래에 선다. 자세한 것은 `SectionHeadFloat`.
|
|
59
|
+
*
|
|
60
|
+
* 고르개·상세보기처럼 **제목과 한 줄로 서야 하는 것**은 `headRight` 가 맞다. 이쪽은 '숙제 보내기'
|
|
61
|
+
* 처럼 카드 전체에 대고 누르는 단추 자리다.
|
|
62
|
+
*/
|
|
63
|
+
headFloat?: ReactNode;
|
|
53
64
|
/** 머리의 보기 고르개. 카드가 좁으면 '⋯' 한 단추로 접히고, 그 메뉴에 칸들이 들어간다. */
|
|
54
65
|
headSegment?: ChartHeadSegment;
|
|
55
66
|
/** 접힌 '⋯' 메뉴에서 칸들 **뒤** 에 구분선과 함께 붙는 항목(상세보기 등) */
|
|
@@ -105,4 +116,4 @@ export interface ChartSectionProps {
|
|
|
105
116
|
* 공유하도록 두 컴포넌트가 내부적으로 사용한다.
|
|
106
117
|
* @public
|
|
107
118
|
*/
|
|
108
|
-
export default function ChartSection({ title, titleClassName, icon, disc, headRight, headSegment, headMenuItems, collapseBelow, onClick, ariaLabel, tooltip, totalNum, totalUnit, totalLabel, subRow, subRowAlign, className, inGrid, children }: ChartSectionProps): import("react").JSX.Element;
|
|
119
|
+
export default function ChartSection({ title, titleClassName, icon, disc, headRight, headFloat, headSegment, headMenuItems, collapseBelow, onClick, ariaLabel, tooltip, totalNum, totalUnit, totalLabel, subRow, subRowAlign, className, inGrid, children }: ChartSectionProps): import("react").JSX.Element;
|
|
@@ -4,6 +4,7 @@ import './ChartSection.css';
|
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import Icon from './Icon';
|
|
7
|
+
import SectionHeadFloat from './SectionHeadFloat';
|
|
7
8
|
import Button from './Button';
|
|
8
9
|
import SegmentedControl from './SegmentedControl';
|
|
9
10
|
import Tooltip from './Tooltip/Tooltip';
|
|
@@ -28,7 +29,7 @@ export function ChartTotal({ label, children }) {
|
|
|
28
29
|
* 공유하도록 두 컴포넌트가 내부적으로 사용한다.
|
|
29
30
|
* @public
|
|
30
31
|
*/
|
|
31
|
-
export default function ChartSection({ title, titleClassName = 'h3', icon, disc, headRight, headSegment, headMenuItems, collapseBelow = 420, onClick, ariaLabel, tooltip, totalNum, totalUnit, totalLabel, subRow, subRowAlign = 'start', className, inGrid = false, children }) {
|
|
32
|
+
export default function ChartSection({ title, titleClassName = 'h3', icon, disc, headRight, headFloat, headSegment, headMenuItems, collapseBelow = 420, onClick, ariaLabel, tooltip, totalNum, totalUnit, totalLabel, subRow, subRowAlign = 'start', className, inGrid = false, children }) {
|
|
32
33
|
const { t } = useTranslation();
|
|
33
34
|
const secRef = useRef(null);
|
|
34
35
|
/* 설명 툴팁 — StatChart 와 같은 결(카드 사각형 기준, 포인터가 나가거나 초점이 빠지면 닫힘).
|
|
@@ -113,5 +114,5 @@ export default function ChartSection({ title, titleClassName = 'h3', icon, disc,
|
|
|
113
114
|
bottom: 설명자리.bottom,
|
|
114
115
|
width: 설명자리.width,
|
|
115
116
|
height: 설명자리.height,
|
|
116
|
-
}, text: tooltip })), _jsxs("div", { className: "manager-section__head", children: [_jsxs("div", { className: "manager-section__title-row", children: [icon && _jsx(Icon, { icon: icon, label: typeof title === 'string' ? title : undefined, className: "manager-section__icon" }), _jsx("div", { className: ['manager-section__title', titleClassName].filter(Boolean).join(' '), children: title })] }), 머리오른쪽, disc && _jsx("div", { className: "manager-section__disc body-3", children: disc })] }), (수줄 || subRow) && (_jsxs("div", { className: ['barchart-sub-row', subRowAlign !== 'start' && `barchart-sub-row--${subRowAlign}`].filter(Boolean).join(' '), children: [수줄, subRow] })), children, 메뉴자리 && (_jsx(Tooltip, { anchorRect: 메뉴자리, onClose: 메뉴닫기, ariaLabel: headSegment?.ariaLabel ?? t('tooltip.menu', '메뉴'), children: _jsx(MenuTooltip, { columns: [{ items: 메뉴항목 }], onClose: 메뉴닫기, size: "small" }) }))] }));
|
|
117
|
+
}, text: tooltip })), headFloat && _jsx(SectionHeadFloat, { children: headFloat }), _jsxs("div", { className: "manager-section__head", children: [_jsxs("div", { className: "manager-section__title-row", children: [icon && _jsx(Icon, { icon: icon, label: typeof title === 'string' ? title : undefined, className: "manager-section__icon" }), _jsx("div", { className: ['manager-section__title', titleClassName].filter(Boolean).join(' '), children: title })] }), 머리오른쪽, disc && _jsx("div", { className: "manager-section__disc body-3", children: disc })] }), (수줄 || subRow) && (_jsxs("div", { className: ['barchart-sub-row', subRowAlign !== 'start' && `barchart-sub-row--${subRowAlign}`].filter(Boolean).join(' '), children: [수줄, subRow] })), children, 메뉴자리 && (_jsx(Tooltip, { anchorRect: 메뉴자리, onClose: 메뉴닫기, ariaLabel: headSegment?.ariaLabel ?? t('tooltip.menu', '메뉴'), children: _jsx(MenuTooltip, { columns: [{ items: 메뉴항목 }], onClose: 메뉴닫기, size: "small" }) }))] }));
|
|
117
118
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import './CountTile.css';
|
|
2
|
+
import type { TrainingPlan } from '../types/training';
|
|
2
3
|
import type { ReactNode, MouseEvent } from 'react';
|
|
3
4
|
/** @public */
|
|
4
5
|
export interface CountTileProps {
|
|
@@ -7,21 +8,25 @@ export interface CountTileProps {
|
|
|
7
8
|
/** 우측 하단 숫자 */
|
|
8
9
|
count: number | string;
|
|
9
10
|
/**
|
|
10
|
-
* 갈래 이름 — 낭독기가 읽는
|
|
11
|
+
* 갈래 이름 — 낭독기가 읽는 이름이다(`"이름 수"` 로 읽힌다).
|
|
11
12
|
*
|
|
12
13
|
* **필수다.** 72×72 안에는 아이콘과 수뿐이라, 이 값이 없으면 그 아이콘이 무슨 갈래인지
|
|
13
|
-
* 알 길이
|
|
14
|
-
* 예전에는 선택값이었고 `aria-label` 로만 나가, 마우스 쓰는 사람은 **아무것도 못 봤다**
|
|
15
|
-
* (사용자 지시 2026-09-23: "호버시 tooltip 은 무조건 나와야 해").
|
|
14
|
+
* 낭독기로는 알 길이 없다(말귀 '나의 단어훈련' 카드에 타일이 열 개 선다).
|
|
16
15
|
*/
|
|
17
16
|
title: string;
|
|
18
17
|
/**
|
|
19
|
-
*
|
|
18
|
+
* 호버하면 보여 줄 **담긴 훈련**.
|
|
20
19
|
*
|
|
21
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
20
|
+
* 주면 마우스를 얹었을 때 `TrainingTooltip` 이 뜬다 — 그 안에 훈련 블록이 서고, 블록의
|
|
21
|
+
* 갈래 아이콘(`CategoryIcon`)에 다시 마우스를 얹으면 제 이름이 작은 툴팁으로 뜬다
|
|
22
|
+
* (사용자 지시 2026-09-23). 안 주면 아무것도 안 뜬다 — 타일 이름만 띄우는 글자 툴팁은
|
|
23
|
+
* **두지 않는다**(0.187.0 에 잠깐 있었고, 그건 요청을 잘못 읽은 것이었다).
|
|
24
|
+
*
|
|
25
|
+
* 이 타일이 세는 그 갈래의 항목만 담은 `plan` 을 준다 — `TrainingTooltip` 은 받은 `plan.items`
|
|
26
|
+
* 를 갈래별로 묶어 그리므로, 카드 전체의 `plan` 을 그대로 주면 타일마다 같은 것이 뜬다.
|
|
24
27
|
*/
|
|
28
|
+
plan?: TrainingPlan;
|
|
29
|
+
/** 호버 툴팁을 끈다(기본 켬) — 부르는 쪽이 제 툴팁을 띄우는 자리용 */
|
|
25
30
|
tooltip?: boolean;
|
|
26
31
|
className?: string;
|
|
27
32
|
/** 클릭 핸들러 — 설정 시 버튼처럼 동작 */
|
|
@@ -33,8 +38,8 @@ export interface CountTileProps {
|
|
|
33
38
|
* 72×72 정사각 타일. 좌상단에 아이콘, 우하단에 숫자를 배치한다.
|
|
34
39
|
* 훈련 카드의 타입별 문항 수 표시 등에 사용.
|
|
35
40
|
*
|
|
36
|
-
* 마우스를
|
|
37
|
-
* (지나가다 스치는 것에 번쩍이지 않게).
|
|
41
|
+
* `plan` 을 주면 마우스를 얹었을 때 **담긴 훈련**(`TrainingTooltip`)이 뜬다 — 늦춤은 다른
|
|
42
|
+
* 자리와 같은 상수를 쓴다(지나가다 스치는 것에 번쩍이지 않게).
|
|
38
43
|
* @public
|
|
39
44
|
*/
|
|
40
|
-
export default function CountTile({ icon, count, title, tooltip, className, onClick, onMouseEnter, onMouseLeave }: CountTileProps): import("react").JSX.Element;
|
|
45
|
+
export default function CountTile({ icon, count, title, plan, tooltip, className, onClick, onMouseEnter, onMouseLeave }: CountTileProps): import("react").JSX.Element;
|
|
@@ -4,47 +4,79 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { cx } from '../utils/cx';
|
|
5
5
|
import './CountTile.css';
|
|
6
6
|
import Icon from './Icon';
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import TrainingTooltip, { TRAINING_TOOLTIP_HOVER_DELAY_MS } from './Tooltip/TrainingTooltip';
|
|
8
|
+
/** 타일과 툴팁 사이 틈(12px)을 건너는 동안 툴팁을 살려 두는 시간 */
|
|
9
|
+
const 건너가는틈_MS = 200;
|
|
9
10
|
/**
|
|
10
11
|
* 72×72 정사각 타일. 좌상단에 아이콘, 우하단에 숫자를 배치한다.
|
|
11
12
|
* 훈련 카드의 타입별 문항 수 표시 등에 사용.
|
|
12
13
|
*
|
|
13
|
-
* 마우스를
|
|
14
|
-
* (지나가다 스치는 것에 번쩍이지 않게).
|
|
14
|
+
* `plan` 을 주면 마우스를 얹었을 때 **담긴 훈련**(`TrainingTooltip`)이 뜬다 — 늦춤은 다른
|
|
15
|
+
* 자리와 같은 상수를 쓴다(지나가다 스치는 것에 번쩍이지 않게).
|
|
15
16
|
* @public
|
|
16
17
|
*/
|
|
17
|
-
export default function CountTile({ icon, count, title, tooltip = true, className = '', onClick, onMouseEnter, onMouseLeave }) {
|
|
18
|
+
export default function CountTile({ icon, count, title, plan, tooltip = true, className = '', onClick, onMouseEnter, onMouseLeave }) {
|
|
18
19
|
const cls = cx('count-tile', onClick && 'count-tile--clickable', className);
|
|
19
20
|
const ref = useRef(null);
|
|
20
21
|
const [anchorRect, setAnchorRect] = useState(null);
|
|
21
22
|
const 늦춤 = useRef(null);
|
|
22
|
-
const
|
|
23
|
+
const 닫힘 = useRef(null);
|
|
24
|
+
const 켜짐 = Boolean(anchorRect && plan && tooltip);
|
|
25
|
+
const 시계끄기 = useCallback(() => {
|
|
23
26
|
if (늦춤.current) {
|
|
24
27
|
clearTimeout(늦춤.current);
|
|
25
28
|
늦춤.current = null;
|
|
26
29
|
}
|
|
27
|
-
|
|
30
|
+
if (닫힘.current) {
|
|
31
|
+
clearTimeout(닫힘.current);
|
|
32
|
+
닫힘.current = null;
|
|
33
|
+
}
|
|
28
34
|
}, []);
|
|
35
|
+
const 닫기 = useCallback(() => { 시계끄기(); setAnchorRect(null); }, [시계끄기]);
|
|
29
36
|
/* 늦춰서 연다 — 타일이 열 개 붙어 서는 자리라, 지나가기만 해도 뜨면 줄줄이 번쩍인다. */
|
|
30
|
-
const 열기 = useCallback((
|
|
31
|
-
if (!tooltip)
|
|
37
|
+
const 열기 = useCallback(() => {
|
|
38
|
+
if (!tooltip || !plan)
|
|
32
39
|
return;
|
|
33
|
-
|
|
40
|
+
시계끄기();
|
|
41
|
+
늦춤.current = setTimeout(() => {
|
|
42
|
+
늦춤.current = null;
|
|
43
|
+
setAnchorRect(ref.current?.getBoundingClientRect() ?? null);
|
|
44
|
+
}, TRAINING_TOOLTIP_HOVER_DELAY_MS);
|
|
45
|
+
}, [tooltip, plan, 시계끄기]);
|
|
46
|
+
/**
|
|
47
|
+
* **바로 닫지 않는다.** 툴팁은 타일에서 12px 떨어져 뜨므로, 타일을 벗어나자마자 닫으면
|
|
48
|
+
* 그 틈을 건너는 동안 사라져 **툴팁 안의 갈래 아이콘에 마우스를 올릴 수가 없다** — 그
|
|
49
|
+
* 아이콘에 다시 뜨는 작은 이름표가 이 툴팁을 다는 까닭의 절반이다(사용자 지시 2026-09-23).
|
|
50
|
+
* 잠깐 두고, 그 사이 툴팁 안으로 들어오면 아래 지킴이가 예약을 무른다.
|
|
51
|
+
*/
|
|
52
|
+
const 닫기예약 = useCallback(() => {
|
|
53
|
+
if (늦춤.current) {
|
|
34
54
|
clearTimeout(늦춤.current);
|
|
35
|
-
const 띄우기 = () => setAnchorRect(ref.current?.getBoundingClientRect() ?? null);
|
|
36
|
-
if (바로) {
|
|
37
55
|
늦춤.current = null;
|
|
38
|
-
띄우기();
|
|
39
|
-
return;
|
|
40
56
|
}
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
if (닫힘.current)
|
|
58
|
+
return;
|
|
59
|
+
닫힘.current = setTimeout(() => { 닫힘.current = null; setAnchorRect(null); }, 건너가는틈_MS);
|
|
60
|
+
}, []);
|
|
43
61
|
/* 떠난 뒤에 타이머가 터지면 아무도 안 보는 자리에 툴팁이 남는다 */
|
|
44
|
-
useEffect(() => () => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
useEffect(() => () => { 시계끄기(); }, [시계끄기]);
|
|
63
|
+
useEffect(() => { if (!tooltip || !plan)
|
|
64
|
+
닫기(); }, [tooltip, plan, 닫기]);
|
|
65
|
+
/* 툴팁이 떠 있는 동안 **포인터가 어디에 있는지** 본다 — 타일이나 툴팁 안이면 살려 두고,
|
|
66
|
+
둘 다 아니면 닫기를 예약한다. 툴팁은 포털로 body 끝에 나가 타일의 자식이 아니라서,
|
|
67
|
+
타일에 건 mouseleave 만으로는 "툴팁으로 건너갔다" 를 알 수 없다. */
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!켜짐)
|
|
70
|
+
return;
|
|
71
|
+
const 살릴자리 = (t) => t instanceof Element && (ref.current?.contains(t) || Boolean(t.closest('.tooltip-training')));
|
|
72
|
+
const 보기 = (e) => { if (살릴자리(e.target)) {
|
|
73
|
+
시계끄기();
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
닫기예약(); };
|
|
77
|
+
document.addEventListener('pointerover', 보기, true);
|
|
78
|
+
return () => document.removeEventListener('pointerover', 보기, true);
|
|
79
|
+
}, [켜짐, 시계끄기, 닫기예약]);
|
|
48
80
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: ref, className: cls, onClick: onClick ? (e) => { 닫기(); onClick(e); } : undefined,
|
|
49
81
|
/* role="button" + tabIndex 만 있고 키 처리가 없으면, 키보드 사용자는 여기 멈추고도
|
|
50
82
|
누를 수가 없다 — `<div>` 는 브라우저가 Enter·Space 를 대신 처리해 주지 않는다.
|
|
@@ -67,7 +99,5 @@ export default function CountTile({ icon, count, title, tooltip = true, classNam
|
|
|
67
99
|
onPointerEnter: (e) => { if (e.pointerType === 'touch' || e.pointerType === 'pen')
|
|
68
100
|
닫기();
|
|
69
101
|
else
|
|
70
|
-
열기(
|
|
71
|
-
/* 키보드로 옮겨 온 사람에게도 보여야 한다 — 멈춘 자리라 늦출 까닭이 없어 바로 연다. */
|
|
72
|
-
onFocus: onClick ? () => 열기(true) : undefined, onBlur: onClick ? 닫기 : undefined, role: onClick ? 'button' : 'img', tabIndex: onClick ? 0 : undefined, "aria-label": `${title} ${count}`, children: [_jsx(Icon, { icon: icon, size: 20, className: "count-tile__icon" }), _jsx("span", { className: "count-tile__count h5", "aria-hidden": "true", children: count })] }), anchorRect && _jsx(InfoTooltip, { anchorRect: anchorRect, text: title })] }));
|
|
102
|
+
열기(); }, onMouseEnter: onMouseEnter, onMouseLeave: (e) => { 닫기예약(); onMouseLeave?.(e); }, role: onClick ? 'button' : 'img', tabIndex: onClick ? 0 : undefined, "aria-label": `${title} ${count}`, children: [_jsx(Icon, { icon: icon, size: 20, className: "count-tile__icon" }), _jsx("span", { className: "count-tile__count h5", "aria-hidden": "true", children: count })] }), anchorRect && plan && _jsx(TrainingTooltip, { plan: plan, anchorRect: anchorRect, onClose: 닫기 })] }));
|
|
73
103
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* 작은 선 그래프 — 까닭과 쓰는 법은 `LineChart.tsx` 의 글에.
|
|
2
|
+
**글자 크기를 여기 px 로 적는 것이 뜻이 있으려면** 그림이 배율 1 이어야 한다(그래서 제 상자를 잰다). */
|
|
3
|
+
.line-chart {
|
|
4
|
+
width: 100%;
|
|
5
|
+
height: auto;
|
|
6
|
+
display: block;
|
|
7
|
+
overflow: visible;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.line-chart__axis-title {
|
|
11
|
+
font-size: 12px;
|
|
12
|
+
fill: var(--color-gray);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.line-chart__tick {
|
|
16
|
+
font-size: 12px;
|
|
17
|
+
fill: var(--color-gray);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.line-chart__grid {
|
|
21
|
+
stroke: var(--color-whiteLine);
|
|
22
|
+
stroke-width: 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.line-chart__line {
|
|
26
|
+
stroke-width: 2;
|
|
27
|
+
stroke-linejoin: round;
|
|
28
|
+
stroke-linecap: round;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* 겨누는 자리 — 보이지 않지만 손이 닿는다 */
|
|
32
|
+
.line-chart__dot-hit {
|
|
33
|
+
fill: transparent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.line-chart__dot-main {
|
|
37
|
+
stroke-width: 1.5;
|
|
38
|
+
}
|
|
39
|
+
/* 속 빈 점 — 믿기 이른 값. 채움은 여기서 준다(선 색은 부르는 쪽이 정하므로 인라인이다) */
|
|
40
|
+
.line-chart__dot-main.is-hollow {
|
|
41
|
+
fill: var(--color-white);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* 점에 얹으면 뜨는 말풍선 — 좁은 카드 안에서 줄이 잘리지 않게 */
|
|
45
|
+
.line-chart__tip {
|
|
46
|
+
max-width: 220px;
|
|
47
|
+
white-space: normal;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.line-chart__dot {
|
|
51
|
+
cursor: default;
|
|
52
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import './LineChart.css';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
/** 점 위에 잠깐 머물면 뜨는 뜸 — 지나가는 마우스에 툴팁이 번쩍이지 않을 만큼만 짧게 @public */
|
|
4
|
+
export declare const LINE_CHART_HOVER_DELAY_MS = 120;
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface LineChartPoint {
|
|
7
|
+
/** 가로 자리 — `range` 안의 값 */
|
|
8
|
+
x: number;
|
|
9
|
+
/** 세로 자리 — 0~100(%) */
|
|
10
|
+
pct: number;
|
|
11
|
+
/** 속을 비워 그린다(믿기 이른 값 등). 비운 점은 **선에서도 뺀다** */
|
|
12
|
+
hollow?: boolean;
|
|
13
|
+
/** 마우스를 얹으면 뜰 글. 없으면 그 점에는 안 뜬다 */
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
/** 그림 칸 안에서 값을 픽셀 자리로 옮기는 자 — `overlay` 가 이것으로 제 것을 덧그린다 @public */
|
|
17
|
+
export interface LineChartScales {
|
|
18
|
+
/** 가로 값 → x 픽셀 */
|
|
19
|
+
x: (v: number) => number;
|
|
20
|
+
/** 0~100 → y 픽셀 */
|
|
21
|
+
y: (pct: number) => number;
|
|
22
|
+
/** 그림이 그려지는 네모(축 안쪽) */
|
|
23
|
+
plot: {
|
|
24
|
+
left: number;
|
|
25
|
+
right: number;
|
|
26
|
+
top: number;
|
|
27
|
+
bottom: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** @public */
|
|
33
|
+
export interface LineChartProps {
|
|
34
|
+
/** 그릴 점들 — 비면 축과 눈금만 선다 */
|
|
35
|
+
points?: LineChartPoint[];
|
|
36
|
+
/** 가로 범위 `[처음, 끝]` — 어떤 축인지(조건·주차·문항 수…)는 부르는 쪽이 정한다 */
|
|
37
|
+
range: [number, number];
|
|
38
|
+
/** 가로 눈금 — 안 주면 가로 글자가 없다 */
|
|
39
|
+
ticks?: {
|
|
40
|
+
x: number;
|
|
41
|
+
label: string;
|
|
42
|
+
}[];
|
|
43
|
+
/** 세로축 이름. **재는 것이 정답률이 아니면 반드시 준다** — 이름이 없으면 보는 사람이 정답률로 읽는다 */
|
|
44
|
+
axisTitle?: ReactNode;
|
|
45
|
+
/** 선·점 색. 안 주면 주 색 */
|
|
46
|
+
color?: string;
|
|
47
|
+
/** 그림 높이(px) — 배율이 1 이라 이 수가 곧 화면 높이다 */
|
|
48
|
+
height?: number;
|
|
49
|
+
/** 제 상자를 **못 잴 때만** 쓰는 대비 폭(첫 렌더·시험 틀) */
|
|
50
|
+
fallbackWidth?: number;
|
|
51
|
+
/** 축 안쪽 여백 — 왼쪽은 "100%" 가 들어갈 만큼이 기본이다 */
|
|
52
|
+
insets?: Partial<{
|
|
53
|
+
left: number;
|
|
54
|
+
right: number;
|
|
55
|
+
top: number;
|
|
56
|
+
bottom: number;
|
|
57
|
+
}>;
|
|
58
|
+
/** 그림 위에 덧그릴 것 — 자를 받아 SVG 조각을 돌려준다(면 칠하기·짚는 자리 같은 **그 화면의 말**) */
|
|
59
|
+
overlay?: (scales: LineChartScales) => ReactNode;
|
|
60
|
+
/** 낭독기가 읽을 글 — 안 주면 점들의 `description` 을 한 줄로 잇는다 */
|
|
61
|
+
ariaLabel?: string;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* **작은 선 그래프** — 시간·조건에 따라 값이 어떻게 흐르는지를 그린다.
|
|
66
|
+
* 세로는 0~100%로 고정이고, 가로 범위는 부르는 쪽이 준다.
|
|
67
|
+
*
|
|
68
|
+
* **제 상자를 재어 배율 1 로 그린다.** `viewBox` 를 상자보다 좁게 두면 SVG 가 통째로 늘어나
|
|
69
|
+
* **글자까지 커진다**(말귀 실측 1.22배 — 사용자 지적 2026-09-17 "이 그래프가 폰트가 크게 보이는게
|
|
70
|
+
* 스케일 되서 그런건가?"). 잰 픽셀을 그대로 쓰면 CSS 에 적은 12px 이 화면에서도 12px 이다.
|
|
71
|
+
* 못 재는 자리(첫 렌더·ResizeObserver 없는 틀)에서는 `fallbackWidth` 로 그린다 — 늘어날 뿐 안 깨진다.
|
|
72
|
+
*
|
|
73
|
+
* **면 칠하기·짚는 자리처럼 그 화면에만 있는 말은 `overlay` 로 덧그린다** — 자(`x`·`y`)를 받으므로
|
|
74
|
+
* 좌표 셈을 다시 하지 않는다.
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export default function LineChart({ points, range, ticks, axisTitle, color, height, fallbackWidth, insets, overlay, ariaLabel, className, }: LineChartProps): import("react").JSX.Element;
|