@ihabdevteam/core 0.14.0 → 0.15.1
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 +176 -176
- package/dist/components.esm.css +1 -1
- package/dist/ihabdevteam-core.cjs.js +34 -34
- package/dist/ihabdevteam-core.d.ts +17 -1
- package/dist/packages/core/src/components/ShortcutBadge.css +10 -4
- package/dist/packages/core/src/components/SoundCompass.css +10 -0
- package/dist/packages/core/src/components/SoundCompass.d.ts +8 -1
- package/dist/packages/core/src/components/SoundCompass.js +15 -2
- package/dist/packages/core/src/types/audio.d.ts +10 -0
- package/package.json +1 -1
|
@@ -5108,7 +5108,7 @@ export declare interface SliderProps {
|
|
|
5108
5108
|
export declare function sortToValue(key: string, asc: boolean): string | undefined;
|
|
5109
5109
|
|
|
5110
5110
|
/** @public */
|
|
5111
|
-
export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, clickable, onSelectSpeaker, buttonSize, showIcon, }: SoundCompassProps): JSX.Element;
|
|
5111
|
+
export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, }: SoundCompassProps): JSX.Element;
|
|
5112
5112
|
|
|
5113
5113
|
export declare interface SoundCompassExam {
|
|
5114
5114
|
/** `<SoundCompass {...bind} />` 로 펼쳐 넣는 제어 props */
|
|
@@ -5183,6 +5183,12 @@ export declare interface SoundCompassProps {
|
|
|
5183
5183
|
buttonSize?: number;
|
|
5184
5184
|
/** 스피커 버튼에 아이콘을 표시할지 여부. false면 숫자만 가운데 표시. 기본 true */
|
|
5185
5185
|
showIcon?: boolean;
|
|
5186
|
+
/**
|
|
5187
|
+
* `speakers[].shortcut` 배지가 버튼의 어느 모서리에 붙을지. 기본 우상단.
|
|
5188
|
+
*
|
|
5189
|
+
* 배지 전부에 같이 걸린다 — 스피커마다 다른 자리에 두면 눈이 규칙을 못 잡는다.
|
|
5190
|
+
*/
|
|
5191
|
+
shortcutAnchor?: ShortcutBadgeAnchor;
|
|
5186
5192
|
}
|
|
5187
5193
|
|
|
5188
5194
|
/**
|
|
@@ -5213,6 +5219,16 @@ export declare interface SpeakerPos {
|
|
|
5213
5219
|
deg: number;
|
|
5214
5220
|
/** 동심원 링 번호 (1=가장 안쪽, 기본값 1) */
|
|
5215
5221
|
row?: number;
|
|
5222
|
+
/**
|
|
5223
|
+
* 이 스피커에 걸린 자판 키. 주면 버튼 모서리에 `ShortcutBadge` 가 얹힌다.
|
|
5224
|
+
*
|
|
5225
|
+
* 키가 모자란 스피커에는 주지 않으면 된다 — 배지도 안 붙는다(예: 27개 배치의
|
|
5226
|
+
* 가운데 층은 13개인데 자판 줄은 9칸이라 넷은 키가 없다).
|
|
5227
|
+
*
|
|
5228
|
+
* 무엇을 눌렀는지 판정하는 일은 부르는 쪽 몫이다. 한글 입력 상태에서는
|
|
5229
|
+
* `event.key` 가 "ㅈ" 이 되므로 `event.code`(늘 `KeyW`)로 보는 편이 안전하다.
|
|
5230
|
+
*/
|
|
5231
|
+
shortcut?: string;
|
|
5216
5232
|
}
|
|
5217
5233
|
|
|
5218
5234
|
/** @public */
|
|
@@ -9,29 +9,35 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/* 코너 배치 — 중심이 부모 코너에서 살짝 안쪽(25%)으로 들어오게 얹힌다. */
|
|
12
|
+
/* 배지를 키우려면 `--shortcut-badge-scale` 을 부모에 얹는다.
|
|
13
|
+
**transform 을 따로 걸면 안 된다** — 자리를 잡는 translate 와 같은 속성이라
|
|
14
|
+
뒤에 오는 쪽이 앞의 것을 통째로 지운다. 실제로 그 사고가 났다: SoundCompass 가
|
|
15
|
+
배지에 scale 을 따로 걸었더니 anchor 의 translate 가 사라져 배지가 모서리에
|
|
16
|
+
걸치지 않고 버튼 안으로 들어왔다(실측: 걸침 +6px → -3px). 그래서 배율을
|
|
17
|
+
변수로 받아 **여기 한 곳에서** 합쳐 적는다. */
|
|
12
18
|
.shortcut-badge--top-left {
|
|
13
19
|
position: absolute;
|
|
14
20
|
left: 0;
|
|
15
21
|
top: 0;
|
|
16
|
-
transform: translate(-25%, -25%);
|
|
22
|
+
transform: translate(-25%, -25%) scale(var(--shortcut-badge-scale, 1));
|
|
17
23
|
}
|
|
18
24
|
.shortcut-badge--top-right {
|
|
19
25
|
position: absolute;
|
|
20
26
|
right: 0;
|
|
21
27
|
top: 0;
|
|
22
|
-
transform: translate(25%, -25%);
|
|
28
|
+
transform: translate(25%, -25%) scale(var(--shortcut-badge-scale, 1));
|
|
23
29
|
}
|
|
24
30
|
.shortcut-badge--bottom-left {
|
|
25
31
|
position: absolute;
|
|
26
32
|
left: 0;
|
|
27
33
|
bottom: 0;
|
|
28
|
-
transform: translate(-25%, 25%);
|
|
34
|
+
transform: translate(-25%, 25%) scale(var(--shortcut-badge-scale, 1));
|
|
29
35
|
}
|
|
30
36
|
.shortcut-badge--bottom-right {
|
|
31
37
|
position: absolute;
|
|
32
38
|
right: 0;
|
|
33
39
|
bottom: 0;
|
|
34
|
-
transform: translate(25%, 25%);
|
|
40
|
+
transform: translate(25%, 25%) scale(var(--shortcut-badge-scale, 1));
|
|
35
41
|
}
|
|
36
42
|
/* static — 흐름 안에 배치(별도 규칙 없음, position: static 기본). */
|
|
37
43
|
|
|
@@ -156,6 +156,16 @@
|
|
|
156
156
|
outline-offset: 2px;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/* 자판 키 배지 — 눌러야 하는 것은 버튼이므로 클릭을 가로채지 않는다.
|
|
160
|
+
상태가 바뀐 스피커가 다른 스피커 위로 올라가도(z-index 10) 배지는 제 버튼
|
|
161
|
+
위에 남아야 하므로 버튼(z-index 1)보다 한 겹 위에 둔다. */
|
|
162
|
+
.sound-compass__speaker-shortcut {
|
|
163
|
+
pointer-events: none;
|
|
164
|
+
z-index: 2;
|
|
165
|
+
/* 배율은 `--shortcut-badge-scale` 로 넘긴다. 여기서 transform 을 걸면 배지가
|
|
166
|
+
자리를 잡는 translate 를 지운다 — 같은 속성이라 뒤에 오는 쪽이 이긴다. */
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
.sound-compass__speaker-pos {
|
|
160
170
|
position: absolute;
|
|
161
171
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ShortcutBadgeAnchor } from './ShortcutBadge';
|
|
1
2
|
import type { SpeakerPos } from '../types/audio';
|
|
2
3
|
import './SoundCompass.css';
|
|
3
4
|
/** @public */
|
|
@@ -37,6 +38,12 @@ export interface SoundCompassProps {
|
|
|
37
38
|
buttonSize?: number;
|
|
38
39
|
/** 스피커 버튼에 아이콘을 표시할지 여부. false면 숫자만 가운데 표시. 기본 true */
|
|
39
40
|
showIcon?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* `speakers[].shortcut` 배지가 버튼의 어느 모서리에 붙을지. 기본 우상단.
|
|
43
|
+
*
|
|
44
|
+
* 배지 전부에 같이 걸린다 — 스피커마다 다른 자리에 두면 눈이 규칙을 못 잡는다.
|
|
45
|
+
*/
|
|
46
|
+
shortcutAnchor?: ShortcutBadgeAnchor;
|
|
40
47
|
}
|
|
41
48
|
/** @public */
|
|
42
|
-
export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, clickable, onSelectSpeaker, buttonSize, showIcon, }: SoundCompassProps): import("react").JSX.Element;
|
|
49
|
+
export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, }: SoundCompassProps): import("react").JSX.Element;
|
|
@@ -10,10 +10,11 @@ import Check from 'lucide-react/dist/esm/icons/check.mjs';
|
|
|
10
10
|
import Volume2 from 'lucide-react/dist/esm/icons/volume-2.mjs';
|
|
11
11
|
import User from 'lucide-react/dist/esm/icons/user.mjs';
|
|
12
12
|
import X from 'lucide-react/dist/esm/icons/x.mjs';
|
|
13
|
+
import ShortcutBadge from './ShortcutBadge';
|
|
13
14
|
import './SoundCompass.css';
|
|
14
15
|
const lerp = (a, b, t) => a + (b - a) * t;
|
|
15
16
|
/** @public */
|
|
16
|
-
export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSpeakerId = null, playedIds, selectedId = null, answerId = null, showFeedback = false, clickable = false, onSelectSpeaker, buttonSize = 40, showIcon = true, }) {
|
|
17
|
+
export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSpeakerId = null, playedIds, selectedId = null, answerId = null, showFeedback = false, clickable = false, onSelectSpeaker, buttonSize = 40, showIcon = true, shortcutAnchor = 'top-right', }) {
|
|
17
18
|
const t = Math.max(0, Math.min(1, viewT));
|
|
18
19
|
const CENTER = size / 2;
|
|
19
20
|
// ── 탑뷰 좌표 ──────────────────────────────────────────────────────────
|
|
@@ -151,6 +152,18 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
151
152
|
const state = getSpeakerState(sp);
|
|
152
153
|
const pulse = shouldPulse(sp);
|
|
153
154
|
const baseStyle = {
|
|
155
|
+
// 배지는 24px 고정인데 버튼은 크게 오간다. 그대로 두면 버튼 대비 비율이
|
|
156
|
+
// 세 배씩 흔들려, 작은 버튼에서는 배지가 버튼을 덮고 큰 버튼에서는
|
|
157
|
+
// 잃어버린다(실측). 버튼과 함께 키운다 — 24/56 이라 묶이지 않는 구간에서는
|
|
158
|
+
// 비율이 0.43 으로 **일정하다**.
|
|
159
|
+
//
|
|
160
|
+
// 상한은 3 이다. 처음엔 1.6 이었는데 buttonSize 89.6px 부터 붙어버려,
|
|
161
|
+
// 104~168px 을 쓰는 소비 앱에서 배지가 0.37 → 0.23 으로 다시 작아졌다.
|
|
162
|
+
// 3 이면 168px 까지 묶이지 않는다. 하한 0.75 는 배지가 읽히는 크기다.
|
|
163
|
+
//
|
|
164
|
+
// 배율은 transform 이 아니라 변수로 넘긴다 — 배지의 anchor 가 같은 속성에
|
|
165
|
+
// translate 를 걸고 있어, 여기서 transform 을 걸면 그것을 지운다.
|
|
166
|
+
['--shortcut-badge-scale']: String(Math.max(0.75, Math.min(3, buttonSize / 56))),
|
|
154
167
|
pointerEvents: (clickable ? 'auto' : 'none'),
|
|
155
168
|
cursor: clickable ? 'pointer' : 'default',
|
|
156
169
|
// 호출된(=idle이 아닌) 스피커를 다른 스피커 위로 끌어올렸다가, 초기화되면 다시 낮춘다
|
|
@@ -162,6 +175,6 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
162
175
|
const iconSize = Math.round(Math.max(10, Math.min(56, BTN * 0.38)));
|
|
163
176
|
const numberSize = Math.round(Math.max(7, Math.min(18, BTN * 0.2)));
|
|
164
177
|
const numberOnlySize = Math.round(Math.max(10, Math.min(56, BTN * 0.4)));
|
|
165
|
-
return (_jsxs("div", { className: "sound-compass__speaker-pos", style: { ...baseStyle, left: x - BTN / 2, top: y - BTN / 2 }, children: [pulse && _jsx("div", { className: "sound-compass__pulse", style: { width: BTN, height: BTN } }), _jsxs("button", { type: "button", tabIndex: clickable ? 0 : -1, "aria-disabled": clickable ? undefined : true, onClick: clickable ? () => onSelectSpeaker?.(sp.id) : undefined, className: `sound-compass__speaker-btn sound-compass__speaker-btn--${state}`, style: { width: BTN, height: BTN }, "aria-label": sp.label, children: [showIcon && getStateIcon(state, iconSize), _jsx("span", { className: "sound-compass__speaker-number", style: { fontSize: showIcon ? numberSize : numberOnlySize }, children: idx + 1 })] })] }, sp.id));
|
|
178
|
+
return (_jsxs("div", { className: "sound-compass__speaker-pos", style: { ...baseStyle, left: x - BTN / 2, top: y - BTN / 2 }, children: [pulse && _jsx("div", { className: "sound-compass__pulse", style: { width: BTN, height: BTN } }), _jsxs("button", { type: "button", tabIndex: clickable ? 0 : -1, "aria-disabled": clickable ? undefined : true, onClick: clickable ? () => onSelectSpeaker?.(sp.id) : undefined, className: `sound-compass__speaker-btn sound-compass__speaker-btn--${state}`, style: { width: BTN, height: BTN }, "aria-label": sp.label, children: [showIcon && getStateIcon(state, iconSize), _jsx("span", { className: "sound-compass__speaker-number", style: { fontSize: showIcon ? numberSize : numberOnlySize }, children: idx + 1 })] }), sp.shortcut && (_jsx(ShortcutBadge, { keys: sp.shortcut, anchor: shortcutAnchor, className: "sound-compass__speaker-shortcut" }))] }, sp.id));
|
|
166
179
|
})] }) }));
|
|
167
180
|
}
|
|
@@ -8,4 +8,14 @@ export interface SpeakerPos {
|
|
|
8
8
|
deg: number;
|
|
9
9
|
/** 동심원 링 번호 (1=가장 안쪽, 기본값 1) */
|
|
10
10
|
row?: number;
|
|
11
|
+
/**
|
|
12
|
+
* 이 스피커에 걸린 자판 키. 주면 버튼 모서리에 `ShortcutBadge` 가 얹힌다.
|
|
13
|
+
*
|
|
14
|
+
* 키가 모자란 스피커에는 주지 않으면 된다 — 배지도 안 붙는다(예: 27개 배치의
|
|
15
|
+
* 가운데 층은 13개인데 자판 줄은 9칸이라 넷은 키가 없다).
|
|
16
|
+
*
|
|
17
|
+
* 무엇을 눌렀는지 판정하는 일은 부르는 쪽 몫이다. 한글 입력 상태에서는
|
|
18
|
+
* `event.key` 가 "ㅈ" 이 되므로 `event.code`(늘 `KeyW`)로 보는 편이 안전하다.
|
|
19
|
+
*/
|
|
20
|
+
shortcut?: string;
|
|
11
21
|
}
|