@ncds/ui-admin 1.8.26-alpha.3 → 1.8.26-alpha.5
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/cjs/assets/scripts/notification/FloatingNotification.js +23 -7
- package/dist/cjs/assets/scripts/notification/FullWidthNotification.js +14 -3
- package/dist/cjs/assets/scripts/notification/MessageNotification.js +3 -0
- package/dist/cjs/assets/scripts/notification/Notification.js +8 -2
- package/dist/cjs/assets/scripts/notification/messageTemplate.js +21 -5
- package/dist/cjs/constant/notification.js +8 -2
- package/dist/cjs/src/components/overlays/notification/FloatingNotification.js +10 -28
- package/dist/cjs/src/components/overlays/notification/FullWidthNotification.js +9 -24
- package/dist/cjs/src/components/overlays/notification/MessageNotification.js +12 -0
- package/dist/cjs/src/components/overlays/notification/MessageNotificationParts.js +5 -2
- package/dist/cjs/src/components/overlays/notification/Notification.js +1 -0
- package/dist/cjs/src/components/overlays/notification/__tests__/CalloutNotification.test.js +8 -2
- package/dist/cjs/src/components/overlays/notification/__tests__/Notification.test.js +176 -0
- package/dist/cjs/src/components/overlays/notification/useAutoClose.js +39 -0
- package/dist/cjs/src/utils/notification/__tests__/resolveAutoClose.test.js +44 -0
- package/dist/cjs/src/utils/notification/index.js +12 -0
- package/dist/cjs/src/utils/notification/resolveAutoClose.js +22 -0
- package/dist/esm/assets/scripts/notification/FloatingNotification.js +23 -7
- package/dist/esm/assets/scripts/notification/FullWidthNotification.js +14 -3
- package/dist/esm/assets/scripts/notification/MessageNotification.js +3 -0
- package/dist/esm/assets/scripts/notification/Notification.js +8 -2
- package/dist/esm/assets/scripts/notification/messageTemplate.js +21 -5
- package/dist/esm/constant/notification.js +7 -1
- package/dist/esm/src/components/overlays/notification/FloatingNotification.js +11 -29
- package/dist/esm/src/components/overlays/notification/FullWidthNotification.js +10 -25
- package/dist/esm/src/components/overlays/notification/MessageNotification.js +12 -0
- package/dist/esm/src/components/overlays/notification/MessageNotificationParts.js +5 -2
- package/dist/esm/src/components/overlays/notification/Notification.js +1 -0
- package/dist/esm/src/components/overlays/notification/__tests__/CalloutNotification.test.js +9 -3
- package/dist/esm/src/components/overlays/notification/__tests__/Notification.test.js +177 -1
- package/dist/esm/src/components/overlays/notification/useAutoClose.js +32 -0
- package/dist/esm/src/utils/notification/__tests__/resolveAutoClose.test.js +42 -0
- package/dist/esm/src/utils/notification/index.js +1 -0
- package/dist/esm/src/utils/notification/resolveAutoClose.js +15 -0
- package/dist/temp/assets/scripts/notification/FloatingNotification.js +10 -6
- package/dist/temp/assets/scripts/notification/FullWidthNotification.js +8 -5
- package/dist/temp/assets/scripts/notification/MessageNotification.js +3 -1
- package/dist/temp/assets/scripts/notification/Notification.js +7 -1
- package/dist/temp/assets/scripts/notification/const/types.d.ts +7 -0
- package/dist/temp/assets/scripts/notification/messageTemplate.d.ts +1 -0
- package/dist/temp/assets/scripts/notification/messageTemplate.js +19 -9
- package/dist/temp/constant/notification.d.ts +6 -0
- package/dist/temp/constant/notification.js +6 -0
- package/dist/temp/src/components/overlays/notification/FloatingNotification.d.ts +8 -0
- package/dist/temp/src/components/overlays/notification/FloatingNotification.js +6 -29
- package/dist/temp/src/components/overlays/notification/FullWidthNotification.d.ts +7 -0
- package/dist/temp/src/components/overlays/notification/FullWidthNotification.js +6 -25
- package/dist/temp/src/components/overlays/notification/MessageNotification.d.ts +12 -0
- package/dist/temp/src/components/overlays/notification/MessageNotification.js +8 -2
- package/dist/temp/src/components/overlays/notification/MessageNotificationParts.d.ts +2 -1
- package/dist/temp/src/components/overlays/notification/MessageNotificationParts.js +5 -3
- package/dist/temp/src/components/overlays/notification/Notification.d.ts +14 -1
- package/dist/temp/src/components/overlays/notification/Notification.js +1 -1
- package/dist/temp/src/components/overlays/notification/__tests__/CalloutNotification.test.js +8 -1
- package/dist/temp/src/components/overlays/notification/__tests__/Notification.test.js +128 -1
- package/dist/temp/src/components/overlays/notification/useAutoClose.d.ts +15 -0
- package/dist/temp/src/components/overlays/notification/useAutoClose.js +25 -0
- package/dist/temp/src/utils/notification/__tests__/resolveAutoClose.test.d.ts +1 -0
- package/dist/temp/src/utils/notification/__tests__/resolveAutoClose.test.js +22 -0
- package/dist/temp/src/utils/notification/index.d.ts +1 -0
- package/dist/temp/src/utils/notification/index.js +1 -0
- package/dist/temp/src/utils/notification/resolveAutoClose.d.ts +14 -0
- package/dist/temp/src/utils/notification/resolveAutoClose.js +12 -0
- package/dist/types/assets/scripts/notification/const/types.d.ts +7 -0
- package/dist/types/assets/scripts/notification/messageTemplate.d.ts +1 -0
- package/dist/types/constant/notification.d.ts +6 -0
- package/dist/types/src/components/overlays/notification/FloatingNotification.d.ts +8 -0
- package/dist/types/src/components/overlays/notification/FullWidthNotification.d.ts +7 -0
- package/dist/types/src/components/overlays/notification/MessageNotification.d.ts +12 -0
- package/dist/types/src/components/overlays/notification/MessageNotificationParts.d.ts +2 -1
- package/dist/types/src/components/overlays/notification/Notification.d.ts +14 -1
- package/dist/types/src/components/overlays/notification/useAutoClose.d.ts +15 -0
- package/dist/types/src/utils/notification/__tests__/resolveAutoClose.test.d.ts +1 -0
- package/dist/types/src/utils/notification/index.d.ts +1 -0
- package/dist/types/src/utils/notification/resolveAutoClose.d.ts +14 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ var _react = require("react");
|
|
|
4
4
|
var _client = require("react-dom/client");
|
|
5
5
|
var _testUtils = require("react-dom/test-utils");
|
|
6
6
|
var _vitest = require("vitest");
|
|
7
|
+
var _notification = require("../../../../../constant/notification");
|
|
7
8
|
var _Notification = require("../Notification");
|
|
8
9
|
// @vitest-environment jsdom
|
|
9
10
|
|
|
@@ -80,4 +81,179 @@ const render = props => {
|
|
|
80
81
|
});
|
|
81
82
|
(0, _vitest.expect)(element).toBeNull();
|
|
82
83
|
});
|
|
84
|
+
});
|
|
85
|
+
const CLOSE_BUTTON = {
|
|
86
|
+
floating: '.ncua-floating-notification__close-button',
|
|
87
|
+
'full-width': '.ncua-full-width-notification__close-button',
|
|
88
|
+
message: '.ncua-message-notification__close-button'
|
|
89
|
+
};
|
|
90
|
+
const MESSAGE_FOOTER = '.ncua-message-notification__footer-container';
|
|
91
|
+
const MESSAGE_HIDE_LINK = '.ncua-message-notification__hide-link';
|
|
92
|
+
/** DES-SPEC-027 §5.3 성공 토스트 자동 닫힘 시간. */
|
|
93
|
+
const AUTO_CLOSE_MS = 3000;
|
|
94
|
+
(0, _vitest.describe)('Notification — hideCloseButton', () => {
|
|
95
|
+
(0, _vitest.afterEach)(() => {
|
|
96
|
+
_vitest.vi.useRealTimers();
|
|
97
|
+
});
|
|
98
|
+
(0, _vitest.it)('R-01: floating — onClose 가 있어도 hideCloseButton 이면 닫기 버튼을 렌더하지 않는다', () => {
|
|
99
|
+
const element = render({
|
|
100
|
+
type: 'floating',
|
|
101
|
+
onClose: _vitest.vi.fn(),
|
|
102
|
+
hideCloseButton: true
|
|
103
|
+
});
|
|
104
|
+
(0, _vitest.expect)(element?.querySelector(CLOSE_BUTTON.floating)).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
(0, _vitest.it)('R-02: full-width — onClose 가 있어도 hideCloseButton 이면 닫기 버튼을 렌더하지 않는다', () => {
|
|
107
|
+
const element = render({
|
|
108
|
+
type: 'full-width',
|
|
109
|
+
onClose: _vitest.vi.fn(),
|
|
110
|
+
hideCloseButton: true
|
|
111
|
+
});
|
|
112
|
+
(0, _vitest.expect)(element?.querySelector(CLOSE_BUTTON['full-width'])).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
(0, _vitest.it)('R-03: message — hideCloseButton 이면 닫기 버튼과 빈 footer 컨테이너를 모두 렌더하지 않는다', () => {
|
|
115
|
+
const element = render({
|
|
116
|
+
type: 'message',
|
|
117
|
+
onClose: _vitest.vi.fn(),
|
|
118
|
+
hideCloseButton: true
|
|
119
|
+
});
|
|
120
|
+
(0, _vitest.expect)(element?.querySelector(CLOSE_BUTTON.message)).toBeNull();
|
|
121
|
+
(0, _vitest.expect)(element?.querySelector(MESSAGE_FOOTER)).toBeNull();
|
|
122
|
+
});
|
|
123
|
+
_vitest.it.each(['floating', 'full-width', 'message'])('R-04: type=%s — hideCloseButton 미지정(기본 false)이면 onClose 만으로 닫기 버튼을 렌더한다', type => {
|
|
124
|
+
const element = render({
|
|
125
|
+
type,
|
|
126
|
+
onClose: _vitest.vi.fn()
|
|
127
|
+
});
|
|
128
|
+
(0, _vitest.expect)(element?.querySelector(CLOSE_BUTTON[type])).not.toBeNull();
|
|
129
|
+
});
|
|
130
|
+
_vitest.it.each(['floating', 'full-width', 'message'])('R-05: type=%s — onClose 없이 hideCloseButton 만 주면 닫기 버튼 없이 오류 없이 렌더한다', type => {
|
|
131
|
+
const element = render({
|
|
132
|
+
type,
|
|
133
|
+
hideCloseButton: true
|
|
134
|
+
});
|
|
135
|
+
(0, _vitest.expect)(element).not.toBeNull();
|
|
136
|
+
(0, _vitest.expect)(element?.querySelector(CLOSE_BUTTON[type])).toBeNull();
|
|
137
|
+
});
|
|
138
|
+
(0, _vitest.it)('R-06: floating — hideCloseButton 이어도 autoClose 만료 시 onClose 가 1회 호출되고 DOM 에서 제거된다', () => {
|
|
139
|
+
_vitest.vi.useFakeTimers();
|
|
140
|
+
const onClose = _vitest.vi.fn();
|
|
141
|
+
render({
|
|
142
|
+
type: 'floating',
|
|
143
|
+
autoClose: AUTO_CLOSE_MS,
|
|
144
|
+
hideCloseButton: true,
|
|
145
|
+
onClose
|
|
146
|
+
});
|
|
147
|
+
(0, _testUtils.act)(() => {
|
|
148
|
+
_vitest.vi.advanceTimersByTime(AUTO_CLOSE_MS);
|
|
149
|
+
});
|
|
150
|
+
(0, _vitest.expect)(onClose).toHaveBeenCalledTimes(1);
|
|
151
|
+
(0, _vitest.expect)(container?.querySelector('.ncua-floating-notification')).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
(0, _vitest.it)('R-07: full-width — hideCloseButton 이어도 autoClose 만료 시 onClose 가 1회 호출되고 DOM 에서 제거된다', () => {
|
|
154
|
+
_vitest.vi.useFakeTimers();
|
|
155
|
+
const onClose = _vitest.vi.fn();
|
|
156
|
+
render({
|
|
157
|
+
type: 'full-width',
|
|
158
|
+
autoClose: AUTO_CLOSE_MS,
|
|
159
|
+
hideCloseButton: true,
|
|
160
|
+
onClose
|
|
161
|
+
});
|
|
162
|
+
(0, _testUtils.act)(() => {
|
|
163
|
+
_vitest.vi.advanceTimersByTime(AUTO_CLOSE_MS);
|
|
164
|
+
});
|
|
165
|
+
(0, _vitest.expect)(onClose).toHaveBeenCalledTimes(1);
|
|
166
|
+
(0, _vitest.expect)(container?.querySelector('.ncua-full-width-notification')).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
(0, _vitest.it)('R-09: message — hideCloseButton + onHidePermanently 이면 footer 에 다시 보지 않기만 렌더한다', () => {
|
|
169
|
+
const element = render({
|
|
170
|
+
type: 'message',
|
|
171
|
+
onClose: _vitest.vi.fn(),
|
|
172
|
+
hideCloseButton: true,
|
|
173
|
+
onHidePermanently: _vitest.vi.fn()
|
|
174
|
+
});
|
|
175
|
+
(0, _vitest.expect)(element?.querySelector(MESSAGE_FOOTER)).not.toBeNull();
|
|
176
|
+
(0, _vitest.expect)(element?.querySelector(MESSAGE_HIDE_LINK)).not.toBeNull();
|
|
177
|
+
(0, _vitest.expect)(element?.querySelector(CLOSE_BUTTON.message)).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
(0, _vitest.it)('R-10: message — hideCloseButton 이어도 autoClose 만료 시 onClose 가 1회 호출되고 DOM 에서 제거된다', () => {
|
|
180
|
+
_vitest.vi.useFakeTimers();
|
|
181
|
+
const onClose = _vitest.vi.fn();
|
|
182
|
+
render({
|
|
183
|
+
type: 'message',
|
|
184
|
+
autoClose: AUTO_CLOSE_MS,
|
|
185
|
+
hideCloseButton: true,
|
|
186
|
+
onClose
|
|
187
|
+
});
|
|
188
|
+
(0, _testUtils.act)(() => {
|
|
189
|
+
_vitest.vi.advanceTimersByTime(AUTO_CLOSE_MS);
|
|
190
|
+
});
|
|
191
|
+
(0, _vitest.expect)(onClose).toHaveBeenCalledTimes(1);
|
|
192
|
+
(0, _vitest.expect)(container?.querySelector('.ncua-message-notification')).toBeNull();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
(0, _vitest.describe)('Notification — hideCloseButton 기본 autoClose', () => {
|
|
196
|
+
(0, _vitest.afterEach)(() => {
|
|
197
|
+
_vitest.vi.useRealTimers();
|
|
198
|
+
});
|
|
199
|
+
(0, _vitest.it)('R-11: floating — hideCloseButton 만 주고 autoClose 를 생략하면 3초 뒤 onClose 가 호출되고 제거된다', () => {
|
|
200
|
+
_vitest.vi.useFakeTimers();
|
|
201
|
+
const onClose = _vitest.vi.fn();
|
|
202
|
+
render({
|
|
203
|
+
type: 'floating',
|
|
204
|
+
hideCloseButton: true,
|
|
205
|
+
onClose
|
|
206
|
+
});
|
|
207
|
+
(0, _testUtils.act)(() => {
|
|
208
|
+
_vitest.vi.advanceTimersByTime(_notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS - 1);
|
|
209
|
+
});
|
|
210
|
+
(0, _vitest.expect)(onClose).not.toHaveBeenCalled();
|
|
211
|
+
(0, _testUtils.act)(() => {
|
|
212
|
+
_vitest.vi.advanceTimersByTime(1);
|
|
213
|
+
});
|
|
214
|
+
(0, _vitest.expect)(onClose).toHaveBeenCalledTimes(1);
|
|
215
|
+
(0, _vitest.expect)(container?.firstElementChild).toBeNull();
|
|
216
|
+
});
|
|
217
|
+
_vitest.it.each(['full-width', 'message'])('R-14: type=%s — hideCloseButton 만 주고 autoClose 를 생략해도 자동으로 닫히지 않는다 (부모 언마운트 용도)', type => {
|
|
218
|
+
_vitest.vi.useFakeTimers();
|
|
219
|
+
const onClose = _vitest.vi.fn();
|
|
220
|
+
render({
|
|
221
|
+
type,
|
|
222
|
+
hideCloseButton: true,
|
|
223
|
+
onClose
|
|
224
|
+
});
|
|
225
|
+
(0, _testUtils.act)(() => {
|
|
226
|
+
_vitest.vi.advanceTimersByTime(_notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS * 2);
|
|
227
|
+
});
|
|
228
|
+
(0, _vitest.expect)(onClose).not.toHaveBeenCalled();
|
|
229
|
+
(0, _vitest.expect)(container?.firstElementChild).not.toBeNull();
|
|
230
|
+
});
|
|
231
|
+
(0, _vitest.it)('R-12: floating — hideCloseButton + autoClose={0} 을 명시하면 자동으로 닫히지 않는다 (부모 언마운트 용도)', () => {
|
|
232
|
+
_vitest.vi.useFakeTimers();
|
|
233
|
+
const onClose = _vitest.vi.fn();
|
|
234
|
+
render({
|
|
235
|
+
type: 'floating',
|
|
236
|
+
hideCloseButton: true,
|
|
237
|
+
autoClose: 0,
|
|
238
|
+
onClose
|
|
239
|
+
});
|
|
240
|
+
(0, _testUtils.act)(() => {
|
|
241
|
+
_vitest.vi.advanceTimersByTime(_notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS * 2);
|
|
242
|
+
});
|
|
243
|
+
(0, _vitest.expect)(onClose).not.toHaveBeenCalled();
|
|
244
|
+
(0, _vitest.expect)(container?.querySelector('.ncua-floating-notification')).not.toBeNull();
|
|
245
|
+
});
|
|
246
|
+
(0, _vitest.it)('R-13: floating — hideCloseButton 이 아니면 autoClose 생략 시 기본 자동 닫힘이 없다 (회귀)', () => {
|
|
247
|
+
_vitest.vi.useFakeTimers();
|
|
248
|
+
const onClose = _vitest.vi.fn();
|
|
249
|
+
render({
|
|
250
|
+
type: 'floating',
|
|
251
|
+
onClose
|
|
252
|
+
});
|
|
253
|
+
(0, _testUtils.act)(() => {
|
|
254
|
+
_vitest.vi.advanceTimersByTime(_notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS * 2);
|
|
255
|
+
});
|
|
256
|
+
(0, _vitest.expect)(onClose).not.toHaveBeenCalled();
|
|
257
|
+
(0, _vitest.expect)(container?.querySelector('.ncua-floating-notification')).not.toBeNull();
|
|
258
|
+
});
|
|
83
259
|
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useAutoClose = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _notification = require("../../../utils/notification");
|
|
9
|
+
/**
|
|
10
|
+
* autoClose 타이머. 만료 시 `onClose`를 호출하고 `true`(제거됨)를 반환한다.
|
|
11
|
+
* floating · full-width · message 가 공유한다.
|
|
12
|
+
*
|
|
13
|
+
* `onClose`는 매 렌더 새 함수일 수 있어 ref 로 latest 만 보관하고 deps 에서 뺀다 —
|
|
14
|
+
* 인라인 핸들러를 넘겨도 부모 리렌더마다 타이머가 리셋되지 않는다.
|
|
15
|
+
*/
|
|
16
|
+
const useAutoClose = _ref => {
|
|
17
|
+
let {
|
|
18
|
+
autoClose,
|
|
19
|
+
hideCloseButton,
|
|
20
|
+
onClose
|
|
21
|
+
} = _ref;
|
|
22
|
+
const [shouldRemove, setShouldRemove] = (0, _react.useState)(false);
|
|
23
|
+
const onCloseRef = (0, _react.useRef)(onClose);
|
|
24
|
+
onCloseRef.current = onClose;
|
|
25
|
+
const autoCloseMs = (0, _notification.resolveAutoClose)({
|
|
26
|
+
autoClose,
|
|
27
|
+
hideCloseButton
|
|
28
|
+
});
|
|
29
|
+
(0, _react.useEffect)(() => {
|
|
30
|
+
if (autoCloseMs <= 0) return;
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
onCloseRef.current?.();
|
|
33
|
+
setShouldRemove(true);
|
|
34
|
+
}, autoCloseMs);
|
|
35
|
+
return () => clearTimeout(timer);
|
|
36
|
+
}, [autoCloseMs]);
|
|
37
|
+
return shouldRemove;
|
|
38
|
+
};
|
|
39
|
+
exports.useAutoClose = useAutoClose;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _vitest = require("vitest");
|
|
4
|
+
var _notification = require("../../../../constant/notification");
|
|
5
|
+
var _resolveAutoClose = require("../resolveAutoClose");
|
|
6
|
+
const EXPLICIT_MS = 5000;
|
|
7
|
+
const FALLBACK_MS = 4000;
|
|
8
|
+
const NO_AUTO_CLOSE = 0;
|
|
9
|
+
(0, _vitest.describe)('resolveAutoClose', () => {
|
|
10
|
+
(0, _vitest.it)('autoClose 를 명시하면 hideCloseButton·fallback 과 무관하게 그대로 쓴다 (0 포함)', () => {
|
|
11
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
12
|
+
autoClose: EXPLICIT_MS,
|
|
13
|
+
hideCloseButton: true,
|
|
14
|
+
fallback: FALLBACK_MS
|
|
15
|
+
})).toBe(EXPLICIT_MS);
|
|
16
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
17
|
+
autoClose: NO_AUTO_CLOSE,
|
|
18
|
+
hideCloseButton: true,
|
|
19
|
+
fallback: FALLBACK_MS
|
|
20
|
+
})).toBe(NO_AUTO_CLOSE);
|
|
21
|
+
});
|
|
22
|
+
(0, _vitest.it)('autoClose 미지정이면 fallback 이 양수일 때 fallback 을 쓴다', () => {
|
|
23
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
24
|
+
fallback: FALLBACK_MS
|
|
25
|
+
})).toBe(FALLBACK_MS);
|
|
26
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
27
|
+
hideCloseButton: true,
|
|
28
|
+
fallback: FALLBACK_MS
|
|
29
|
+
})).toBe(FALLBACK_MS);
|
|
30
|
+
});
|
|
31
|
+
(0, _vitest.it)('autoClose·fallback 모두 없으면 hideCloseButton 일 때만 기본 3초를 준다', () => {
|
|
32
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
33
|
+
hideCloseButton: true
|
|
34
|
+
})).toBe(_notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS);
|
|
35
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
36
|
+
hideCloseButton: true,
|
|
37
|
+
fallback: NO_AUTO_CLOSE
|
|
38
|
+
})).toBe(_notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS);
|
|
39
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({
|
|
40
|
+
hideCloseButton: false
|
|
41
|
+
})).toBe(NO_AUTO_CLOSE);
|
|
42
|
+
(0, _vitest.expect)((0, _resolveAutoClose.resolveAutoClose)({})).toBe(NO_AUTO_CLOSE);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "resolveAutoClose", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _resolveAutoClose.resolveAutoClose;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var _resolveAutoClose = require("./resolveAutoClose");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.resolveAutoClose = void 0;
|
|
7
|
+
var _notification = require("../../../constant/notification");
|
|
8
|
+
/**
|
|
9
|
+
* 실제 적용할 autoClose(ms)를 정한다. React·Vanilla 두 구현이 공유한다.
|
|
10
|
+
* 우선순위: 명시 `autoClose` > `fallback`(양수) > `hideCloseButton`이면 3초 > 0.
|
|
11
|
+
*/
|
|
12
|
+
const resolveAutoClose = _ref => {
|
|
13
|
+
let {
|
|
14
|
+
autoClose,
|
|
15
|
+
hideCloseButton = false,
|
|
16
|
+
fallback = 0
|
|
17
|
+
} = _ref;
|
|
18
|
+
if (autoClose !== undefined) return autoClose;
|
|
19
|
+
if (fallback > 0) return fallback;
|
|
20
|
+
return hideCloseButton ? _notification.HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS : 0;
|
|
21
|
+
};
|
|
22
|
+
exports.resolveAutoClose = resolveAutoClose;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MEDIA_QUERY } from '../../../src/constant/breakpoint';
|
|
2
|
+
import { resolveAutoClose } from '../../../src/utils/notification';
|
|
2
3
|
import { FeaturedIcon } from '../featuredIcon';
|
|
3
4
|
import { ButtonCloseX } from '../shared/ButtonCloseX';
|
|
4
5
|
import { sanitizeHtml } from '../utils/sanitize';
|
|
@@ -10,9 +11,13 @@ export class FloatingNotification {
|
|
|
10
11
|
color: 'neutral',
|
|
11
12
|
className: '',
|
|
12
13
|
actions: [],
|
|
13
|
-
|
|
14
|
+
hideCloseButton: false,
|
|
14
15
|
supportingText: undefined,
|
|
15
|
-
...options
|
|
16
|
+
...options,
|
|
17
|
+
autoClose: resolveAutoClose({
|
|
18
|
+
autoClose: options.autoClose,
|
|
19
|
+
hideCloseButton: options.hideCloseButton
|
|
20
|
+
})
|
|
16
21
|
};
|
|
17
22
|
this.element = this.createElement();
|
|
18
23
|
this.bindEvents();
|
|
@@ -26,7 +31,8 @@ export class FloatingNotification {
|
|
|
26
31
|
color,
|
|
27
32
|
className,
|
|
28
33
|
actions,
|
|
29
|
-
onClose
|
|
34
|
+
onClose,
|
|
35
|
+
hideCloseButton
|
|
30
36
|
} = this.options;
|
|
31
37
|
// 플로팅 알림에서 info 색상 사용 시 neutral로 대체
|
|
32
38
|
let actualColor = color;
|
|
@@ -55,6 +61,7 @@ export class FloatingNotification {
|
|
|
55
61
|
supportingText,
|
|
56
62
|
actions,
|
|
57
63
|
onClose,
|
|
64
|
+
hideCloseButton,
|
|
58
65
|
isMobile: mobile
|
|
59
66
|
}));
|
|
60
67
|
// FeaturedIcon을 container에 추가
|
|
@@ -72,6 +79,7 @@ export class FloatingNotification {
|
|
|
72
79
|
supportingText,
|
|
73
80
|
actions,
|
|
74
81
|
onClose,
|
|
82
|
+
hideCloseButton,
|
|
75
83
|
isMobile
|
|
76
84
|
} = params;
|
|
77
85
|
return `
|
|
@@ -86,12 +94,20 @@ export class FloatingNotification {
|
|
|
86
94
|
</div>
|
|
87
95
|
</div>
|
|
88
96
|
</div>
|
|
89
|
-
${this.renderCloseButton(
|
|
97
|
+
${this.renderCloseButton({
|
|
98
|
+
onClose,
|
|
99
|
+
isMobile,
|
|
100
|
+
hideCloseButton
|
|
101
|
+
})}
|
|
90
102
|
`;
|
|
91
103
|
}
|
|
92
|
-
renderCloseButton(
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
renderCloseButton(params) {
|
|
105
|
+
const {
|
|
106
|
+
onClose,
|
|
107
|
+
isMobile = false,
|
|
108
|
+
hideCloseButton = false
|
|
109
|
+
} = params;
|
|
110
|
+
if (!onClose || hideCloseButton) return '';
|
|
95
111
|
const size = getSizes.closeButton(isMobile);
|
|
96
112
|
return ButtonCloseX(size, 'light', CLASS_NAMES.FLOATING.CLOSE_BUTTON, '알림 닫기', onClose);
|
|
97
113
|
}
|
|
@@ -9,6 +9,7 @@ export class FullWidthNotification {
|
|
|
9
9
|
className: '',
|
|
10
10
|
actions: [],
|
|
11
11
|
autoClose: 0,
|
|
12
|
+
hideCloseButton: false,
|
|
12
13
|
supportingText: undefined,
|
|
13
14
|
...options
|
|
14
15
|
};
|
|
@@ -24,6 +25,7 @@ export class FullWidthNotification {
|
|
|
24
25
|
className,
|
|
25
26
|
actions,
|
|
26
27
|
onClose,
|
|
28
|
+
hideCloseButton,
|
|
27
29
|
supportTextLink,
|
|
28
30
|
supportTextLinkTarget,
|
|
29
31
|
onHidePermanently,
|
|
@@ -37,6 +39,7 @@ export class FullWidthNotification {
|
|
|
37
39
|
supportingText,
|
|
38
40
|
actions,
|
|
39
41
|
onClose,
|
|
42
|
+
hideCloseButton,
|
|
40
43
|
supportTextLink,
|
|
41
44
|
onHidePermanently,
|
|
42
45
|
hidePermanentlyLabel
|
|
@@ -51,6 +54,7 @@ export class FullWidthNotification {
|
|
|
51
54
|
supportingText,
|
|
52
55
|
actions,
|
|
53
56
|
onClose,
|
|
57
|
+
hideCloseButton,
|
|
54
58
|
supportTextLink,
|
|
55
59
|
onHidePermanently,
|
|
56
60
|
hidePermanentlyLabel
|
|
@@ -68,7 +72,10 @@ export class FullWidthNotification {
|
|
|
68
72
|
<div class="${CLASS_NAMES.FULL_WIDTH.ACTIONS_CONTAINER}">
|
|
69
73
|
${actions && actions.length > 0 ? renderActions(actions, CLASS_NAMES.FULL_WIDTH.ACTIONS) : ''}
|
|
70
74
|
${this.renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel)}
|
|
71
|
-
${this.renderCloseButton(
|
|
75
|
+
${this.renderCloseButton({
|
|
76
|
+
onClose,
|
|
77
|
+
hideCloseButton
|
|
78
|
+
})}
|
|
72
79
|
</div>
|
|
73
80
|
</div>
|
|
74
81
|
</div>
|
|
@@ -89,8 +96,12 @@ export class FullWidthNotification {
|
|
|
89
96
|
</button>
|
|
90
97
|
`;
|
|
91
98
|
}
|
|
92
|
-
renderCloseButton(
|
|
93
|
-
|
|
99
|
+
renderCloseButton(params) {
|
|
100
|
+
const {
|
|
101
|
+
onClose,
|
|
102
|
+
hideCloseButton = false
|
|
103
|
+
} = params;
|
|
104
|
+
if (!onClose || hideCloseButton) return '';
|
|
94
105
|
return `
|
|
95
106
|
<button type="button" class="${CLASS_NAMES.FULL_WIDTH.CLOSE_BUTTON}" aria-label="알림 닫기">
|
|
96
107
|
${SVG_ICONS['x-close'](FULL_WIDTH_SIZES.CLOSE_BUTTON)}
|
|
@@ -10,6 +10,7 @@ export class MessageNotification {
|
|
|
10
10
|
className: '',
|
|
11
11
|
actions: [],
|
|
12
12
|
autoClose: 0,
|
|
13
|
+
hideCloseButton: false,
|
|
13
14
|
supportingText: undefined,
|
|
14
15
|
...options
|
|
15
16
|
};
|
|
@@ -25,6 +26,7 @@ export class MessageNotification {
|
|
|
25
26
|
className,
|
|
26
27
|
actions,
|
|
27
28
|
onClose,
|
|
29
|
+
hideCloseButton,
|
|
28
30
|
onHidePermanently,
|
|
29
31
|
hidePermanentlyLabel,
|
|
30
32
|
variant,
|
|
@@ -44,6 +46,7 @@ export class MessageNotification {
|
|
|
44
46
|
color: actualColor,
|
|
45
47
|
actions,
|
|
46
48
|
onClose,
|
|
49
|
+
hideCloseButton,
|
|
47
50
|
onHidePermanently,
|
|
48
51
|
hidePermanentlyLabel
|
|
49
52
|
}));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveAutoClose } from '../../../src/utils/notification';
|
|
1
2
|
import { FloatingNotification } from './FloatingNotification';
|
|
2
3
|
import { FullWidthNotification } from './FullWidthNotification';
|
|
3
4
|
import { MessageNotification } from './MessageNotification';
|
|
@@ -73,8 +74,13 @@ export class NcuaNotification {
|
|
|
73
74
|
supportingText,
|
|
74
75
|
color,
|
|
75
76
|
type: 'floating',
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
...options,
|
|
78
|
+
// 명시 autoClose > 색상별 기본값 > (floating 이고 hideCloseButton 이면 3초). neutral 은 기본 0 이라 세 번째 분기가 필요.
|
|
79
|
+
autoClose: resolveAutoClose({
|
|
80
|
+
autoClose: options?.autoClose,
|
|
81
|
+
hideCloseButton: (options?.type ?? 'floating') === 'floating' && options?.hideCloseButton,
|
|
82
|
+
fallback: autoCloseMap[color]
|
|
83
|
+
})
|
|
78
84
|
});
|
|
79
85
|
}
|
|
80
86
|
// 클래스를 생성하지 않고도 기본 floating 알림 생성을 하기 위한 함수들
|
|
@@ -19,6 +19,7 @@ export function buildMessageTemplate(params) {
|
|
|
19
19
|
color,
|
|
20
20
|
actions,
|
|
21
21
|
onClose,
|
|
22
|
+
hideCloseButton,
|
|
22
23
|
onHidePermanently,
|
|
23
24
|
hidePermanentlyLabel
|
|
24
25
|
} = params;
|
|
@@ -34,6 +35,7 @@ export function buildMessageTemplate(params) {
|
|
|
34
35
|
${renderActionsContainer({
|
|
35
36
|
actions,
|
|
36
37
|
onClose,
|
|
38
|
+
hideCloseButton,
|
|
37
39
|
onHidePermanently,
|
|
38
40
|
hidePermanentlyLabel,
|
|
39
41
|
color
|
|
@@ -51,6 +53,7 @@ function renderActionsContainer(params) {
|
|
|
51
53
|
const {
|
|
52
54
|
actions,
|
|
53
55
|
onClose,
|
|
56
|
+
hideCloseButton,
|
|
54
57
|
onHidePermanently,
|
|
55
58
|
hidePermanentlyLabel,
|
|
56
59
|
color
|
|
@@ -58,6 +61,7 @@ function renderActionsContainer(params) {
|
|
|
58
61
|
const actionsHtml = renderActions(actions ?? [], CLASS_NAMES.MESSAGE.ACTIONS, ACTION_BUTTON_SIZE);
|
|
59
62
|
const footerHtml = renderFooterContainer({
|
|
60
63
|
onClose,
|
|
64
|
+
hideCloseButton,
|
|
61
65
|
onHidePermanently,
|
|
62
66
|
hidePermanentlyLabel,
|
|
63
67
|
color
|
|
@@ -69,15 +73,23 @@ function renderActionsContainer(params) {
|
|
|
69
73
|
function renderFooterContainer(params) {
|
|
70
74
|
const {
|
|
71
75
|
onClose,
|
|
76
|
+
hideCloseButton,
|
|
72
77
|
onHidePermanently,
|
|
73
78
|
hidePermanentlyLabel,
|
|
74
79
|
color
|
|
75
80
|
} = params;
|
|
76
|
-
|
|
81
|
+
// footer 컨테이너와 닫기 버튼이 같은 판정을 보게 해 빈 footer 가 남지 않게 한다.
|
|
82
|
+
// cf. React `MessageNotificationParts.tsx` 의 showCloseButton — 두 구현이 같은 판정을 유지해야 한다.
|
|
83
|
+
const showCloseButton = Boolean(onClose) && !hideCloseButton;
|
|
84
|
+
if (!onHidePermanently && !showCloseButton) return '';
|
|
77
85
|
return `
|
|
78
86
|
<div class="${CLASS_NAMES.MESSAGE.FOOTER_CONTAINER}">
|
|
79
87
|
${renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel)}
|
|
80
|
-
${renderCloseButton(
|
|
88
|
+
${renderCloseButton({
|
|
89
|
+
onClose,
|
|
90
|
+
hideCloseButton,
|
|
91
|
+
color
|
|
92
|
+
})}
|
|
81
93
|
</div>
|
|
82
94
|
`;
|
|
83
95
|
}
|
|
@@ -100,9 +112,13 @@ function renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel) {
|
|
|
100
112
|
* 닫기(X) 버튼. 아이콘 색은 React `<XClose color>`와 같이 svg 의 `color` 속성으로 주고
|
|
101
113
|
* path 는 `stroke="currentColor"`로 상속받는다 — 두 구현이 같은 마크업을 낸다.
|
|
102
114
|
*/
|
|
103
|
-
function renderCloseButton(
|
|
104
|
-
|
|
105
|
-
|
|
115
|
+
function renderCloseButton(params) {
|
|
116
|
+
const {
|
|
117
|
+
onClose,
|
|
118
|
+
hideCloseButton = false,
|
|
119
|
+
color = 'neutral'
|
|
120
|
+
} = params;
|
|
121
|
+
if (!onClose || hideCloseButton) return '';
|
|
106
122
|
const iconColor = MESSAGE_CLOSE_ICON_COLOR[color];
|
|
107
123
|
return `
|
|
108
124
|
<button type="button" class="${CLASS_NAMES.MESSAGE.CLOSE_BUTTON}" aria-label="알림 닫기">
|
|
@@ -26,4 +26,10 @@ export const MESSAGE_CLOSE_ICON_COLOR = {
|
|
|
26
26
|
warning: COLOR.orange500,
|
|
27
27
|
success: COLOR.green600,
|
|
28
28
|
info: COLOR.violet600
|
|
29
|
-
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* floating(토스트)에서 `hideCloseButton`인데 `autoClose`를 지정하지 않았을 때 적용되는 자동 닫힘 시간(ms).
|
|
32
|
+
* 닫기 버튼이 없는 토스트가 영구히 남지 않게 한다 — DES-SPEC-027 §5.3 Toast Alert 3초.
|
|
33
|
+
* full-width · message 는 부모가 언마운트로 닫으므로 적용하지 않는다.
|
|
34
|
+
*/
|
|
35
|
+
export const HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS = 3000;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { AlertCircle, AlertTriangle, CheckCircle, Pin02 } from '@ncds/ui-admin-icon';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
|
-
import { forwardRef, useEffect, useId,
|
|
4
|
+
import { forwardRef, useEffect, useId, useState } from 'react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { MEDIA_QUERY } from '../../../constant/breakpoint';
|
|
7
7
|
import { useMediaQuery } from '../../../hooks/useMediaQuery';
|
|
@@ -11,6 +11,7 @@ import { FeaturedIcon } from '../../image-and-icons/featured-icon/FeaturedIcon';
|
|
|
11
11
|
// 하므로 React 컴포넌트는 deep relative path 로 vanilla internals 를 직접 import 하지 않는다.
|
|
12
12
|
import { mountFloatingNotificationHost } from './host';
|
|
13
13
|
import { NotificationActionButtons } from './NotificationActionButtons';
|
|
14
|
+
import { useAutoClose } from './useAutoClose';
|
|
14
15
|
/**
|
|
15
16
|
* 색상별 a11y role.
|
|
16
17
|
* - error/warning → role="alert" (implicit aria-live="assertive": 즉시 발화)
|
|
@@ -65,13 +66,19 @@ const FloatingNotification = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
65
66
|
supportingText,
|
|
66
67
|
color = 'neutral',
|
|
67
68
|
onClose,
|
|
69
|
+
hideCloseButton = false,
|
|
68
70
|
className,
|
|
69
71
|
actions,
|
|
70
|
-
autoClose
|
|
72
|
+
autoClose,
|
|
71
73
|
portal = false,
|
|
72
74
|
...rest
|
|
73
75
|
} = _ref;
|
|
74
|
-
|
|
76
|
+
// floating 은 닫기 버튼이 없는데 autoClose 도 없으면 영구히 남으므로 기본 3초를 준다.
|
|
77
|
+
const shouldRemove = useAutoClose({
|
|
78
|
+
autoClose,
|
|
79
|
+
hideCloseButton,
|
|
80
|
+
onClose
|
|
81
|
+
});
|
|
75
82
|
const featuredIconProps = {
|
|
76
83
|
icon: iconMap[color] || Pin02,
|
|
77
84
|
size: 'sm',
|
|
@@ -81,31 +88,6 @@ const FloatingNotification = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
81
88
|
const isMobile = useMediaQuery(MEDIA_QUERY.mobile, {
|
|
82
89
|
onMatched: onClose
|
|
83
90
|
});
|
|
84
|
-
// onClose 는 매 렌더 새 함수 ref 일 수 있으므로 ref 로 latest 만 보관.
|
|
85
|
-
// → autoClose 타이머 effect 의 deps 에서 onClose 를 빼서 타이머 재시작 회피.
|
|
86
|
-
// render 본문에서 직접 할당 — useEffect 로 미루면 같은 commit 안에서 fire 되는 타이머가 stale onClose 를 볼 위험.
|
|
87
|
-
const onCloseRef = useRef(onClose);
|
|
88
|
-
onCloseRef.current = onClose;
|
|
89
|
-
// autoClose 타이머 관리
|
|
90
|
-
const timerRef = useRef(null);
|
|
91
|
-
// onClose 는 onCloseRef 로 latest 만 추적하므로 deps 에서 제외 (의도된 패턴).
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
// autoClose 가 0 보다 크면 타이머 설정.
|
|
94
|
-
if (autoClose > 0) {
|
|
95
|
-
timerRef.current = setTimeout(() => {
|
|
96
|
-
onCloseRef.current?.();
|
|
97
|
-
// DOM 에서 바로 제거
|
|
98
|
-
setShouldRemove(true);
|
|
99
|
-
}, autoClose);
|
|
100
|
-
}
|
|
101
|
-
// cleanup: 컴포넌트 언마운트 또는 autoClose 변경 시 타이머 정리
|
|
102
|
-
return () => {
|
|
103
|
-
if (timerRef.current) {
|
|
104
|
-
clearTimeout(timerRef.current);
|
|
105
|
-
timerRef.current = null;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
}, [autoClose]);
|
|
109
91
|
// portal=true 일 때만 호스트가 필요하다.
|
|
110
92
|
const host = useMountFloatingNotificationHost(portal);
|
|
111
93
|
// a11y — 카드 root 의 role 과 제목/본문 ID. 색상에 따라 alert(error/warning) / status(나머지) 로 분기.
|
|
@@ -155,7 +137,7 @@ const FloatingNotification = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
155
137
|
})]
|
|
156
138
|
})]
|
|
157
139
|
})
|
|
158
|
-
}), onClose && _jsx(ButtonCloseX, {
|
|
140
|
+
}), onClose && !hideCloseButton && _jsx(ButtonCloseX, {
|
|
159
141
|
theme: "light",
|
|
160
142
|
className: "ncua-floating-notification__close-button",
|
|
161
143
|
size: isMobile ? 'sm' : 'xs',
|