@ncds/ui-admin 1.8.26-alpha.4 → 1.8.26-alpha.6
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 +9 -9
- package/dist/cjs/assets/scripts/notification/Notification.js +8 -2
- package/dist/cjs/assets/scripts/notification/const/icons.js +17 -8
- package/dist/cjs/assets/scripts/notification/const/index.js +6 -0
- package/dist/cjs/assets/scripts/notification/messageTemplate.js +21 -5
- package/dist/cjs/constant/notification.js +8 -3
- package/dist/cjs/src/components/overlays/notification/FloatingNotification.js +11 -29
- package/dist/cjs/src/components/overlays/notification/FullWidthNotification.js +9 -24
- package/dist/cjs/src/components/overlays/notification/MessageNotification.js +25 -9
- 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__/MessageNotification.test.js +55 -13
- 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/generated/scoped-css.js +1 -1
- 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 +10 -10
- package/dist/esm/assets/scripts/notification/Notification.js +8 -2
- package/dist/esm/assets/scripts/notification/const/icons.js +16 -7
- package/dist/esm/assets/scripts/notification/const/index.js +1 -1
- package/dist/esm/assets/scripts/notification/messageTemplate.js +21 -5
- package/dist/esm/constant/notification.js +7 -2
- package/dist/esm/src/components/overlays/notification/FloatingNotification.js +12 -30
- package/dist/esm/src/components/overlays/notification/FullWidthNotification.js +10 -25
- package/dist/esm/src/components/overlays/notification/MessageNotification.js +25 -9
- 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__/MessageNotification.test.js +55 -13
- 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/generated/scoped-css.js +1 -1
- 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.d.ts +1 -1
- package/dist/temp/assets/scripts/notification/MessageNotification.js +10 -12
- package/dist/temp/assets/scripts/notification/Notification.js +7 -1
- package/dist/temp/assets/scripts/notification/const/icons.d.ts +6 -6
- package/dist/temp/assets/scripts/notification/const/icons.js +16 -7
- package/dist/temp/assets/scripts/notification/const/index.d.ts +1 -1
- package/dist/temp/assets/scripts/notification/const/index.js +1 -1
- package/dist/temp/assets/scripts/notification/const/types.d.ts +8 -1
- 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 -1
- package/dist/temp/constant/notification.js +6 -1
- package/dist/temp/src/components/overlays/notification/FloatingNotification.d.ts +8 -0
- package/dist/temp/src/components/overlays/notification/FloatingNotification.js +7 -30
- 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 +13 -1
- package/dist/temp/src/components/overlays/notification/MessageNotification.js +16 -9
- 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 +16 -3
- 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__/MessageNotification.test.js +44 -9
- 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/generated/scoped-css.js +1 -1
- 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/MessageNotification.d.ts +1 -1
- package/dist/types/assets/scripts/notification/const/icons.d.ts +6 -6
- package/dist/types/assets/scripts/notification/const/index.d.ts +1 -1
- package/dist/types/assets/scripts/notification/const/types.d.ts +8 -1
- package/dist/types/assets/scripts/notification/messageTemplate.d.ts +1 -0
- package/dist/types/constant/notification.d.ts +6 -1
- 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 +13 -1
- package/dist/types/src/components/overlays/notification/MessageNotificationParts.d.ts +2 -1
- package/dist/types/src/components/overlays/notification/Notification.d.ts +16 -3
- 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/dist/ui-admin/assets/styles/style.css +17 -11
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS } from '../../../../constant/notification';
|
|
3
|
+
import { resolveAutoClose } from '../resolveAutoClose';
|
|
4
|
+
const EXPLICIT_MS = 5000;
|
|
5
|
+
const FALLBACK_MS = 4000;
|
|
6
|
+
const NO_AUTO_CLOSE = 0;
|
|
7
|
+
describe('resolveAutoClose', () => {
|
|
8
|
+
it('autoClose 를 명시하면 hideCloseButton·fallback 과 무관하게 그대로 쓴다 (0 포함)', () => {
|
|
9
|
+
expect(resolveAutoClose({
|
|
10
|
+
autoClose: EXPLICIT_MS,
|
|
11
|
+
hideCloseButton: true,
|
|
12
|
+
fallback: FALLBACK_MS
|
|
13
|
+
})).toBe(EXPLICIT_MS);
|
|
14
|
+
expect(resolveAutoClose({
|
|
15
|
+
autoClose: NO_AUTO_CLOSE,
|
|
16
|
+
hideCloseButton: true,
|
|
17
|
+
fallback: FALLBACK_MS
|
|
18
|
+
})).toBe(NO_AUTO_CLOSE);
|
|
19
|
+
});
|
|
20
|
+
it('autoClose 미지정이면 fallback 이 양수일 때 fallback 을 쓴다', () => {
|
|
21
|
+
expect(resolveAutoClose({
|
|
22
|
+
fallback: FALLBACK_MS
|
|
23
|
+
})).toBe(FALLBACK_MS);
|
|
24
|
+
expect(resolveAutoClose({
|
|
25
|
+
hideCloseButton: true,
|
|
26
|
+
fallback: FALLBACK_MS
|
|
27
|
+
})).toBe(FALLBACK_MS);
|
|
28
|
+
});
|
|
29
|
+
it('autoClose·fallback 모두 없으면 hideCloseButton 일 때만 기본 3초를 준다', () => {
|
|
30
|
+
expect(resolveAutoClose({
|
|
31
|
+
hideCloseButton: true
|
|
32
|
+
})).toBe(HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS);
|
|
33
|
+
expect(resolveAutoClose({
|
|
34
|
+
hideCloseButton: true,
|
|
35
|
+
fallback: NO_AUTO_CLOSE
|
|
36
|
+
})).toBe(HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS);
|
|
37
|
+
expect(resolveAutoClose({
|
|
38
|
+
hideCloseButton: false
|
|
39
|
+
})).toBe(NO_AUTO_CLOSE);
|
|
40
|
+
expect(resolveAutoClose({})).toBe(NO_AUTO_CLOSE);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { resolveAutoClose } from './resolveAutoClose';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS } from '../../../constant/notification';
|
|
2
|
+
/**
|
|
3
|
+
* 실제 적용할 autoClose(ms)를 정한다. React·Vanilla 두 구현이 공유한다.
|
|
4
|
+
* 우선순위: 명시 `autoClose` > `fallback`(양수) > `hideCloseButton`이면 3초 > 0.
|
|
5
|
+
*/
|
|
6
|
+
export const resolveAutoClose = _ref => {
|
|
7
|
+
let {
|
|
8
|
+
autoClose,
|
|
9
|
+
hideCloseButton = false,
|
|
10
|
+
fallback = 0
|
|
11
|
+
} = _ref;
|
|
12
|
+
if (autoClose !== undefined) return autoClose;
|
|
13
|
+
if (fallback > 0) return fallback;
|
|
14
|
+
return hideCloseButton ? HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS : 0;
|
|
15
|
+
};
|
|
@@ -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,10 @@ export class FloatingNotification {
|
|
|
10
11
|
color: 'neutral',
|
|
11
12
|
className: '',
|
|
12
13
|
actions: [],
|
|
13
|
-
|
|
14
|
+
hideCloseButton: false,
|
|
14
15
|
supportingText: undefined,
|
|
15
16
|
...options,
|
|
17
|
+
autoClose: resolveAutoClose({ autoClose: options.autoClose, hideCloseButton: options.hideCloseButton }),
|
|
16
18
|
};
|
|
17
19
|
this.element = this.createElement();
|
|
18
20
|
this.bindEvents();
|
|
@@ -20,7 +22,7 @@ export class FloatingNotification {
|
|
|
20
22
|
this.setupMobileListener();
|
|
21
23
|
}
|
|
22
24
|
createElement() {
|
|
23
|
-
const { title, supportingText, color, className, actions, onClose } = this.options;
|
|
25
|
+
const { title, supportingText, color, className, actions, onClose, hideCloseButton } = this.options;
|
|
24
26
|
// 플로팅 알림에서 info 색상 사용 시 neutral로 대체
|
|
25
27
|
let actualColor = color;
|
|
26
28
|
if (color === 'info') {
|
|
@@ -48,6 +50,7 @@ export class FloatingNotification {
|
|
|
48
50
|
supportingText,
|
|
49
51
|
actions,
|
|
50
52
|
onClose,
|
|
53
|
+
hideCloseButton,
|
|
51
54
|
isMobile: mobile,
|
|
52
55
|
}));
|
|
53
56
|
// FeaturedIcon을 container에 추가
|
|
@@ -60,7 +63,7 @@ export class FloatingNotification {
|
|
|
60
63
|
return wrapper;
|
|
61
64
|
}
|
|
62
65
|
buildTemplate(params) {
|
|
63
|
-
const { title, supportingText, actions, onClose, isMobile } = params;
|
|
66
|
+
const { title, supportingText, actions, onClose, hideCloseButton, isMobile } = params;
|
|
64
67
|
return `
|
|
65
68
|
<div class="${CLASS_NAMES.FLOATING.CONTENT}">
|
|
66
69
|
<div class="${CLASS_NAMES.FLOATING.CONTAINER}">
|
|
@@ -73,11 +76,12 @@ export class FloatingNotification {
|
|
|
73
76
|
</div>
|
|
74
77
|
</div>
|
|
75
78
|
</div>
|
|
76
|
-
${this.renderCloseButton(onClose, isMobile)}
|
|
79
|
+
${this.renderCloseButton({ onClose, isMobile, hideCloseButton })}
|
|
77
80
|
`;
|
|
78
81
|
}
|
|
79
|
-
renderCloseButton(
|
|
80
|
-
|
|
82
|
+
renderCloseButton(params) {
|
|
83
|
+
const { onClose, isMobile = false, hideCloseButton = false } = params;
|
|
84
|
+
if (!onClose || hideCloseButton)
|
|
81
85
|
return '';
|
|
82
86
|
const size = getSizes.closeButton(isMobile);
|
|
83
87
|
return ButtonCloseX(size, 'light', CLASS_NAMES.FLOATING.CLOSE_BUTTON, '알림 닫기', onClose);
|
|
@@ -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
|
};
|
|
@@ -17,7 +18,7 @@ export class FullWidthNotification {
|
|
|
17
18
|
this.setupAutoClose();
|
|
18
19
|
}
|
|
19
20
|
createElement() {
|
|
20
|
-
const { title, supportingText, color, className, actions, onClose, supportTextLink, supportTextLinkTarget, onHidePermanently, hidePermanentlyLabel, } = this.options;
|
|
21
|
+
const { title, supportingText, color, className, actions, onClose, hideCloseButton, supportTextLink, supportTextLinkTarget, onHidePermanently, hidePermanentlyLabel, } = this.options;
|
|
21
22
|
const wrapper = createWrapperElement(CLASS_NAMES.FULL_WIDTH.BASE, color, className);
|
|
22
23
|
const iconHtml = FULL_WIDTH_ICON_MAP[color](FULL_WIDTH_SIZES.ICON);
|
|
23
24
|
wrapper.innerHTML = sanitizeHtml(this.buildTemplate({
|
|
@@ -26,6 +27,7 @@ export class FullWidthNotification {
|
|
|
26
27
|
supportingText,
|
|
27
28
|
actions,
|
|
28
29
|
onClose,
|
|
30
|
+
hideCloseButton,
|
|
29
31
|
supportTextLink,
|
|
30
32
|
onHidePermanently,
|
|
31
33
|
hidePermanentlyLabel,
|
|
@@ -34,7 +36,7 @@ export class FullWidthNotification {
|
|
|
34
36
|
return wrapper;
|
|
35
37
|
}
|
|
36
38
|
buildTemplate(params) {
|
|
37
|
-
const { iconHtml, title, supportingText, actions, onClose, supportTextLink, onHidePermanently, hidePermanentlyLabel, } = params;
|
|
39
|
+
const { iconHtml, title, supportingText, actions, onClose, hideCloseButton, supportTextLink, onHidePermanently, hidePermanentlyLabel, } = params;
|
|
38
40
|
return `
|
|
39
41
|
<div class="${CLASS_NAMES.FULL_WIDTH.CONTAINER}">
|
|
40
42
|
<div class="${CLASS_NAMES.FULL_WIDTH.CONTENT}">
|
|
@@ -48,7 +50,7 @@ export class FullWidthNotification {
|
|
|
48
50
|
<div class="${CLASS_NAMES.FULL_WIDTH.ACTIONS_CONTAINER}">
|
|
49
51
|
${actions && actions.length > 0 ? renderActions(actions, CLASS_NAMES.FULL_WIDTH.ACTIONS) : ''}
|
|
50
52
|
${this.renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel)}
|
|
51
|
-
${this.renderCloseButton(onClose)}
|
|
53
|
+
${this.renderCloseButton({ onClose, hideCloseButton })}
|
|
52
54
|
</div>
|
|
53
55
|
</div>
|
|
54
56
|
</div>
|
|
@@ -70,8 +72,9 @@ export class FullWidthNotification {
|
|
|
70
72
|
</button>
|
|
71
73
|
`;
|
|
72
74
|
}
|
|
73
|
-
renderCloseButton(
|
|
74
|
-
|
|
75
|
+
renderCloseButton(params) {
|
|
76
|
+
const { onClose, hideCloseButton = false } = params;
|
|
77
|
+
if (!onClose || hideCloseButton)
|
|
75
78
|
return '';
|
|
76
79
|
return `
|
|
77
80
|
<button type="button" class="${CLASS_NAMES.FULL_WIDTH.CLOSE_BUTTON}" aria-label="알림 닫기">
|
|
@@ -9,7 +9,7 @@ export declare class MessageNotification {
|
|
|
9
9
|
private featuredIcon?;
|
|
10
10
|
constructor(options: MessageNotificationOptions);
|
|
11
11
|
private createElement;
|
|
12
|
-
/** 색상별 FeaturedIcon. default 는 md(40/20), block 은 sm(32/16). */
|
|
12
|
+
/** 색상별 FeaturedIcon. default 는 md(40/20), block 은 sm(32/16). info 아이콘만 variant 별로 다르다. */
|
|
13
13
|
private createFeaturedIcon;
|
|
14
14
|
private bindEvents;
|
|
15
15
|
private setupAutoClose;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FeaturedIcon } from '../featuredIcon';
|
|
2
2
|
import { sanitizeHtml } from '../utils/sanitize';
|
|
3
|
-
import { CLASS_NAMES, MESSAGE_BLOCK_SIZES, MESSAGE_ICON_MAP, MESSAGE_SIZES } from './const';
|
|
3
|
+
import { CLASS_NAMES, MESSAGE_BLOCK_ICON_MAP, MESSAGE_BLOCK_SIZES, MESSAGE_ICON_MAP, MESSAGE_SIZES } from './const';
|
|
4
4
|
import { buildMessageTemplate } from './messageTemplate';
|
|
5
5
|
import { applySupportTextLinkTarget, bindNotificationEvents, createWrapperElement, setupAutoClose } from './utils';
|
|
6
6
|
export class MessageNotification {
|
|
@@ -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
|
};
|
|
@@ -18,20 +19,19 @@ export class MessageNotification {
|
|
|
18
19
|
this.setupAutoClose();
|
|
19
20
|
}
|
|
20
21
|
createElement() {
|
|
21
|
-
const { title, supportingText, color, className, actions, onClose, onHidePermanently, hidePermanentlyLabel, variant, supportTextLink, supportTextLinkTarget, } = this.options;
|
|
22
|
+
const { title, supportingText, color, className, actions, onClose, hideCloseButton, onHidePermanently, hidePermanentlyLabel, variant, supportTextLink, supportTextLinkTarget, } = this.options;
|
|
22
23
|
const isBlock = variant === 'block';
|
|
23
|
-
// default 는 neutral, error, warning, success 4가지 색상만 지원 — info 는 block 에서만 유지한다.
|
|
24
|
-
const actualColor = !isBlock && color === 'info' ? 'neutral' : color;
|
|
25
24
|
const wrapperClassName = [isBlock ? CLASS_NAMES.MESSAGE.BLOCK : '', className].filter(Boolean).join(' ');
|
|
26
|
-
const wrapper = createWrapperElement(CLASS_NAMES.MESSAGE.BASE,
|
|
27
|
-
const featuredIconElement = this.createFeaturedIcon(
|
|
25
|
+
const wrapper = createWrapperElement(CLASS_NAMES.MESSAGE.BASE, color, wrapperClassName);
|
|
26
|
+
const featuredIconElement = this.createFeaturedIcon(color, isBlock);
|
|
28
27
|
wrapper.innerHTML = sanitizeHtml(buildMessageTemplate({
|
|
29
28
|
title,
|
|
30
29
|
supportingText,
|
|
31
30
|
supportTextLink: isBlock ? supportTextLink : undefined,
|
|
32
|
-
color
|
|
31
|
+
color,
|
|
33
32
|
actions,
|
|
34
33
|
onClose,
|
|
34
|
+
hideCloseButton,
|
|
35
35
|
onHidePermanently,
|
|
36
36
|
hidePermanentlyLabel,
|
|
37
37
|
}));
|
|
@@ -45,16 +45,14 @@ export class MessageNotification {
|
|
|
45
45
|
}
|
|
46
46
|
return wrapper;
|
|
47
47
|
}
|
|
48
|
-
/** 색상별 FeaturedIcon. default 는 md(40/20), block 은 sm(32/16). */
|
|
48
|
+
/** 색상별 FeaturedIcon. default 는 md(40/20), block 은 sm(32/16). info 아이콘만 variant 별로 다르다. */
|
|
49
49
|
createFeaturedIcon(color, isBlock) {
|
|
50
|
-
const iconFunction = MESSAGE_ICON_MAP[color];
|
|
51
|
-
if (!iconFunction)
|
|
52
|
-
return null;
|
|
50
|
+
const iconFunction = (isBlock ? MESSAGE_BLOCK_ICON_MAP : MESSAGE_ICON_MAP)[color];
|
|
53
51
|
const sizes = isBlock ? MESSAGE_BLOCK_SIZES : MESSAGE_SIZES;
|
|
54
52
|
this.featuredIcon = FeaturedIcon.create({
|
|
55
53
|
svgString: iconFunction(sizes.ICON_PIXEL),
|
|
56
54
|
theme: 'light-circle',
|
|
57
|
-
// FeaturedIcon 에는 info 색이 없다.
|
|
55
|
+
// FeaturedIcon 에는 info 색이 없다. info 의 violet 톤은 message SCSS 가 덮어쓴다.
|
|
58
56
|
color: (color === 'info' ? 'neutral' : color),
|
|
59
57
|
size: sizes.FEATURED_ICON,
|
|
60
58
|
// React 구현과 동일하게 `__icon`(flex-shrink: 0)을 부여한다.
|
|
@@ -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';
|
|
@@ -72,8 +73,13 @@ export class NcuaNotification {
|
|
|
72
73
|
supportingText,
|
|
73
74
|
color,
|
|
74
75
|
type: 'floating',
|
|
75
|
-
autoClose: autoCloseMap[color],
|
|
76
76
|
...options,
|
|
77
|
+
// 명시 autoClose > 색상별 기본값 > (floating 이고 hideCloseButton 이면 3초). neutral 은 기본 0 이라 세 번째 분기가 필요.
|
|
78
|
+
autoClose: resolveAutoClose({
|
|
79
|
+
autoClose: options?.autoClose,
|
|
80
|
+
hideCloseButton: (options?.type ?? 'floating') === 'floating' && options?.hideCloseButton,
|
|
81
|
+
fallback: autoCloseMap[color],
|
|
82
|
+
}),
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
// 클래스를 생성하지 않고도 기본 floating 알림 생성을 하기 위한 함수들
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NotificationColor } from './types';
|
|
2
|
+
type IconFunction = (size: string) => string;
|
|
2
3
|
export declare const SVG_ICONS: {
|
|
3
4
|
readonly 'pin-02': (size: string) => string;
|
|
4
5
|
readonly 'alert-triangle': (size: string) => string;
|
|
@@ -23,9 +24,8 @@ export declare const ICON_MAP: {
|
|
|
23
24
|
readonly success: (size: string) => string;
|
|
24
25
|
readonly info: (size: string) => string;
|
|
25
26
|
};
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export declare const MESSAGE_ICON_MAP: Partial<Record<NotificationColor, (size: string) => string>>;
|
|
27
|
+
/** message 기본 타입 아이콘. floating 과 같은 4색에 info(neutral 과 같은 핀)를 더한다. */
|
|
28
|
+
export declare const MESSAGE_ICON_MAP: Record<NotificationColor, IconFunction>;
|
|
29
|
+
/** block 변형 아이콘. info 만 InfoCircle 로 다르다 (#536 유지). */
|
|
30
|
+
export declare const MESSAGE_BLOCK_ICON_MAP: Record<NotificationColor, IconFunction>;
|
|
31
|
+
export {};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* message block 변형의 InfoCircle. `SVG_ICONS['info-circle']`은 full-width 전용 class·color 가 박혀 있어
|
|
3
|
+
* 쓰지 않는다 (`.ncua-full-width-notification__icon`은 모바일에서 숨겨진다).
|
|
4
|
+
*/
|
|
5
|
+
const INFO_CIRCLE_ICON = (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10"></path></svg>`;
|
|
1
6
|
// SVG 아이콘들 (크기는 동적으로 설정)
|
|
2
7
|
export const SVG_ICONS = {
|
|
3
8
|
'pin-02': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.377 15.616 2.72 21.273m8.974-14.631-1.56 1.56a2 2 0 0 1-.264.242 1 1 0 0 1-.207.111c-.082.032-.17.05-.347.085l-3.665.733c-.952.19-1.428.286-1.65.537a1 1 0 0 0-.243.8c.046.333.39.677 1.076 1.363l7.086 7.086c.686.687 1.03 1.03 1.362 1.076a1 1 0 0 0 .801-.242c.251-.223.346-.7.537-1.651l.733-3.665c.035-.176.053-.265.085-.347a1 1 0 0 1 .11-.207c.051-.072.115-.136.242-.263l1.561-1.561c.082-.082.122-.122.167-.158q.06-.047.126-.085c.05-.029.103-.051.208-.097l2.495-1.069c.727-.312 1.091-.467 1.256-.72a1 1 0 0 0 .144-.747c-.06-.295-.34-.575-.9-1.135l-5.142-5.143c-.56-.56-.84-.84-1.135-.9a1 1 0 0 0-.748.145c-.252.165-.407.529-.72 1.256l-1.068 2.495a2 2 0 0 1-.097.208 1 1 0 0 1-.085.126 2 2 0 0 1-.158.167"></path></svg>`,
|
|
@@ -23,12 +28,16 @@ export const FULL_WIDTH_ICON_MAP = {
|
|
|
23
28
|
info: SVG_ICONS['info-circle'],
|
|
24
29
|
};
|
|
25
30
|
export const ICON_MAP = FULL_WIDTH_ICON_MAP;
|
|
26
|
-
/**
|
|
27
|
-
* message 타입 아이콘. floating 과 같은 4색에 block 변형 전용 info 를 더한다.
|
|
28
|
-
* `SVG_ICONS['info-circle']`은 full-width 전용 class·color 가 박혀 있어 쓰지 않는다
|
|
29
|
-
* (`.ncua-full-width-notification__icon`은 모바일에서 숨겨진다).
|
|
30
|
-
*/
|
|
31
|
+
/** message 기본 타입 아이콘. floating 과 같은 4색에 info(neutral 과 같은 핀)를 더한다. */
|
|
31
32
|
export const MESSAGE_ICON_MAP = {
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
neutral: SVG_ICONS['pin-02'],
|
|
34
|
+
error: SVG_ICONS['alert-triangle'],
|
|
35
|
+
warning: SVG_ICONS['alert-circle'],
|
|
36
|
+
success: SVG_ICONS['check-circle'],
|
|
37
|
+
info: SVG_ICONS['pin-02'],
|
|
38
|
+
};
|
|
39
|
+
/** block 변형 아이콘. info 만 InfoCircle 로 다르다 (#536 유지). */
|
|
40
|
+
export const MESSAGE_BLOCK_ICON_MAP = {
|
|
41
|
+
...MESSAGE_ICON_MAP,
|
|
42
|
+
info: INFO_CIRCLE_ICON,
|
|
34
43
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { CLASS_NAMES } from './classNames';
|
|
2
|
-
export { FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP, MESSAGE_ICON_MAP, SVG_ICONS } from './icons';
|
|
2
|
+
export { FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP, MESSAGE_BLOCK_ICON_MAP, MESSAGE_ICON_MAP, SVG_ICONS, } from './icons';
|
|
3
3
|
export { CLOSE_BUTTON_SIZES, CLOSE_BUTTON_SVG_SIZES, FEATURED_ICON_SIZES, FULL_WIDTH_SIZES, getSizes, ICON_PIXEL_SIZES, MESSAGE_BLOCK_SIZES, MESSAGE_SIZES, } from './sizes';
|
|
4
4
|
export type { BaseNotificationOptions, LegacyNotificationHierarchy, NotificationAction, NotificationActionButtonSize, NotificationColor, NotificationHierarchy, NotificationLinkTarget, NotificationVariant, } from './types';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Re-export all constants and types from separate files
|
|
2
2
|
export { CLASS_NAMES } from './classNames';
|
|
3
|
-
export { FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP, MESSAGE_ICON_MAP, SVG_ICONS } from './icons';
|
|
3
|
+
export { FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP, MESSAGE_BLOCK_ICON_MAP, MESSAGE_ICON_MAP, SVG_ICONS, } from './icons';
|
|
4
4
|
export { CLOSE_BUTTON_SIZES, CLOSE_BUTTON_SVG_SIZES, FEATURED_ICON_SIZES, FULL_WIDTH_SIZES, getSizes, ICON_PIXEL_SIZES, MESSAGE_BLOCK_SIZES, MESSAGE_SIZES, } from './sizes';
|
|
@@ -2,7 +2,7 @@ export type NotificationColor = 'neutral' | 'error' | 'warning' | 'success' | 'i
|
|
|
2
2
|
/**
|
|
3
3
|
* message 타입의 표시 변형. React `NotificationVariant`와 같은 어휘다.
|
|
4
4
|
* - `'default'`: 기존 message
|
|
5
|
-
* - `'block'`: 서브 페이지 공지용 (흰 배경, FeaturedIcon sm, 제목 + 텍스트 링크 1행, info
|
|
5
|
+
* - `'block'`: 서브 페이지 공지용 (흰 배경, FeaturedIcon sm, 제목 + 텍스트 링크 1행, info 아이콘 InfoCircle)
|
|
6
6
|
*/
|
|
7
7
|
export type NotificationVariant = 'default' | 'block';
|
|
8
8
|
/**
|
|
@@ -40,6 +40,13 @@ export interface BaseNotificationOptions {
|
|
|
40
40
|
supportingText?: string;
|
|
41
41
|
color?: NotificationColor;
|
|
42
42
|
onClose?: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* 닫기(X) 버튼 숨김 여부. `true`면 `onClose`가 있어도 X 버튼을 렌더하지 않는다.
|
|
45
|
+
* autoClose 만료 시 `onClose` 호출은 그대로 유지된다. 기본값 `false`.
|
|
46
|
+
* floating 은 `autoClose` 미지정 시 3초 뒤 자동으로 닫힌다(직접 `remove()` 로 닫으려면 `autoClose: 0` 명시).
|
|
47
|
+
* full-width · message 는 `autoClose` 를 명시했을 때만 자동으로 닫힌다.
|
|
48
|
+
*/
|
|
49
|
+
hideCloseButton?: boolean;
|
|
43
50
|
className?: string;
|
|
44
51
|
actions?: NotificationAction[];
|
|
45
52
|
autoClose?: number;
|
|
@@ -12,7 +12,7 @@ const ACTION_BUTTON_SIZE = 'xs';
|
|
|
12
12
|
* 동일한 클래스 트리를 산출해야 한다 — SCSS 한 벌을 두 구현이 공유한다.
|
|
13
13
|
*/
|
|
14
14
|
export function buildMessageTemplate(params) {
|
|
15
|
-
const { title, supportingText, supportTextLink, color, actions, onClose, onHidePermanently, hidePermanentlyLabel } = params;
|
|
15
|
+
const { title, supportingText, supportTextLink, color, actions, onClose, hideCloseButton, onHidePermanently, hidePermanentlyLabel, } = params;
|
|
16
16
|
return `
|
|
17
17
|
<div class="${CLASS_NAMES.MESSAGE.CONTAINER}">
|
|
18
18
|
<div class="${CLASS_NAMES.MESSAGE.CONTENT}">
|
|
@@ -22,7 +22,7 @@ export function buildMessageTemplate(params) {
|
|
|
22
22
|
${renderSupportingText(supportingText, CLASS_NAMES.MESSAGE.SUPPORTING_TEXT, supportTextLink, CLASS_NAMES.MESSAGE.LINK)}
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
|
-
${renderActionsContainer({ actions, onClose, onHidePermanently, hidePermanentlyLabel, color })}
|
|
25
|
+
${renderActionsContainer({ actions, onClose, hideCloseButton, onHidePermanently, hidePermanentlyLabel, color })}
|
|
26
26
|
</div>
|
|
27
27
|
</div>
|
|
28
28
|
`;
|
|
@@ -33,22 +33,31 @@ export function buildMessageTemplate(params) {
|
|
|
33
33
|
* 표시할 요소가 하나도 없으면 빈 컨테이너를 남기지 않는다.
|
|
34
34
|
*/
|
|
35
35
|
function renderActionsContainer(params) {
|
|
36
|
-
const { actions, onClose, onHidePermanently, hidePermanentlyLabel, color } = params;
|
|
36
|
+
const { actions, onClose, hideCloseButton, onHidePermanently, hidePermanentlyLabel, color } = params;
|
|
37
37
|
const actionsHtml = renderActions(actions ?? [], CLASS_NAMES.MESSAGE.ACTIONS, ACTION_BUTTON_SIZE);
|
|
38
|
-
const footerHtml = renderFooterContainer({
|
|
38
|
+
const footerHtml = renderFooterContainer({
|
|
39
|
+
onClose,
|
|
40
|
+
hideCloseButton,
|
|
41
|
+
onHidePermanently,
|
|
42
|
+
hidePermanentlyLabel,
|
|
43
|
+
color,
|
|
44
|
+
});
|
|
39
45
|
if (!actionsHtml && !footerHtml)
|
|
40
46
|
return '';
|
|
41
47
|
return `<div class="${CLASS_NAMES.MESSAGE.ACTIONS_CONTAINER}">${actionsHtml}${footerHtml}</div>`;
|
|
42
48
|
}
|
|
43
49
|
/** 다시 보지 않기 + 닫기 그룹. 전용 래퍼를 쓰는 이유는 `CLASS_NAMES.MESSAGE.FOOTER_CONTAINER` 참조. */
|
|
44
50
|
function renderFooterContainer(params) {
|
|
45
|
-
const { onClose, onHidePermanently, hidePermanentlyLabel, color } = params;
|
|
46
|
-
|
|
51
|
+
const { onClose, hideCloseButton, onHidePermanently, hidePermanentlyLabel, color } = params;
|
|
52
|
+
// footer 컨테이너와 닫기 버튼이 같은 판정을 보게 해 빈 footer 가 남지 않게 한다.
|
|
53
|
+
// cf. React `MessageNotificationParts.tsx` 의 showCloseButton — 두 구현이 같은 판정을 유지해야 한다.
|
|
54
|
+
const showCloseButton = Boolean(onClose) && !hideCloseButton;
|
|
55
|
+
if (!onHidePermanently && !showCloseButton)
|
|
47
56
|
return '';
|
|
48
57
|
return `
|
|
49
58
|
<div class="${CLASS_NAMES.MESSAGE.FOOTER_CONTAINER}">
|
|
50
59
|
${renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel)}
|
|
51
|
-
${renderCloseButton(onClose, color)}
|
|
60
|
+
${renderCloseButton({ onClose, hideCloseButton, color })}
|
|
52
61
|
</div>
|
|
53
62
|
`;
|
|
54
63
|
}
|
|
@@ -72,8 +81,9 @@ function renderHidePermanentlyButton(onHidePermanently, hidePermanentlyLabel) {
|
|
|
72
81
|
* 닫기(X) 버튼. 아이콘 색은 React `<XClose color>`와 같이 svg 의 `color` 속성으로 주고
|
|
73
82
|
* path 는 `stroke="currentColor"`로 상속받는다 — 두 구현이 같은 마크업을 낸다.
|
|
74
83
|
*/
|
|
75
|
-
function renderCloseButton(
|
|
76
|
-
|
|
84
|
+
function renderCloseButton(params) {
|
|
85
|
+
const { onClose, hideCloseButton = false, color = 'neutral' } = params;
|
|
86
|
+
if (!onClose || hideCloseButton)
|
|
77
87
|
return '';
|
|
78
88
|
const iconColor = MESSAGE_CLOSE_ICON_COLOR[color];
|
|
79
89
|
return `
|
|
@@ -17,7 +17,6 @@ export declare const HIDE_PERMANENTLY_LABEL: {
|
|
|
17
17
|
* message 타입 닫기(X) 아이콘의 color 별 색.
|
|
18
18
|
*
|
|
19
19
|
* React(`MessageNotification.tsx`)와 Vanilla(`messageTemplate.ts`)가 함께 참조하는 단일 출처다.
|
|
20
|
-
* `info`는 `variant: 'block'`에서만 렌더된다(default 는 neutral 로 폴백).
|
|
21
20
|
*/
|
|
22
21
|
export declare const MESSAGE_CLOSE_ICON_COLOR: {
|
|
23
22
|
readonly neutral: string;
|
|
@@ -26,3 +25,9 @@ export declare const MESSAGE_CLOSE_ICON_COLOR: {
|
|
|
26
25
|
readonly success: string;
|
|
27
26
|
readonly info: string;
|
|
28
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* floating(토스트)에서 `hideCloseButton`인데 `autoClose`를 지정하지 않았을 때 적용되는 자동 닫힘 시간(ms).
|
|
30
|
+
* 닫기 버튼이 없는 토스트가 영구히 남지 않게 한다 — DES-SPEC-027 §5.3 Toast Alert 3초.
|
|
31
|
+
* full-width · message 는 부모가 언마운트로 닫으므로 적용하지 않는다.
|
|
32
|
+
*/
|
|
33
|
+
export declare const HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS = 3000;
|
|
@@ -18,7 +18,6 @@ export const HIDE_PERMANENTLY_LABEL = {
|
|
|
18
18
|
* message 타입 닫기(X) 아이콘의 color 별 색.
|
|
19
19
|
*
|
|
20
20
|
* React(`MessageNotification.tsx`)와 Vanilla(`messageTemplate.ts`)가 함께 참조하는 단일 출처다.
|
|
21
|
-
* `info`는 `variant: 'block'`에서만 렌더된다(default 는 neutral 로 폴백).
|
|
22
21
|
*/
|
|
23
22
|
export const MESSAGE_CLOSE_ICON_COLOR = {
|
|
24
23
|
neutral: COLOR.gray700,
|
|
@@ -27,3 +26,9 @@ export const MESSAGE_CLOSE_ICON_COLOR = {
|
|
|
27
26
|
success: COLOR.green600,
|
|
28
27
|
info: COLOR.violet600,
|
|
29
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* floating(토스트)에서 `hideCloseButton`인데 `autoClose`를 지정하지 않았을 때 적용되는 자동 닫힘 시간(ms).
|
|
31
|
+
* 닫기 버튼이 없는 토스트가 영구히 남지 않게 한다 — DES-SPEC-027 §5.3 Toast Alert 3초.
|
|
32
|
+
* full-width · message 는 부모가 언마운트로 닫으므로 적용하지 않는다.
|
|
33
|
+
*/
|
|
34
|
+
export const HIDE_CLOSE_BUTTON_AUTO_CLOSE_MS = 3000;
|
|
@@ -18,6 +18,13 @@ interface FloatingNotificationProps extends Omit<ComponentPropsWithoutRef<'div'>
|
|
|
18
18
|
* 닫기 버튼 클릭 이벤트 핸들러
|
|
19
19
|
*/
|
|
20
20
|
onClose?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* 닫기(X) 버튼 숨김 여부. `true`면 `onClose`가 있어도 X 버튼을 렌더하지 않는다.
|
|
23
|
+
* autoClose 만료·모바일 전환 시 `onClose` 호출은 그대로 유지된다.
|
|
24
|
+
* `autoClose` 미지정이면 3초 뒤 자동으로 닫힌다. 부모가 직접 언마운트하려면 `autoClose={0}`을 명시한다.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
hideCloseButton?: boolean;
|
|
21
28
|
/**
|
|
22
29
|
* 추가 CSS 클래스
|
|
23
30
|
*/
|
|
@@ -28,6 +35,7 @@ interface FloatingNotificationProps extends Omit<ComponentPropsWithoutRef<'div'>
|
|
|
28
35
|
actions?: NotificationAction[];
|
|
29
36
|
/**
|
|
30
37
|
* 자동 닫기 시간 (밀리초 단위, 0이면 자동으로 닫히지 않음)
|
|
38
|
+
* 미지정 시 0. 단 `hideCloseButton`이면 3초.
|
|
31
39
|
* @default 0
|
|
32
40
|
*/
|
|
33
41
|
autoClose?: number;
|
|
@@ -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": 즉시 발화)
|
|
@@ -26,7 +27,7 @@ const iconMap = {
|
|
|
26
27
|
error: AlertTriangle,
|
|
27
28
|
warning: AlertCircle,
|
|
28
29
|
success: CheckCircle,
|
|
29
|
-
// info 는
|
|
30
|
+
// info 는 floating 에서 지원하지 않으므로 아이콘 매핑 없음.
|
|
30
31
|
};
|
|
31
32
|
/**
|
|
32
33
|
* NotificationColor → FeaturedIconColor 매핑.
|
|
@@ -60,8 +61,9 @@ function useMountFloatingNotificationHost(enabled) {
|
|
|
60
61
|
}, [enabled]);
|
|
61
62
|
return host;
|
|
62
63
|
}
|
|
63
|
-
const FloatingNotification = forwardRef(({ title, supportingText, color = 'neutral', onClose, className, actions, autoClose
|
|
64
|
-
|
|
64
|
+
const FloatingNotification = forwardRef(({ title, supportingText, color = 'neutral', onClose, hideCloseButton = false, className, actions, autoClose, portal = false, ...rest }, ref) => {
|
|
65
|
+
// floating 은 닫기 버튼이 없는데 autoClose 도 없으면 영구히 남으므로 기본 3초를 준다.
|
|
66
|
+
const shouldRemove = useAutoClose({ autoClose, hideCloseButton, onClose });
|
|
65
67
|
const featuredIconProps = {
|
|
66
68
|
icon: iconMap[color] || Pin02,
|
|
67
69
|
size: 'sm',
|
|
@@ -71,31 +73,6 @@ const FloatingNotification = forwardRef(({ title, supportingText, color = 'neutr
|
|
|
71
73
|
const isMobile = useMediaQuery(MEDIA_QUERY.mobile, {
|
|
72
74
|
onMatched: onClose,
|
|
73
75
|
});
|
|
74
|
-
// onClose 는 매 렌더 새 함수 ref 일 수 있으므로 ref 로 latest 만 보관.
|
|
75
|
-
// → autoClose 타이머 effect 의 deps 에서 onClose 를 빼서 타이머 재시작 회피.
|
|
76
|
-
// render 본문에서 직접 할당 — useEffect 로 미루면 같은 commit 안에서 fire 되는 타이머가 stale onClose 를 볼 위험.
|
|
77
|
-
const onCloseRef = useRef(onClose);
|
|
78
|
-
onCloseRef.current = onClose;
|
|
79
|
-
// autoClose 타이머 관리
|
|
80
|
-
const timerRef = useRef(null);
|
|
81
|
-
// onClose 는 onCloseRef 로 latest 만 추적하므로 deps 에서 제외 (의도된 패턴).
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
// autoClose 가 0 보다 크면 타이머 설정.
|
|
84
|
-
if (autoClose > 0) {
|
|
85
|
-
timerRef.current = setTimeout(() => {
|
|
86
|
-
onCloseRef.current?.();
|
|
87
|
-
// DOM 에서 바로 제거
|
|
88
|
-
setShouldRemove(true);
|
|
89
|
-
}, autoClose);
|
|
90
|
-
}
|
|
91
|
-
// cleanup: 컴포넌트 언마운트 또는 autoClose 변경 시 타이머 정리
|
|
92
|
-
return () => {
|
|
93
|
-
if (timerRef.current) {
|
|
94
|
-
clearTimeout(timerRef.current);
|
|
95
|
-
timerRef.current = null;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}, [autoClose]);
|
|
99
76
|
// portal=true 일 때만 호스트가 필요하다.
|
|
100
77
|
const host = useMountFloatingNotificationHost(portal);
|
|
101
78
|
// a11y — 카드 root 의 role 과 제목/본문 ID. 색상에 따라 alert(error/warning) / status(나머지) 로 분기.
|
|
@@ -110,7 +87,7 @@ const FloatingNotification = forwardRef(({ title, supportingText, color = 'neutr
|
|
|
110
87
|
const card = (
|
|
111
88
|
// a11y/예측가능성 — caller 가 className/id 등 일부는 덮어쓸 수 있게 {...rest} 를 먼저 펼치고,
|
|
112
89
|
// role/aria-labelledby/aria-describedby 같은 컴포넌트 본질 prop 은 그 뒤에 두어 우선 적용.
|
|
113
|
-
_jsxs("div", { ...rest, ref: ref, className: classNames('ncua-floating-notification', `ncua-floating-notification--${color}`, className), role: role, "aria-labelledby": titleId, "aria-describedby": supportingText ? descId : undefined, children: [_jsx("div", { className: "ncua-floating-notification__content", children: _jsxs("div", { className: "ncua-floating-notification__container", children: [iconMap[color] && _jsx(FeaturedIcon, { ...featuredIconProps, size: isMobile ? 'md' : 'sm' }), _jsxs("div", { className: "ncua-floating-notification__text-container", children: [_jsx("div", { className: "ncua-floating-notification__title-wrapper", children: _jsx("span", { id: titleId, className: "ncua-floating-notification__title", children: title }) }), supportingText && (_jsx("span", { id: descId, className: "ncua-floating-notification__supporting-text", children: supportingText })), actions && (_jsx("div", { className: "ncua-floating-notification__actions", children: _jsx(NotificationActionButtons, { actions: actions }) }))] })] }) }), onClose && (_jsx(ButtonCloseX, { theme: "light", className: "ncua-floating-notification__close-button", size: isMobile ? 'sm' : 'xs', onClick: onClose }))] }));
|
|
90
|
+
_jsxs("div", { ...rest, ref: ref, className: classNames('ncua-floating-notification', `ncua-floating-notification--${color}`, className), role: role, "aria-labelledby": titleId, "aria-describedby": supportingText ? descId : undefined, children: [_jsx("div", { className: "ncua-floating-notification__content", children: _jsxs("div", { className: "ncua-floating-notification__container", children: [iconMap[color] && _jsx(FeaturedIcon, { ...featuredIconProps, size: isMobile ? 'md' : 'sm' }), _jsxs("div", { className: "ncua-floating-notification__text-container", children: [_jsx("div", { className: "ncua-floating-notification__title-wrapper", children: _jsx("span", { id: titleId, className: "ncua-floating-notification__title", children: title }) }), supportingText && (_jsx("span", { id: descId, className: "ncua-floating-notification__supporting-text", children: supportingText })), actions && (_jsx("div", { className: "ncua-floating-notification__actions", children: _jsx(NotificationActionButtons, { actions: actions }) }))] })] }) }), onClose && !hideCloseButton && (_jsx(ButtonCloseX, { theme: "light", className: "ncua-floating-notification__close-button", size: isMobile ? 'sm' : 'xs', onClick: onClose }))] }));
|
|
114
91
|
// 기본은 인라인 렌더 — 부모 JSX 트리에 카드를 그대로 둔다.
|
|
115
92
|
if (!portal) {
|
|
116
93
|
return card;
|
|
@@ -18,6 +18,13 @@ interface FullWidthNotificationProps extends Omit<ComponentPropsWithoutRef<'div'
|
|
|
18
18
|
* 닫기 버튼 클릭 이벤트 핸들러
|
|
19
19
|
*/
|
|
20
20
|
onClose?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* 닫기(X) 버튼 숨김 여부. `true`면 `onClose`가 있어도 X 버튼을 렌더하지 않는다.
|
|
23
|
+
* autoClose 만료 시 `onClose` 호출은 그대로 유지된다.
|
|
24
|
+
* 기본 자동 닫힘은 없다 — 부모가 언마운트로 닫거나 `autoClose`를 명시한다.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
hideCloseButton?: boolean;
|
|
21
28
|
/**
|
|
22
29
|
* 추가 CSS 클래스
|
|
23
30
|
*/
|