@ihabdevteam/core 0.17.0 → 0.18.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/dist/components.cjs.js +165 -165
- package/dist/components.esm.css +1 -1
- package/dist/ihabdevteam-core.cjs.js +17 -17
- package/dist/ihabdevteam-core.d.ts +15 -1
- package/dist/packages/core/src/components/SoundCompass.css +5 -0
- package/dist/packages/core/src/components/SoundCompass.d.ts +15 -1
- package/dist/packages/core/src/components/SoundCompass.js +14 -5
- package/package.json +1 -1
|
@@ -5119,7 +5119,7 @@ export declare interface SliderProps {
|
|
|
5119
5119
|
export declare function sortToValue(key: string, asc: boolean): string | undefined;
|
|
5120
5120
|
|
|
5121
5121
|
/** @public */
|
|
5122
|
-
export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): JSX.Element;
|
|
5122
|
+
export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): JSX.Element;
|
|
5123
5123
|
|
|
5124
5124
|
export declare interface SoundCompassExam {
|
|
5125
5125
|
/** `<SoundCompass {...bind} />` 로 펼쳐 넣는 제어 props */
|
|
@@ -5186,6 +5186,20 @@ export declare interface SoundCompassProps {
|
|
|
5186
5186
|
answerId?: string | null;
|
|
5187
5187
|
/** 정답/오답을 시각적으로 표시할지 여부. 기본 false */
|
|
5188
5188
|
showFeedback?: boolean;
|
|
5189
|
+
/**
|
|
5190
|
+
* 고른 스피커(`selectedId`)에도 울림을 붙일지. 기본 꺼짐.
|
|
5191
|
+
*
|
|
5192
|
+
* 울림은 원래 **소리가 나는 스피커**(`activeSpeakerId`)와 **피드백의 정답**에만
|
|
5193
|
+
* 붙는다. 정답을 알려주지 않는 검사에서는 그 둘을 쓰지 않으므로 울림이 한 번도
|
|
5194
|
+
* 안 난다 — 고른 자리에 손맛이 없다.
|
|
5195
|
+
*
|
|
5196
|
+
* 피드백을 보이는 동안에는 걸리지 않는다. 그때는 정답이 이미 울리고 있어서,
|
|
5197
|
+
* 고른 것까지 울리면 무엇을 가리키는 울림인지 흐려진다.
|
|
5198
|
+
*
|
|
5199
|
+
* 이 울림은 **한 번만** 번진다. 소리가 나는 동안 계속 알리는 것과 달리, 선택은
|
|
5200
|
+
* 일어난 순간을 알리는 것이라 되풀이할 까닭이 없다.
|
|
5201
|
+
*/
|
|
5202
|
+
pulseSelected?: boolean;
|
|
5189
5203
|
/** 스피커 클릭 가능 여부. 기본 false */
|
|
5190
5204
|
clickable?: boolean;
|
|
5191
5205
|
/** 스피커 선택 시 호출 */
|
|
@@ -136,6 +136,11 @@
|
|
|
136
136
|
pointer-events: none;
|
|
137
137
|
animation: sound-compass-pulse 1s cubic-bezier(0.33, 1, 0.68, 1) infinite;
|
|
138
138
|
}
|
|
139
|
+
/* 고른 순간을 알리는 울림은 한 번만 번진다. 마지막 프레임이 opacity 0 이라
|
|
140
|
+
되풀이를 끊는 것만으로 그대로 사라진다. */
|
|
141
|
+
.sound-compass__pulse--once {
|
|
142
|
+
animation-iteration-count: 1;
|
|
143
|
+
}
|
|
139
144
|
|
|
140
145
|
/* 스피커 버튼 wrap의 정적 속성만 분리(나머지는 상태·좌표 기반 동적 값이라 인라인 유지) */
|
|
141
146
|
/* 호버·포커스 — `all: unset` 이 브라우저 기본 상태까지 지워, 누를 수 있는 자리인지
|
|
@@ -30,6 +30,20 @@ export interface SoundCompassProps {
|
|
|
30
30
|
answerId?: string | null;
|
|
31
31
|
/** 정답/오답을 시각적으로 표시할지 여부. 기본 false */
|
|
32
32
|
showFeedback?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* 고른 스피커(`selectedId`)에도 울림을 붙일지. 기본 꺼짐.
|
|
35
|
+
*
|
|
36
|
+
* 울림은 원래 **소리가 나는 스피커**(`activeSpeakerId`)와 **피드백의 정답**에만
|
|
37
|
+
* 붙는다. 정답을 알려주지 않는 검사에서는 그 둘을 쓰지 않으므로 울림이 한 번도
|
|
38
|
+
* 안 난다 — 고른 자리에 손맛이 없다.
|
|
39
|
+
*
|
|
40
|
+
* 피드백을 보이는 동안에는 걸리지 않는다. 그때는 정답이 이미 울리고 있어서,
|
|
41
|
+
* 고른 것까지 울리면 무엇을 가리키는 울림인지 흐려진다.
|
|
42
|
+
*
|
|
43
|
+
* 이 울림은 **한 번만** 번진다. 소리가 나는 동안 계속 알리는 것과 달리, 선택은
|
|
44
|
+
* 일어난 순간을 알리는 것이라 되풀이할 까닭이 없다.
|
|
45
|
+
*/
|
|
46
|
+
pulseSelected?: boolean;
|
|
33
47
|
/** 스피커 클릭 가능 여부. 기본 false */
|
|
34
48
|
clickable?: boolean;
|
|
35
49
|
/** 스피커 선택 시 호출 */
|
|
@@ -65,4 +79,4 @@ export interface SoundCompassProps {
|
|
|
65
79
|
scaleShortcut?: boolean;
|
|
66
80
|
}
|
|
67
81
|
/** @public */
|
|
68
|
-
export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): import("react").JSX.Element;
|
|
82
|
+
export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): import("react").JSX.Element;
|
|
@@ -14,7 +14,7 @@ import ShortcutBadge from './ShortcutBadge';
|
|
|
14
14
|
import './SoundCompass.css';
|
|
15
15
|
const lerp = (a, b, t) => a + (b - a) * t;
|
|
16
16
|
/** @public */
|
|
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', shortcutScale, scaleShortcut = false, }) {
|
|
17
|
+
export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSpeakerId = null, playedIds, selectedId = null, answerId = null, showFeedback = false, pulseSelected = false, clickable = false, onSelectSpeaker, buttonSize = 40, showIcon = true, shortcutAnchor = 'top-right', shortcutScale, scaleShortcut = false, }) {
|
|
18
18
|
const t = Math.max(0, Math.min(1, viewT));
|
|
19
19
|
const CENTER = size / 2;
|
|
20
20
|
// ── 탑뷰 좌표 ──────────────────────────────────────────────────────────
|
|
@@ -132,8 +132,17 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
132
132
|
return 'played';
|
|
133
133
|
return 'idle';
|
|
134
134
|
};
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
/**
|
|
136
|
+
* 울림을 어떤 까닭으로 다는지. 까닭에 따라 되풀이가 다르다 — 소리가 나는 동안은
|
|
137
|
+
* 계속(`loop`), 고른 순간은 한 번(`once`).
|
|
138
|
+
*/
|
|
139
|
+
const pulseKind = (sp) => {
|
|
140
|
+
if (activeSpeakerId === sp.id)
|
|
141
|
+
return 'loop';
|
|
142
|
+
if (showFeedback)
|
|
143
|
+
return sp.id === answerId ? 'loop' : null;
|
|
144
|
+
return pulseSelected && sp.id === selectedId ? 'once' : null;
|
|
145
|
+
};
|
|
137
146
|
// SpeakerState → 아이콘. 색상/배경은 Button에 기대지 않고 SoundCompass.css의
|
|
138
147
|
// .sound-compass__speaker-btn--{state} 클래스가 전담해, 자유롭게 바꿀 수 있다.
|
|
139
148
|
const getStateIcon = (state, iconSize) => {
|
|
@@ -150,7 +159,7 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
150
159
|
}), rowArcPaths.map(a => (_jsx("path", { d: a.d, fill: "none", stroke: "#dde3ec", strokeWidth: "1" }, a.key)))] }), _jsx("div", { className: "sound-compass__center", style: { width: centerSize, height: centerSize, left: personPos.x, top: personPos.y }, children: _jsx(User, { size: centerIconSize, color: "currentColor", strokeWidth: 1.5 }) }), speakers.map((sp, idx) => {
|
|
151
160
|
const { x, y } = getSpeakerXY(sp);
|
|
152
161
|
const state = getSpeakerState(sp);
|
|
153
|
-
const pulse =
|
|
162
|
+
const pulse = pulseKind(sp);
|
|
154
163
|
const baseStyle = {
|
|
155
164
|
// 배지는 24px 고정인데 버튼은 크게 오간다. 그대로 두면 버튼 대비 비율이
|
|
156
165
|
// 세 배씩 흔들려, 작은 버튼에서는 배지가 버튼을 덮고 큰 버튼에서는
|
|
@@ -183,6 +192,6 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
183
192
|
const iconSize = Math.round(Math.max(10, Math.min(56, BTN * 0.38)));
|
|
184
193
|
const numberSize = Math.round(Math.max(7, Math.min(18, BTN * 0.2)));
|
|
185
194
|
const numberOnlySize = Math.round(Math.max(10, Math.min(56, BTN * 0.4)));
|
|
186
|
-
return (_jsxs("div", { className: "sound-compass__speaker-pos", style: { ...baseStyle, left: x - BTN / 2, top: y - BTN / 2 }, children: [pulse && _jsx("div", { className:
|
|
195
|
+
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${pulse === 'once' ? ' sound-compass__pulse--once' : ''}`, 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));
|
|
187
196
|
})] }) }));
|
|
188
197
|
}
|