@ihabdevteam/core 0.20.0 → 0.22.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 +170 -170
- package/dist/ihabdevteam-core.d.ts +37 -2
- package/dist/packages/core/src/components/SoundCompass.css +64 -6
- package/dist/packages/core/src/components/SoundCompass.d.ts +37 -2
- package/dist/packages/core/src/components/SoundCompass.js +10 -3
- 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, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): JSX.Element;
|
|
5122
|
+
export declare function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, onCenterClick, centerLabel, centerDisabled, centerAttention, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): JSX.Element;
|
|
5123
5123
|
|
|
5124
5124
|
export declare interface SoundCompassExam {
|
|
5125
5125
|
/** `<SoundCompass {...bind} />` 로 펼쳐 넣는 제어 props */
|
|
@@ -5230,7 +5230,42 @@ export declare interface SoundCompassProps {
|
|
|
5230
5230
|
/** 스피커 버튼 크기(px). 기본 40 */
|
|
5231
5231
|
buttonSize?: number;
|
|
5232
5232
|
/** 스피커 버튼에 아이콘을 표시할지 여부. false면 숫자만 가운데 표시. 기본 true */
|
|
5233
|
-
|
|
5233
|
+
/**
|
|
5234
|
+
* 버튼 안에 무엇을 그릴지.
|
|
5235
|
+
*
|
|
5236
|
+
* - `true` — 아이콘과 번호를 함께 (기본)
|
|
5237
|
+
* - `false` — 번호만
|
|
5238
|
+
* - `'answered'` — **답이 정해진 상태에서만 아이콘으로 갈아 끼운다.** 그때는
|
|
5239
|
+
* 번호를 그리지 않는다. 나머지(`idle`·`active`)는 `false` 와 같이 번호만이다.
|
|
5240
|
+
*
|
|
5241
|
+
* `'answered'` 가 가르는 선은 자의적이지 않다. `idle` 과 `active` 는 아이콘이
|
|
5242
|
+
* 둘 다 같은 `Volume2` 라 번호보다 더 말해 주는 것이 없다. `played`·`correct` 의
|
|
5243
|
+
* 체크와 `wrong` 의 가위표만이 번호가 못 하는 말을 한다 — 아이콘이 자리를
|
|
5244
|
+
* 차지할 값을 하는 자리만 바꿔 끼운다.
|
|
5245
|
+
*
|
|
5246
|
+
* 스피커를 번호로 부르는 화면에서 필요하다. `true` 로 켜면 고르지 않은 스피커까지
|
|
5247
|
+
* 아이콘이 붙고 번호가 함께 작아져(실측: 36px → 18px) 번호로 부르는 화면이 무너진다.
|
|
5248
|
+
*/
|
|
5249
|
+
showIcon?: boolean | 'answered';
|
|
5250
|
+
/**
|
|
5251
|
+
* 가운데(피험자 자리)를 눌렀을 때. **주면 그 자리가 진짜 `<button>` 이 된다** —
|
|
5252
|
+
* 초점이 닿고 Enter·Space 로 눌리고 포커스 링이 뜬다.
|
|
5253
|
+
*
|
|
5254
|
+
* 주지 않으면 예전 그대로 그림일 뿐이다(`<div>` · `pointer-events: none`).
|
|
5255
|
+
* 소리를 듣는 단추를 그 자리에 두면 자리로도 말이 된다 — 소리는 그 사람을
|
|
5256
|
+
* 향해 나온다.
|
|
5257
|
+
*/
|
|
5258
|
+
onCenterClick?: () => void;
|
|
5259
|
+
/** 가운데 단추의 이름표. `onCenterClick` 을 줄 때만 쓰인다. */
|
|
5260
|
+
centerLabel?: string;
|
|
5261
|
+
/** 가운데 단추를 못 누르게. 재생 중처럼 지금은 누를 때가 아닌 자리에 쓴다. */
|
|
5262
|
+
centerDisabled?: boolean;
|
|
5263
|
+
/**
|
|
5264
|
+
* 가운데 단추에 눈을 끄는 울림을 건다. 스피커 재생 울림과 같은 그림·같은 색이라
|
|
5265
|
+
* 화면이 한 벌로 읽힌다. 못 누를 때(`centerDisabled`)는 걸리지 않는다 —
|
|
5266
|
+
* 누를 수 없는 자리로 눈을 끌면 안 된다.
|
|
5267
|
+
*/
|
|
5268
|
+
centerAttention?: boolean;
|
|
5234
5269
|
/**
|
|
5235
5270
|
* `speakers[].shortcut` 배지가 버튼의 어느 모서리에 붙을지. 기본 우상단.
|
|
5236
5271
|
*
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
/* 상태 색은 **뿌리에** 둔다. 예전에는 버튼 안에 선언돼 있었는데, 울림(pulse)은
|
|
13
13
|
버튼의 형제라 그 변수를 못 봤다 — 울림 색을 상태에 묶자마자 투명하게 나왔다
|
|
14
|
-
(실측: rgba(0,0,0,0)). 버튼과 울림이 함께 보려면 공통 조상에 있어야 한다.
|
|
14
|
+
(실측: rgba(0,0,0,0)). 버튼과 울림이 함께 보려면 공통 조상에 있어야 한다.
|
|
15
|
+
|
|
16
|
+
**덮어쓸 때도 여기나 그 위에 얹어야 한다.** `.sound-compass__speaker-btn` 같은
|
|
17
|
+
버튼 선택자 위에 얹으면 버튼만 바뀌고 울림은 안 따라온다 — 그 자리에서는
|
|
18
|
+
조용히 어긋나므로, 색이 갈려 보이면 이것부터 보라. */
|
|
15
19
|
--scb-idle-bg: var(--color-deepWhite);
|
|
16
20
|
--scb-idle-border: var(--color-whiteLine);
|
|
17
21
|
--scb-idle-fg: var(--color-lightGray);
|
|
@@ -29,6 +33,20 @@
|
|
|
29
33
|
--scb-idle-hover-border: var(--color-lightGray);
|
|
30
34
|
/* 호버의 글자색. 기본은 idle 과 같다 — 주지 않으면 예전 그대로다. */
|
|
31
35
|
--scb-idle-hover-fg: var(--scb-idle-fg);
|
|
36
|
+
--scb-focus-ring: var(--color-main);
|
|
37
|
+
|
|
38
|
+
/* 가운데 단추 색 — 스피커 버튼(--scb-*)과 같은 결로 연다. 기본값은 지금 그림
|
|
39
|
+
그대로라, 단추로 쓰지 않는 소비자에게는 변화가 없다. */
|
|
40
|
+
--scc-bg: var(--color-white);
|
|
41
|
+
--scc-border: var(--color-whiteLine);
|
|
42
|
+
--scc-fg: var(--color-lightGray);
|
|
43
|
+
--scc-hover-bg: var(--color-deepWhite);
|
|
44
|
+
--scc-hover-border: var(--color-lightGray);
|
|
45
|
+
--scc-hover-fg: var(--color-darkGray);
|
|
46
|
+
/* 못 누를 때. 눌러야 하는 자리인지 한눈에 갈려야 한다. */
|
|
47
|
+
--scc-disabled-bg: var(--color-deepWhite);
|
|
48
|
+
--scc-disabled-border: var(--color-whiteLine);
|
|
49
|
+
--scc-disabled-fg: var(--color-whiteLine);
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
.sound-compass__status {
|
|
@@ -63,8 +81,6 @@
|
|
|
63
81
|
border-width: 1px;
|
|
64
82
|
border-style: solid;
|
|
65
83
|
transition: background-color .15s, border-color .15s, color .15s;
|
|
66
|
-
|
|
67
|
-
--scb-focus-ring: var(--color-main);
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
.sound-compass__speaker-btn--idle {
|
|
@@ -114,13 +130,13 @@
|
|
|
114
130
|
left: 50%;
|
|
115
131
|
transform: translate(-50%, -50%);
|
|
116
132
|
pointer-events: none;
|
|
117
|
-
background: var(--
|
|
118
|
-
border: 1px solid var(--
|
|
133
|
+
background: var(--scc-bg);
|
|
134
|
+
border: 1px solid var(--scc-border);
|
|
119
135
|
border-radius: 50%;
|
|
120
136
|
display: flex;
|
|
121
137
|
align-items: center;
|
|
122
138
|
justify-content: center;
|
|
123
|
-
color: var(--
|
|
139
|
+
color: var(--scc-fg);
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
/* ── Button 모드 전용 스타일 (speakerType='button') ── */
|
|
@@ -134,6 +150,48 @@
|
|
|
134
150
|
100% { transform: scale(var(--sc-pulse-scale, 2.8)); opacity: 0; }
|
|
135
151
|
}
|
|
136
152
|
|
|
153
|
+
/* 가운데를 단추로 쓸 때. `onCenterClick` 을 준 자리에만 붙는다 — 안 주면 위의
|
|
154
|
+
그림 그대로이고 pointer-events 도 none 이라 변화가 없다.
|
|
155
|
+
|
|
156
|
+
`<button>` 의 기본 모양만 걷어낸다. 위 규칙이 이미 배치·색을 다 정하므로
|
|
157
|
+
`all: unset` 까지 갈 까닭이 없다 — 그러면 position 까지 지워져 자리가 무너진다. */
|
|
158
|
+
.sound-compass__center--button {
|
|
159
|
+
appearance: none;
|
|
160
|
+
font: inherit;
|
|
161
|
+
padding: 0;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
pointer-events: auto;
|
|
164
|
+
transition: background-color .15s, border-color .15s, color .15s;
|
|
165
|
+
}
|
|
166
|
+
.sound-compass__center--button:hover {
|
|
167
|
+
background: var(--scc-hover-bg);
|
|
168
|
+
border-color: var(--scc-hover-border);
|
|
169
|
+
color: var(--scc-hover-fg);
|
|
170
|
+
}
|
|
171
|
+
/* 스피커 버튼과 같은 링을 쓴다 — 화면 안에서 초점이 한 가지 모양으로 읽히게. */
|
|
172
|
+
.sound-compass__center--button:focus-visible {
|
|
173
|
+
outline: 2px solid var(--scb-focus-ring);
|
|
174
|
+
outline-offset: 2px;
|
|
175
|
+
}
|
|
176
|
+
/* 못 누를 때는 눌러도 안 되는 것이 눈으로 보여야 한다. */
|
|
177
|
+
.sound-compass__center--disabled {
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
cursor: default;
|
|
180
|
+
background: var(--scc-disabled-bg);
|
|
181
|
+
border-color: var(--scc-disabled-border);
|
|
182
|
+
color: var(--scc-disabled-fg);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* 가운데 울림은 그 자리에 겹쳐 놓는다 — 가운데 원과 같은 크기·같은 자리에서
|
|
186
|
+
번져야 한 몸으로 보인다. 뒤에 깔리도록 단추보다 낮게 둔다.
|
|
187
|
+
|
|
188
|
+
**가운데 맞춤을 transform 으로 하지 않는다.** 이 원소는 keyframe 이 transform 에
|
|
189
|
+
scale 을 거는 자리라, translate 를 함께 걸면 애니메이션이 도는 동안 그것이 통째로
|
|
190
|
+
날아간다 — 배지에서 이미 겪은 그 충돌이다. 좌표를 미리 빼서 얹는다. */
|
|
191
|
+
.sound-compass__pulse--center {
|
|
192
|
+
z-index: 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
137
195
|
.sound-compass__pulse {
|
|
138
196
|
position: absolute;
|
|
139
197
|
top: 0; left: 0;
|
|
@@ -74,7 +74,42 @@ export interface SoundCompassProps {
|
|
|
74
74
|
/** 스피커 버튼 크기(px). 기본 40 */
|
|
75
75
|
buttonSize?: number;
|
|
76
76
|
/** 스피커 버튼에 아이콘을 표시할지 여부. false면 숫자만 가운데 표시. 기본 true */
|
|
77
|
-
|
|
77
|
+
/**
|
|
78
|
+
* 버튼 안에 무엇을 그릴지.
|
|
79
|
+
*
|
|
80
|
+
* - `true` — 아이콘과 번호를 함께 (기본)
|
|
81
|
+
* - `false` — 번호만
|
|
82
|
+
* - `'answered'` — **답이 정해진 상태에서만 아이콘으로 갈아 끼운다.** 그때는
|
|
83
|
+
* 번호를 그리지 않는다. 나머지(`idle`·`active`)는 `false` 와 같이 번호만이다.
|
|
84
|
+
*
|
|
85
|
+
* `'answered'` 가 가르는 선은 자의적이지 않다. `idle` 과 `active` 는 아이콘이
|
|
86
|
+
* 둘 다 같은 `Volume2` 라 번호보다 더 말해 주는 것이 없다. `played`·`correct` 의
|
|
87
|
+
* 체크와 `wrong` 의 가위표만이 번호가 못 하는 말을 한다 — 아이콘이 자리를
|
|
88
|
+
* 차지할 값을 하는 자리만 바꿔 끼운다.
|
|
89
|
+
*
|
|
90
|
+
* 스피커를 번호로 부르는 화면에서 필요하다. `true` 로 켜면 고르지 않은 스피커까지
|
|
91
|
+
* 아이콘이 붙고 번호가 함께 작아져(실측: 36px → 18px) 번호로 부르는 화면이 무너진다.
|
|
92
|
+
*/
|
|
93
|
+
showIcon?: boolean | 'answered';
|
|
94
|
+
/**
|
|
95
|
+
* 가운데(피험자 자리)를 눌렀을 때. **주면 그 자리가 진짜 `<button>` 이 된다** —
|
|
96
|
+
* 초점이 닿고 Enter·Space 로 눌리고 포커스 링이 뜬다.
|
|
97
|
+
*
|
|
98
|
+
* 주지 않으면 예전 그대로 그림일 뿐이다(`<div>` · `pointer-events: none`).
|
|
99
|
+
* 소리를 듣는 단추를 그 자리에 두면 자리로도 말이 된다 — 소리는 그 사람을
|
|
100
|
+
* 향해 나온다.
|
|
101
|
+
*/
|
|
102
|
+
onCenterClick?: () => void;
|
|
103
|
+
/** 가운데 단추의 이름표. `onCenterClick` 을 줄 때만 쓰인다. */
|
|
104
|
+
centerLabel?: string;
|
|
105
|
+
/** 가운데 단추를 못 누르게. 재생 중처럼 지금은 누를 때가 아닌 자리에 쓴다. */
|
|
106
|
+
centerDisabled?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* 가운데 단추에 눈을 끄는 울림을 건다. 스피커 재생 울림과 같은 그림·같은 색이라
|
|
109
|
+
* 화면이 한 벌로 읽힌다. 못 누를 때(`centerDisabled`)는 걸리지 않는다 —
|
|
110
|
+
* 누를 수 없는 자리로 눈을 끌면 안 된다.
|
|
111
|
+
*/
|
|
112
|
+
centerAttention?: boolean;
|
|
78
113
|
/**
|
|
79
114
|
* `speakers[].shortcut` 배지가 버튼의 어느 모서리에 붙을지. 기본 우상단.
|
|
80
115
|
*
|
|
@@ -102,4 +137,4 @@ export interface SoundCompassProps {
|
|
|
102
137
|
scaleShortcut?: boolean;
|
|
103
138
|
}
|
|
104
139
|
/** @public */
|
|
105
|
-
export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, shortcutAnchor, shortcutScale, scaleShortcut, }: SoundCompassProps): import("react").JSX.Element;
|
|
140
|
+
export default function SoundCompass({ speakers, size, viewT, activeSpeakerId, playedIds, selectedId, answerId, showFeedback, pulseSelected, pulseSelectedDuration, pulseScale, clickable, onSelectSpeaker, buttonSize, showIcon, onCenterClick, centerLabel, centerDisabled, centerAttention, 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, pulseSelected = false, pulseSelectedDuration, pulseScale, 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, pulseSelectedDuration, pulseScale, clickable = false, onSelectSpeaker, buttonSize = 40, showIcon = true, onCenterClick, centerLabel, centerDisabled = false, centerAttention = false, 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
|
// ── 탑뷰 좌표 ──────────────────────────────────────────────────────────
|
|
@@ -156,7 +156,8 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
156
156
|
return (_jsx("div", { className: "sound-compass", children: _jsxs("div", { className: "sound-compass__compass-wrap", style: { width: size, height: size }, children: [_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "sound-compass__svg", children: [t === 0 && speakers.map(sp => {
|
|
157
157
|
const { x, y } = getSpeakerXY(sp);
|
|
158
158
|
return (_jsx("line", { x1: personPos.x, y1: personPos.y, x2: x, y2: y, stroke: "#dde3ec", strokeWidth: "1" }, sp.id));
|
|
159
|
-
}), rowArcPaths.map(a => (_jsx("path", { d: a.d, fill: "none", stroke: "#dde3ec", strokeWidth: "1" }, a.key)))] }), _jsx("div", { className: "sound-
|
|
159
|
+
}), rowArcPaths.map(a => (_jsx("path", { d: a.d, fill: "none", stroke: "#dde3ec", strokeWidth: "1" }, a.key)))] }), centerAttention && !centerDisabled && (_jsx("div", { className: "sound-compass__pulse sound-compass__pulse--active sound-compass__pulse--center", style: { width: centerSize, height: centerSize,
|
|
160
|
+
left: personPos.x - centerSize / 2, top: personPos.y - centerSize / 2 } })), onCenterClick ? (_jsx("button", { type: "button", className: `sound-compass__center sound-compass__center--button${centerDisabled ? ' sound-compass__center--disabled' : ''}`, style: { width: centerSize, height: centerSize, left: personPos.x, top: personPos.y }, "aria-label": centerLabel, "aria-disabled": centerDisabled || undefined, tabIndex: centerDisabled ? -1 : 0, onClick: centerDisabled ? undefined : onCenterClick, children: _jsx(User, { size: centerIconSize, color: "currentColor", strokeWidth: 1.5 }) })) : (_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) => {
|
|
160
161
|
const { x, y } = getSpeakerXY(sp);
|
|
161
162
|
const state = getSpeakerState(sp);
|
|
162
163
|
const pulse = pulseKind(sp);
|
|
@@ -192,6 +193,12 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
192
193
|
const iconSize = Math.round(Math.max(10, Math.min(56, BTN * 0.38)));
|
|
193
194
|
const numberSize = Math.round(Math.max(7, Math.min(18, BTN * 0.2)));
|
|
194
195
|
const numberOnlySize = Math.round(Math.max(10, Math.min(56, BTN * 0.4)));
|
|
196
|
+
// `'answered'` 는 답이 정해진 자리에서만 아이콘으로 **갈아 끼운다** —
|
|
197
|
+
// 아이콘과 번호를 같이 그리면 90px 버튼에서 둘 다 작아진다.
|
|
198
|
+
const swapped = showIcon === 'answered'
|
|
199
|
+
&& (state === 'played' || state === 'correct' || state === 'wrong');
|
|
200
|
+
const withIcon = showIcon === true || swapped;
|
|
201
|
+
const withNumber = !swapped;
|
|
195
202
|
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 sound-compass__pulse--${state}`
|
|
196
203
|
+ (pulse === 'once' ? ' sound-compass__pulse--once' : ''), style: {
|
|
197
204
|
width: BTN, height: BTN,
|
|
@@ -202,6 +209,6 @@ export default function SoundCompass({ speakers, size = 300, viewT = 0, activeSp
|
|
|
202
209
|
...(pulseScale !== undefined
|
|
203
210
|
? { ['--sc-pulse-scale']: String(pulseScale) }
|
|
204
211
|
: null),
|
|
205
|
-
} })), _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: [
|
|
212
|
+
} })), _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: [withIcon && getStateIcon(state, iconSize), withNumber && (_jsx("span", { className: "sound-compass__speaker-number", style: { fontSize: showIcon === true ? numberSize : numberOnlySize }, children: idx + 1 }))] }), sp.shortcut && (_jsx(ShortcutBadge, { keys: sp.shortcut, anchor: shortcutAnchor, className: "sound-compass__speaker-shortcut" }))] }, sp.id));
|
|
206
213
|
})] }) }));
|
|
207
214
|
}
|