@ihabdevteam/core 0.13.0 → 0.13.2
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/dist/components.cjs.js +18 -18
- package/dist/ihabdevteam-core.cjs.js +19 -19
- package/dist/ihabdevteam-core.d.ts +9 -1
- package/dist/packages/core/src/components/ProgressIndicator.d.ts +9 -1
- package/dist/packages/core/src/components/ProgressIndicator.js +2 -2
- package/dist/packages/core/src/components/SoundCompass.js +36 -5
- package/package.json +1 -1
|
@@ -3889,7 +3889,7 @@ export declare interface ProgressBarProps {
|
|
|
3889
3889
|
}
|
|
3890
3890
|
|
|
3891
3891
|
/** @public */
|
|
3892
|
-
export declare function ProgressIndicator({ max, index, inactiveBg, inactiveColor, transparent, variant, announce }: ProgressIndicatorProps): JSX.Element | null;
|
|
3892
|
+
export declare function ProgressIndicator({ max, index, inactiveBg, inactiveColor, transparent, variant, announce, ariaLabel }: ProgressIndicatorProps): JSX.Element | null;
|
|
3893
3893
|
|
|
3894
3894
|
/** @public */
|
|
3895
3895
|
export declare interface ProgressIndicatorProps {
|
|
@@ -3923,6 +3923,14 @@ export declare interface ProgressIndicatorProps {
|
|
|
3923
3923
|
* 자리(검사 안내)에서는 켜는 편이 낫다.
|
|
3924
3924
|
*/
|
|
3925
3925
|
announce?: boolean;
|
|
3926
|
+
/**
|
|
3927
|
+
* 이름표를 직접 준다. 안 주면 i18n 의 `progress.steps` 로 짓는다.
|
|
3928
|
+
*
|
|
3929
|
+
* i18n 을 쓰지 않는 앱에서는 그 값이 한글 기본값으로 떨어진다 —
|
|
3930
|
+
* 화면은 영문인데 이름표만 한글이 되는 자리가 실제로 있었다
|
|
3931
|
+
* (sound-localization-system 의 병원용 빌드).
|
|
3932
|
+
*/
|
|
3933
|
+
ariaLabel?: string;
|
|
3926
3934
|
}
|
|
3927
3935
|
|
|
3928
3936
|
/** key → 실제 배경 컴포넌트. PromoBanner 등 사용처가 이걸로 렌더한다. 각 컴포넌트는 자신의
|
|
@@ -31,6 +31,14 @@ export interface ProgressIndicatorProps {
|
|
|
31
31
|
* 자리(검사 안내)에서는 켜는 편이 낫다.
|
|
32
32
|
*/
|
|
33
33
|
announce?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* 이름표를 직접 준다. 안 주면 i18n 의 `progress.steps` 로 짓는다.
|
|
36
|
+
*
|
|
37
|
+
* i18n 을 쓰지 않는 앱에서는 그 값이 한글 기본값으로 떨어진다 —
|
|
38
|
+
* 화면은 영문인데 이름표만 한글이 되는 자리가 실제로 있었다
|
|
39
|
+
* (sound-localization-system 의 병원용 빌드).
|
|
40
|
+
*/
|
|
41
|
+
ariaLabel?: string;
|
|
34
42
|
}
|
|
35
43
|
/** @public */
|
|
36
|
-
export default function ProgressIndicator({ max, index, inactiveBg, inactiveColor, transparent, variant, announce }: ProgressIndicatorProps): import("react").JSX.Element | null;
|
|
44
|
+
export default function ProgressIndicator({ max, index, inactiveBg, inactiveColor, transparent, variant, announce, ariaLabel }: ProgressIndicatorProps): import("react").JSX.Element | null;
|
|
@@ -14,7 +14,7 @@ import './ProgressIndicator.css';
|
|
|
14
14
|
*/
|
|
15
15
|
const TRANSPARENT_INACTIVE_BG = 'rgba(255, 255, 255, 0.16)';
|
|
16
16
|
/** @public */
|
|
17
|
-
export default function ProgressIndicator({ max, index, inactiveBg, inactiveColor, transparent, variant = 'numbered', announce = false }) {
|
|
17
|
+
export default function ProgressIndicator({ max, index, inactiveBg, inactiveColor, transparent, variant = 'numbered', announce = false, ariaLabel }) {
|
|
18
18
|
const { t } = useTranslation();
|
|
19
19
|
const isMobile = useMediaQuery('(max-width: 767px)');
|
|
20
20
|
if (!Number.isInteger(max) || max <= 0)
|
|
@@ -23,7 +23,7 @@ export default function ProgressIndicator({ max, index, inactiveBg, inactiveColo
|
|
|
23
23
|
/* 목록에 이름이 없어 낭독기가 "목록, 4개, 1, 2, 3, 4" 로만 읽었다 — 무엇의 목록인지,
|
|
24
24
|
지금 어디인지 알 수 없다. 훈련 팝업에는 숫자 묶음이 여럿이라 더 헷갈린다.
|
|
25
25
|
칸마다 붙은 aria-current 는 목록을 훑어야 닿으므로, **머리에서 바로 자리를 말한다.** */
|
|
26
|
-
const groupLabel = t('progress.steps', {
|
|
26
|
+
const groupLabel = ariaLabel ?? t('progress.steps', {
|
|
27
27
|
current: idx + 1, total: max, defaultValue: '진행 단계 {{current}} / {{total}}',
|
|
28
28
|
});
|
|
29
29
|
const rootStyle = {};
|
|
@@ -79,11 +79,42 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
79
79
|
const avgStep = span / (degs.length - 1);
|
|
80
80
|
const wrapGap = 360 - span;
|
|
81
81
|
const isLoop = sorted.length >= 3 && avgStep > 0 && wrapGap <= avgStep * 1.5;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
// 스피커는 탑뷰에서 한 원 위에 놓이므로 이웃을 **직선이 아니라 호**로 잇는다.
|
|
83
|
+
// 작은 나침반에서는 티가 안 났지만 크게 키우면 꺾인 다각형이 그대로 보인다
|
|
84
|
+
// (실측: HORIZON_5 는 45° 간격이라 현이 호 안쪽으로 57.6px 들어간다).
|
|
85
|
+
//
|
|
86
|
+
// 다만 **정면뷰에서는 호가 틀린다** — 그쪽은 격자라 같은 row 가 같은 y 에
|
|
87
|
+
// 서고, 즉 곧은 가로줄이다. 그래서 t 로 부풀림을 줄여 t=1 에서 정확히
|
|
88
|
+
// 직선이 되게 한다. 탑뷰(t=0)에서는 A 로 그려 근사 없이 스피커 중심을 지난다.
|
|
89
|
+
// 부동소수 찌꺼기를 path 에 싣지 않는다 — `300 * (110/300)` 이 109.99999999999999 이라
|
|
90
|
+
// 그대로 쓰면 반지름이 호마다 그 긴 값으로 적힌다(SVG 만 커지고 얻는 것이 없다).
|
|
91
|
+
const R = Math.round(getRadius(row) * 1000) / 1000;
|
|
92
|
+
const seg = (prev, cur, p0, p1) => {
|
|
93
|
+
// 방향을 짐작하지 않고 각도 차로 정한다 — 배열이 거꾸로 온 소비자도 맞게.
|
|
94
|
+
const delta = ((cur.angleDeg - prev.angleDeg + 540) % 360) - 180;
|
|
95
|
+
if (delta === 0)
|
|
96
|
+
return `L ${p1.x} ${p1.y}`;
|
|
97
|
+
if (t === 0) {
|
|
98
|
+
// sweep-flag 는 SVG 의 양의 각 방향(=화면상 시계방향)이 1 이다.
|
|
99
|
+
return `A ${R} ${R} 0 0 ${delta > 0 ? 1 : 0} ${p1.x} ${p1.y}`;
|
|
100
|
+
}
|
|
101
|
+
// 호의 중간점을 현의 중간점 쪽으로 t 만큼 당긴다. t=1 이면 제어점이 현의
|
|
102
|
+
// 중간점과 같아져 2차 곡선이 곧은 선으로 무너진다.
|
|
103
|
+
const midRad = ((prev.angleDeg + delta / 2) * Math.PI) / 180;
|
|
104
|
+
const arcMid = { x: CENTER + R * Math.cos(midRad), y: CENTER + R * Math.sin(midRad) };
|
|
105
|
+
const chordMid = { x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2 };
|
|
106
|
+
const m = { x: lerp(arcMid.x, chordMid.x, t), y: lerp(arcMid.y, chordMid.y, t) };
|
|
107
|
+
// Q(0.5) = (P0 + 2C + P1)/4 = m → C = 2m − (P0+P1)/2
|
|
108
|
+
const c = { x: 2 * m.x - chordMid.x, y: 2 * m.y - chordMid.y };
|
|
109
|
+
return `Q ${c.x} ${c.y} ${p1.x} ${p1.y}`;
|
|
110
|
+
};
|
|
111
|
+
const ring = isLoop ? [...sorted, sorted[0]] : sorted;
|
|
112
|
+
const ringPts = isLoop ? [...pts, pts[0]] : pts;
|
|
113
|
+
let d = `M ${ringPts[0].x} ${ringPts[0].y}`;
|
|
114
|
+
for (let i = 1; i < ringPts.length; i++) {
|
|
115
|
+
d += ' ' + seg(ring[i - 1], ring[i], ringPts[i - 1], ringPts[i]);
|
|
116
|
+
}
|
|
117
|
+
rowArcPaths.push({ key: `arc-${row}`, d });
|
|
87
118
|
}
|
|
88
119
|
// ── 스피커 시각 상태를 props에서 파생 ──────────────────────────────────
|
|
89
120
|
const played = useMemo(() => new Set(playedIds ?? []), [playedIds]);
|