@ncds/ui-admin 1.8.21-alpha.1 → 1.8.21-alpha.11
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/FullWidthNotification.js +17 -6
- package/dist/cjs/assets/scripts/notification/MessageNotification.js +10 -48
- package/dist/cjs/assets/scripts/notification/const/classNames.js +3 -0
- package/dist/cjs/assets/scripts/notification/const/index.js +1 -1
- package/dist/cjs/assets/scripts/notification/const/sizes.js +4 -2
- package/dist/cjs/assets/scripts/notification/messageTemplate.js +106 -0
- package/dist/cjs/assets/scripts/notification/utils.js +40 -4
- package/dist/cjs/constant/notification.js +21 -0
- package/dist/cjs/src/components/forms-and-input/input-base/InputBase.js +30 -18
- package/dist/cjs/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +116 -0
- package/dist/cjs/src/components/forms-and-input/password-input/PasswordInput.js +3 -3
- package/dist/cjs/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js +59 -0
- package/dist/cjs/src/components/index.js +11 -0
- package/dist/cjs/src/components/layout/ncua-scope/NcuaScope.js +8 -2
- package/dist/cjs/src/components/navigation/pagination/Pagination.js +3 -1
- package/dist/cjs/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
- package/dist/cjs/src/components/overlays/notification/FullWidthNotification.js +3 -1
- package/dist/cjs/src/components/overlays/notification/MessageNotification.js +14 -54
- package/dist/cjs/src/components/overlays/notification/MessageNotificationParts.js +92 -0
- package/dist/cjs/src/components/overlays/notification/__tests__/MessageNotification.test.js +246 -0
- package/dist/cjs/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +209 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +259 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +68 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +623 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/index.js +16 -0
- package/dist/cjs/src/generated/scoped-css.js +1 -1
- package/dist/esm/assets/scripts/notification/FullWidthNotification.js +17 -6
- package/dist/esm/assets/scripts/notification/MessageNotification.js +12 -50
- package/dist/esm/assets/scripts/notification/const/classNames.js +3 -0
- package/dist/esm/assets/scripts/notification/const/index.js +3 -2
- package/dist/esm/assets/scripts/notification/const/sizes.js +4 -2
- package/dist/esm/assets/scripts/notification/messageTemplate.js +100 -0
- package/dist/esm/assets/scripts/notification/utils.js +39 -4
- package/dist/esm/constant/notification.js +15 -0
- package/dist/esm/src/components/forms-and-input/input-base/InputBase.js +30 -18
- package/dist/esm/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +116 -0
- package/dist/esm/src/components/forms-and-input/password-input/PasswordInput.js +3 -3
- package/dist/esm/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js +56 -0
- package/dist/esm/src/components/index.js +1 -0
- package/dist/esm/src/components/layout/ncua-scope/NcuaScope.js +8 -2
- package/dist/esm/src/components/navigation/pagination/Pagination.js +3 -1
- package/dist/esm/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
- package/dist/esm/src/components/overlays/notification/FullWidthNotification.js +3 -1
- package/dist/esm/src/components/overlays/notification/MessageNotification.js +15 -55
- package/dist/esm/src/components/overlays/notification/MessageNotificationParts.js +85 -0
- package/dist/esm/src/components/overlays/notification/__tests__/MessageNotification.test.js +243 -0
- package/dist/esm/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +206 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +252 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +61 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +620 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/index.js +1 -0
- package/dist/esm/src/generated/scoped-css.js +1 -1
- package/dist/temp/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
- package/dist/temp/assets/scripts/notification/FullWidthNotification.js +14 -5
- package/dist/temp/assets/scripts/notification/MessageNotification.d.ts +0 -3
- package/dist/temp/assets/scripts/notification/MessageNotification.js +9 -44
- package/dist/temp/assets/scripts/notification/const/classNames.d.ts +1 -0
- package/dist/temp/assets/scripts/notification/const/classNames.js +3 -0
- package/dist/temp/assets/scripts/notification/const/index.d.ts +3 -3
- package/dist/temp/assets/scripts/notification/const/index.js +3 -2
- package/dist/temp/assets/scripts/notification/const/sizes.d.ts +2 -2
- package/dist/temp/assets/scripts/notification/const/sizes.js +4 -2
- package/dist/temp/assets/scripts/notification/const/types.d.ts +30 -2
- package/dist/temp/assets/scripts/notification/messageTemplate.d.ts +15 -0
- package/dist/temp/assets/scripts/notification/messageTemplate.js +79 -0
- package/dist/temp/assets/scripts/notification/utils.d.ts +19 -2
- package/dist/temp/assets/scripts/notification/utils.js +39 -5
- package/dist/temp/constant/notification.d.ts +15 -0
- package/dist/temp/constant/notification.js +15 -0
- package/dist/temp/src/components/forms-and-input/input-base/InputBase.d.ts +1 -1
- package/dist/temp/src/components/forms-and-input/input-base/InputBase.js +26 -14
- package/dist/temp/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +79 -0
- package/dist/temp/src/components/forms-and-input/password-input/PasswordInput.js +2 -2
- package/dist/temp/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.d.ts +1 -0
- package/dist/temp/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js +48 -0
- package/dist/temp/src/components/index.d.ts +1 -0
- package/dist/temp/src/components/index.js +1 -0
- package/dist/temp/src/components/layout/ncua-scope/NcuaScope.js +5 -1
- package/dist/temp/src/components/navigation/pagination/Pagination.js +3 -1
- package/dist/temp/src/components/navigation/pagination/__tests__/Pagination.test.js +74 -0
- package/dist/temp/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
- package/dist/temp/src/components/overlays/notification/FullWidthNotification.js +3 -2
- package/dist/temp/src/components/overlays/notification/MessageNotification.d.ts +6 -0
- package/dist/temp/src/components/overlays/notification/MessageNotification.js +4 -13
- package/dist/temp/src/components/overlays/notification/MessageNotificationParts.d.ts +28 -0
- package/dist/temp/src/components/overlays/notification/MessageNotificationParts.js +28 -0
- package/dist/temp/src/components/overlays/notification/Notification.d.ts +11 -2
- package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.js +190 -0
- package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.js +160 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +45 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +125 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.d.ts +16 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.js +24 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +503 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/index.d.ts +1 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/index.js +1 -0
- package/dist/temp/src/generated/scoped-css.js +1 -1
- package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +6 -0
- package/dist/types/assets/scripts/notification/MessageNotification.d.ts +0 -3
- package/dist/types/assets/scripts/notification/const/classNames.d.ts +1 -0
- package/dist/types/assets/scripts/notification/const/index.d.ts +3 -3
- package/dist/types/assets/scripts/notification/const/sizes.d.ts +2 -2
- package/dist/types/assets/scripts/notification/const/types.d.ts +30 -2
- package/dist/types/assets/scripts/notification/messageTemplate.d.ts +15 -0
- package/dist/types/assets/scripts/notification/utils.d.ts +19 -2
- package/dist/types/constant/notification.d.ts +15 -0
- package/dist/types/src/components/forms-and-input/input-base/InputBase.d.ts +1 -1
- package/dist/types/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.d.ts +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/overlays/notification/FullWidthNotification.d.ts +6 -0
- package/dist/types/src/components/overlays/notification/MessageNotification.d.ts +6 -0
- package/dist/types/src/components/overlays/notification/MessageNotificationParts.d.ts +28 -0
- package/dist/types/src/components/overlays/notification/Notification.d.ts +11 -2
- package/dist/types/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts +1 -0
- package/dist/types/src/components/overlays/notification/__tests__/hidePermanentlyLabel.test.d.ts +1 -0
- package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +45 -0
- package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModalTip.d.ts +16 -0
- package/dist/types/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.d.ts +1 -0
- package/dist/types/src/components/overlays/postcode-search-modal/index.d.ts +1 -0
- package/dist/ui-admin/assets/styles/style.css +279 -37
- package/dist/ui-admin/assets/styles/style.scoped.css +279 -37
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import { createElement } from 'react';
|
|
|
4
4
|
import { createRoot } from 'react-dom/client';
|
|
5
5
|
import { act } from 'react-dom/test-utils';
|
|
6
6
|
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { COLOR } from '../../../../../constant/color';
|
|
7
8
|
import { InputBase } from '../InputBase';
|
|
8
9
|
// React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
|
|
9
10
|
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
@@ -107,6 +108,42 @@ describe('InputBase — 아이콘 사이즈', () => {
|
|
|
107
108
|
view.unmount();
|
|
108
109
|
});
|
|
109
110
|
});
|
|
111
|
+
describe('InputBase — 슬롯 아이콘 상태 색상', () => {
|
|
112
|
+
// 상태별 색은 CSS 가 결정한다. jsdom 에서는 스타일시트를 로드하지 않으므로
|
|
113
|
+
// "상태 규칙을 타는 클래스가 붙었는지" 와 "지정 색이 인라인으로 남았는지" 로 검증한다.
|
|
114
|
+
const leftIconOf = (container) => container.querySelector('.ncua-input__left-icon');
|
|
115
|
+
it('color 를 지정하지 않으면 상태 색상 클래스가 붙는다', () => {
|
|
116
|
+
// Given/When: color 없이 슬롯 아이콘을 렌더하면
|
|
117
|
+
const view = mountInputBase({ leadingElement: { type: 'icon', icon: SearchLg } });
|
|
118
|
+
// Then: 상태 규칙을 타는 클래스가 붙고, 인라인 색은 지정되지 않는다
|
|
119
|
+
const icon = leftIconOf(view.container);
|
|
120
|
+
expect(icon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
121
|
+
expect(icon?.hasAttribute('color')).toBe(false);
|
|
122
|
+
view.unmount();
|
|
123
|
+
});
|
|
124
|
+
it('color 를 지정하면 상태 색상 클래스가 붙지 않고 지정 색이 유지된다', () => {
|
|
125
|
+
// Given/When: color 를 지정해 렌더하면
|
|
126
|
+
const view = mountInputBase({
|
|
127
|
+
leadingElement: { type: 'icon', icon: SearchLg, color: 'blue500' },
|
|
128
|
+
});
|
|
129
|
+
// Then: 상태 규칙에서 제외되고 지정한 색이 그대로 남는다
|
|
130
|
+
const icon = leftIconOf(view.container);
|
|
131
|
+
expect(icon?.classList.contains('ncua-input__slot-icon')).toBe(false);
|
|
132
|
+
expect(icon?.getAttribute('color')).toBe(COLOR.blue500);
|
|
133
|
+
view.unmount();
|
|
134
|
+
});
|
|
135
|
+
it('slot.className 을 함께 주어도 상태 색상 클래스가 유지된다', () => {
|
|
136
|
+
// Given/When: className 을 추가로 넘기면
|
|
137
|
+
const view = mountInputBase({
|
|
138
|
+
leadingElement: { type: 'icon', icon: SearchLg, className: 'my-icon' },
|
|
139
|
+
});
|
|
140
|
+
// Then: 두 클래스가 모두 붙는다
|
|
141
|
+
const icon = leftIconOf(view.container);
|
|
142
|
+
expect(icon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
143
|
+
expect(icon?.classList.contains('my-icon')).toBe(true);
|
|
144
|
+
view.unmount();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
110
147
|
describe('InputBase — has-value 상태 클래스', () => {
|
|
111
148
|
const typeInto = (container, text) => {
|
|
112
149
|
const input = container.querySelector('input');
|
|
@@ -152,3 +189,45 @@ describe('InputBase — has-value 상태 클래스', () => {
|
|
|
152
189
|
view.unmount();
|
|
153
190
|
});
|
|
154
191
|
});
|
|
192
|
+
describe('InputBase — 텍스트 슬롯', () => {
|
|
193
|
+
it('leadingElement 텍스트는 leading 클래스와 wrap 클래스가 붙는다', () => {
|
|
194
|
+
const { container, unmount } = mountInputBase({
|
|
195
|
+
leadingElement: { type: 'text', text: 'https://' },
|
|
196
|
+
});
|
|
197
|
+
const text = container.querySelector('.ncua-input__leading-text');
|
|
198
|
+
expect(text?.textContent).toBe('https://');
|
|
199
|
+
expect(container.querySelector('.ncua-input__leading-text-wrap')).not.toBeNull();
|
|
200
|
+
unmount();
|
|
201
|
+
});
|
|
202
|
+
it('trailingElement 텍스트는 trailing 클래스와 wrap 클래스가 붙는다', () => {
|
|
203
|
+
const { container, unmount } = mountInputBase({
|
|
204
|
+
trailingElement: { type: 'text', text: 'px' },
|
|
205
|
+
});
|
|
206
|
+
const text = container.querySelector('.ncua-input__trailing-text');
|
|
207
|
+
expect(text?.textContent).toBe('px');
|
|
208
|
+
expect(container.querySelector('.ncua-input__trailing-text-wrap')).not.toBeNull();
|
|
209
|
+
// leading 클래스가 섞이면 모서리·테두리 방향이 반대가 된다
|
|
210
|
+
expect(container.querySelector('.ncua-input__leading-text')).toBeNull();
|
|
211
|
+
unmount();
|
|
212
|
+
});
|
|
213
|
+
it('trailingElement 텍스트는 입력 필드 뒤에 온다', () => {
|
|
214
|
+
const { container, unmount } = mountInputBase({
|
|
215
|
+
trailingElement: { type: 'text', text: 'px' },
|
|
216
|
+
});
|
|
217
|
+
const content = container.querySelector('.ncua-input__content');
|
|
218
|
+
const children = Array.from(content?.children ?? []);
|
|
219
|
+
const fieldIndex = children.findIndex((el) => el.classList.contains('ncua-input__field'));
|
|
220
|
+
const textIndex = children.findIndex((el) => el.classList.contains('ncua-input__trailing-text'));
|
|
221
|
+
expect(fieldIndex).toBeGreaterThanOrEqual(0);
|
|
222
|
+
expect(textIndex).toBeGreaterThan(fieldIndex);
|
|
223
|
+
unmount();
|
|
224
|
+
});
|
|
225
|
+
it('사이즈 modifier 가 함께 붙는다', () => {
|
|
226
|
+
const { container, unmount } = mountInputBase({
|
|
227
|
+
size: 'sm',
|
|
228
|
+
trailingElement: { type: 'text', text: 'px' },
|
|
229
|
+
});
|
|
230
|
+
expect(container.querySelector('.ncua-input__trailing-text--sm')).not.toBeNull();
|
|
231
|
+
unmount();
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -19,8 +19,8 @@ export const PasswordInput = forwardRef(({ size = 'xs', ...props }, ref) => {
|
|
|
19
19
|
return (_jsx(InputBase, { ref: ref, size: size, type: isVisible ? 'text' : 'password', leadingElement: {
|
|
20
20
|
type: 'icon',
|
|
21
21
|
icon: Lock01,
|
|
22
|
-
//
|
|
23
|
-
|
|
22
|
+
// color 를 넘기지 않아 슬롯 아이콘 공통 규칙(상태별 색)을 그대로 따른다.
|
|
23
|
+
// 여기서 props.value 로 판정하면 uncontrolled Input 에서 영원히 빈 값 취급된다.
|
|
24
24
|
}, trailingElement: {
|
|
25
25
|
type: 'custom',
|
|
26
26
|
placement: 'inside',
|
package/dist/temp/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/temp/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { createRoot } from 'react-dom/client';
|
|
4
|
+
import { act } from 'react-dom/test-utils';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { PasswordInput } from '../PasswordInput';
|
|
7
|
+
// React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
|
|
8
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
9
|
+
function mountPasswordInput(props = {}) {
|
|
10
|
+
const container = document.createElement('div');
|
|
11
|
+
document.body.appendChild(container);
|
|
12
|
+
const root = createRoot(container);
|
|
13
|
+
act(() => {
|
|
14
|
+
root.render(createElement(PasswordInput, props));
|
|
15
|
+
});
|
|
16
|
+
const unmount = () => {
|
|
17
|
+
act(() => {
|
|
18
|
+
root.unmount();
|
|
19
|
+
});
|
|
20
|
+
container.remove();
|
|
21
|
+
};
|
|
22
|
+
return { container, unmount };
|
|
23
|
+
}
|
|
24
|
+
describe('PasswordInput — 자물쇠 아이콘 색상', () => {
|
|
25
|
+
// 자물쇠 색은 CSS 가 결정한다. 인라인 color 를 넘기면 상태 규칙이 무시되므로,
|
|
26
|
+
// "인라인 색이 없고 슬롯 아이콘 공통 클래스를 탄다" 는 것이 색상 반영의 전제다.
|
|
27
|
+
it('자물쇠에 인라인 색을 넘기지 않고 슬롯 아이콘 공통 클래스를 탄다', () => {
|
|
28
|
+
// Given/When: 기본 상태로 렌더하면
|
|
29
|
+
const view = mountPasswordInput();
|
|
30
|
+
// Then: 상태 규칙을 타는 클래스가 붙고 인라인 색은 없다
|
|
31
|
+
const lockIcon = view.container.querySelector('.ncua-input__left-icon');
|
|
32
|
+
expect(lockIcon).not.toBeNull();
|
|
33
|
+
expect(lockIcon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
34
|
+
expect(lockIcon?.hasAttribute('color')).toBe(false);
|
|
35
|
+
view.unmount();
|
|
36
|
+
});
|
|
37
|
+
// 값이 있는 채로 비활성화되면 has-value 가 살아 있어 자물쇠가 진하게 남던 문제
|
|
38
|
+
it('값이 있는 채로 disabled 여도 자물쇠가 상태 규칙 대상으로 남는다', () => {
|
|
39
|
+
// Given/When: 값이 있는 비활성화 상태로 렌더하면
|
|
40
|
+
const view = mountPasswordInput({ value: 'secret', disabled: true, onChange: vi.fn() });
|
|
41
|
+
// Then: 루트에 is-disabled 와 has-value 가 함께 붙고, 자물쇠는 공통 클래스를 유지한다
|
|
42
|
+
const root = view.container.querySelector('.ncua-input');
|
|
43
|
+
expect(root?.classList.contains('is-disabled')).toBe(true);
|
|
44
|
+
expect(root?.classList.contains('has-value')).toBe(true);
|
|
45
|
+
expect(view.container.querySelector('.ncua-input__left-icon')?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
46
|
+
view.unmount();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -44,6 +44,7 @@ export * from './navigation/vertical-tab';
|
|
|
44
44
|
export * from './overlays/dropdown';
|
|
45
45
|
export * from './overlays/modal';
|
|
46
46
|
export * from './overlays/notification';
|
|
47
|
+
export * from './overlays/postcode-search-modal';
|
|
47
48
|
export * from './overlays/tooltip';
|
|
48
49
|
export * from './select-dropdown';
|
|
49
50
|
export * from './shared';
|
|
@@ -51,6 +51,7 @@ export * from './navigation/vertical-tab';
|
|
|
51
51
|
export * from './overlays/dropdown';
|
|
52
52
|
export * from './overlays/modal';
|
|
53
53
|
export * from './overlays/notification';
|
|
54
|
+
export * from './overlays/postcode-search-modal';
|
|
54
55
|
export * from './overlays/tooltip';
|
|
55
56
|
// Shared
|
|
56
57
|
export * from './select-dropdown';
|
|
@@ -13,6 +13,10 @@ const DECIMAL_RADIX = 10;
|
|
|
13
13
|
const SUPPORTS_STYLE_HOISTING = Number.parseInt(reactVersion, DECIMAL_RADIX) >= STYLE_HOISTING_MIN_REACT_MAJOR;
|
|
14
14
|
const STYLE_KEY = 'ncua-scoped';
|
|
15
15
|
const STYLE_HOISTING_PROPS = { href: STYLE_KEY, precedence: 'default' };
|
|
16
|
+
// SSR 은 <style> 의 자식 텍스트를 HTML 이스케이프하지만(' -> '), 브라우저는 <style>
|
|
17
|
+
// 안을 raw text 로 읽어 되돌리지 않는다. 그러면 CSS 가 깨지고 하이드레이션도 불일치한다.
|
|
18
|
+
// innerHTML 로 넣으면 서버가 원문 그대로 내보낸다.
|
|
19
|
+
const STYLE_CONTENT = { __html: NCUA_SCOPED_CSS };
|
|
16
20
|
/**
|
|
17
21
|
* NCUA 스타일을 이 엘리먼트 하위로 한정하는 경계.
|
|
18
22
|
*
|
|
@@ -42,6 +46,6 @@ export const NcuaScope = forwardRef(({ className, children }, ref) => {
|
|
|
42
46
|
const styleAlreadyMounted = useContext(NcuaStyleMountedContext);
|
|
43
47
|
const shouldRenderStyle = SUPPORTS_STYLE_HOISTING || !styleAlreadyMounted;
|
|
44
48
|
return (_jsxs(NcuaStyleMountedContext.Provider, { value: true, children: [shouldRenderStyle &&
|
|
45
|
-
(SUPPORTS_STYLE_HOISTING ? (_jsx("style", { ...STYLE_HOISTING_PROPS,
|
|
49
|
+
(SUPPORTS_STYLE_HOISTING ? (_jsx("style", { ...STYLE_HOISTING_PROPS, dangerouslySetInnerHTML: STYLE_CONTENT })) : (_jsx("style", { "data-ncua-scope": STYLE_KEY, dangerouslySetInnerHTML: STYLE_CONTENT }))), _jsx("div", { ref: ref, className: classnames('ncua-scope', className), children: children })] }));
|
|
46
50
|
});
|
|
47
51
|
NcuaScope.displayName = 'NcuaScope';
|
|
@@ -9,7 +9,9 @@ export const Pagination = ({ className = '', totalCount, itemCountPerPage = 10,
|
|
|
9
9
|
const [start, setStart] = useState(1);
|
|
10
10
|
const noPrev = breakPoint === 'mo' ? currentPage === 1 : start === 1;
|
|
11
11
|
const noNext = breakPoint === 'mo' ? currentPage >= totalPage : start + pageCount - 1 >= totalPage;
|
|
12
|
-
|
|
12
|
+
// mo 는 CSS 가 페이지 번호 목록과 first/last 를 숨겨 prev/next 만 남는다.
|
|
13
|
+
// pc 기준(totalPage > pageCount)을 그대로 쓰면 페이지가 적을 때 이동 수단이 하나도 없다.
|
|
14
|
+
const showJumpPageButton = breakPoint === 'mo' ? totalPage > 1 : totalPage > pageCount;
|
|
13
15
|
const handleClickButton = (pageNum) => {
|
|
14
16
|
if (isFunction(onPageChange)) {
|
|
15
17
|
onPageChange(pageNum);
|
|
@@ -12,6 +12,12 @@ const CURRENT_PAGE = 91; // 100페이지 기준 마지막 그룹(91~100)을 보
|
|
|
12
12
|
const GROUP_START_100_PAGES = 91; // currentPage=91 일 때의 그룹 시작
|
|
13
13
|
const GROUP_START_50_PAGES = 41; // totalPage=50 으로 줄었을 때의 마지막 그룹 시작
|
|
14
14
|
const LAST_PAGE_50 = 50;
|
|
15
|
+
/** 페이지 그룹 크기(10) 이하로 나뉘는 건수 — 3페이지 */
|
|
16
|
+
const TOTAL_COUNT_3_PAGES = 30;
|
|
17
|
+
/** TOTAL_COUNT_3_PAGES 기준 총 페이지 수 = 마지막 페이지 번호 */
|
|
18
|
+
const LAST_PAGE_3 = 3;
|
|
19
|
+
/** 페이지가 하나뿐인 건수 */
|
|
20
|
+
const TOTAL_COUNT_1_PAGE = 8;
|
|
15
21
|
let root = null;
|
|
16
22
|
let container = null;
|
|
17
23
|
function render(props) {
|
|
@@ -34,6 +40,8 @@ afterEach(() => {
|
|
|
34
40
|
container = null;
|
|
35
41
|
});
|
|
36
42
|
const pageNums = (el) => Array.from(el.querySelectorAll('.ncua-pagination__page-num')).map((b) => Number(b.textContent));
|
|
43
|
+
const prevOf = (el) => el.querySelector('.ncua-pagination__before');
|
|
44
|
+
const nextOf = (el) => el.querySelector('.ncua-pagination__next');
|
|
37
45
|
describe('#332 Pagination — totalPage 축소 시 start 보정', () => {
|
|
38
46
|
it('totalPage 가 줄면 start 를 마지막 그룹으로 보정하고 범위 밖 페이지 버튼을 표시하지 않는다', () => {
|
|
39
47
|
// 1) 100 페이지에서 91 페이지 보는 중 → 91~100 그룹 표시
|
|
@@ -57,3 +65,69 @@ describe('#332 Pagination — totalPage 축소 시 start 보정', () => {
|
|
|
57
65
|
expect(Math.max(...nums)).toBeLessThanOrEqual(LAST_PAGE_50);
|
|
58
66
|
});
|
|
59
67
|
});
|
|
68
|
+
describe('Pagination — mo 모드 이전/다음 버튼 노출', () => {
|
|
69
|
+
it('페이지가 그룹 크기 이하여도 mo 에서는 이전·다음 버튼이 노출된다', () => {
|
|
70
|
+
// Given/When: 3페이지(그룹 크기 10 이하) 분량을 mo 로 렌더하면
|
|
71
|
+
const el = render({
|
|
72
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
73
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
74
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
75
|
+
currentPage: 1,
|
|
76
|
+
breakPoint: 'mo',
|
|
77
|
+
});
|
|
78
|
+
// Then: mo 는 페이지 번호 목록이 CSS 로 숨겨지므로 이전·다음 버튼이 있어야 이동할 수 있다
|
|
79
|
+
expect(prevOf(el)).not.toBeNull();
|
|
80
|
+
expect(nextOf(el)).not.toBeNull();
|
|
81
|
+
});
|
|
82
|
+
it('첫 페이지에서는 이전 버튼이, 마지막 페이지에서는 다음 버튼이 비활성이다', () => {
|
|
83
|
+
// Given/When: 3페이지 중 첫 페이지를 mo 로 렌더하면
|
|
84
|
+
const first = render({
|
|
85
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
86
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
87
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
88
|
+
currentPage: 1,
|
|
89
|
+
breakPoint: 'mo',
|
|
90
|
+
});
|
|
91
|
+
// Then: 이전만 막히고 다음은 누를 수 있다
|
|
92
|
+
expect(prevOf(first)?.disabled).toBe(true);
|
|
93
|
+
expect(nextOf(first)?.disabled).toBe(false);
|
|
94
|
+
// When: 마지막 페이지로 이동하면
|
|
95
|
+
const last = render({
|
|
96
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
97
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
98
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
99
|
+
currentPage: LAST_PAGE_3,
|
|
100
|
+
breakPoint: 'mo',
|
|
101
|
+
});
|
|
102
|
+
// Then: 반대로 다음이 막힌다
|
|
103
|
+
expect(prevOf(last)?.disabled).toBe(false);
|
|
104
|
+
expect(nextOf(last)?.disabled).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
it('페이지가 하나뿐이면 mo 에서도 이전·다음 버튼을 내보내지 않는다', () => {
|
|
107
|
+
// Given/When: 1페이지 분량을 mo 로 렌더하면
|
|
108
|
+
const el = render({
|
|
109
|
+
totalCount: TOTAL_COUNT_1_PAGE,
|
|
110
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
111
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
112
|
+
currentPage: 1,
|
|
113
|
+
breakPoint: 'mo',
|
|
114
|
+
});
|
|
115
|
+
// Then: 이동할 곳이 없으므로 버튼도 없다
|
|
116
|
+
expect(prevOf(el)).toBeNull();
|
|
117
|
+
expect(nextOf(el)).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
it('pc 는 기존대로 페이지 그룹을 넘길 때만 점프 버튼을 노출한다', () => {
|
|
120
|
+
// Given/When: 3페이지 분량을 pc 로 렌더하면
|
|
121
|
+
const el = render({
|
|
122
|
+
totalCount: TOTAL_COUNT_3_PAGES,
|
|
123
|
+
itemCountPerPage: ITEM_COUNT_PER_PAGE,
|
|
124
|
+
pageCount: PAGE_GROUP_SIZE,
|
|
125
|
+
currentPage: 1,
|
|
126
|
+
breakPoint: 'pc',
|
|
127
|
+
});
|
|
128
|
+
// Then: 페이지 번호로 직접 이동하므로 점프 버튼은 나오지 않는다
|
|
129
|
+
expect(prevOf(el)).toBeNull();
|
|
130
|
+
expect(nextOf(el)).toBeNull();
|
|
131
|
+
expect(pageNums(el)).toHaveLength(LAST_PAGE_3);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -40,6 +40,12 @@ interface FullWidthNotificationProps extends Omit<ComponentPropsWithoutRef<'div'
|
|
|
40
40
|
* 다시보지 않기 이벤트 핸들러 (선택사항)
|
|
41
41
|
*/
|
|
42
42
|
onHidePermanently?: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* 다시보지 않기 버튼의 라벨 (선택사항)
|
|
45
|
+
* `onHidePermanently`를 지정했을 때만 렌더된다.
|
|
46
|
+
* @default '다시보지 않기'
|
|
47
|
+
*/
|
|
48
|
+
hidePermanentlyLabel?: ReactNode;
|
|
43
49
|
}
|
|
44
50
|
declare const FullWidthNotification: import("react").ForwardRefExoticComponent<FullWidthNotificationProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
45
51
|
export type { FullWidthNotificationProps };
|
|
@@ -3,6 +3,7 @@ import { AlertTriangle, CheckCircle, InfoCircle, MessageChatSquare, XClose } fro
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
5
5
|
import { COLOR } from '../../../../constant/color';
|
|
6
|
+
import { HIDE_PERMANENTLY_LABEL } from '../../../../constant/notification';
|
|
6
7
|
const iconMap = {
|
|
7
8
|
neutral: MessageChatSquare,
|
|
8
9
|
error: AlertTriangle,
|
|
@@ -17,7 +18,7 @@ const iconColorMap = {
|
|
|
17
18
|
success: 'green600',
|
|
18
19
|
info: 'violet600',
|
|
19
20
|
};
|
|
20
|
-
const FullWidthNotification = forwardRef(({ title, supportingText, color = 'neutral', onClose, className, actions, autoClose = 0, supportTextLink, onHidePermanently, ...rest }, ref) => {
|
|
21
|
+
const FullWidthNotification = forwardRef(({ title, supportingText, color = 'neutral', onClose, className, actions, autoClose = 0, supportTextLink, onHidePermanently, hidePermanentlyLabel = HIDE_PERMANENTLY_LABEL.fullWidth, ...rest }, ref) => {
|
|
21
22
|
const [shouldRemove, setShouldRemove] = useState(false);
|
|
22
23
|
const closeIconSize = {
|
|
23
24
|
width: 20,
|
|
@@ -51,6 +52,6 @@ const FullWidthNotification = forwardRef(({ title, supportingText, color = 'neut
|
|
|
51
52
|
return (_jsx("div", { ref: ref, className: classNames('ncua-full-width-notification', `ncua-full-width-notification--${color}`, className), role: "alert", ...rest, children: _jsx("div", { className: "ncua-full-width-notification__container", children: _jsxs("div", { className: "ncua-full-width-notification__content", children: [_jsxs("div", { className: "ncua-full-width-notification__content-wrapper", children: [(() => {
|
|
52
53
|
const IconComponent = iconMap[color];
|
|
53
54
|
return (_jsx(IconComponent, { width: 16, height: 16, color: COLOR[iconColorMap[color]], className: "ncua-full-width-notification__icon" }));
|
|
54
|
-
})(), _jsxs("div", { className: "ncua-full-width-notification__text-container", children: [_jsx("span", { className: "ncua-full-width-notification__title", children: title }), !supportTextLink && supportingText && (_jsx("span", { className: "ncua-full-width-notification__supporting-text", children: supportingText })), supportTextLink && supportingText && (_jsx("a", { href: supportTextLink, className: "ncua-full-width-notification__link", children: _jsx("span", { className: "ncua-full-width-notification__supporting-text", children: supportingText }) }))] })] }), _jsxs("div", { className: "ncua-full-width-notification__actions-container", children: [actions && (_jsx("div", { className: "ncua-full-width-notification__actions", children: actions.map((action) => (_jsx("button", { onClick: action.onClick, className: classNames('ncua-notification__action-button', `ncua-notification__action-button--${action.hierarchy || 'text'}`), children: action.label }, `${action.label}-${action.hierarchy}`))) })), onHidePermanently && (_jsx("button", { type: "button", className: classNames('ncua-notification__action-button', 'ncua-notification__action-button--text', 'ncua-full-width-notification__link'), onClick: onHidePermanently, children:
|
|
55
|
+
})(), _jsxs("div", { className: "ncua-full-width-notification__text-container", children: [_jsx("span", { className: "ncua-full-width-notification__title", children: title }), !supportTextLink && supportingText && (_jsx("span", { className: "ncua-full-width-notification__supporting-text", children: supportingText })), supportTextLink && supportingText && (_jsx("a", { href: supportTextLink, className: "ncua-full-width-notification__link", children: _jsx("span", { className: "ncua-full-width-notification__supporting-text", children: supportingText }) }))] })] }), _jsxs("div", { className: "ncua-full-width-notification__actions-container", children: [actions && (_jsx("div", { className: "ncua-full-width-notification__actions", children: actions.map((action) => (_jsx("button", { onClick: action.onClick, className: classNames('ncua-notification__action-button', `ncua-notification__action-button--${action.hierarchy || 'text'}`), children: action.label }, `${action.label}-${action.hierarchy}`))) })), onHidePermanently && (_jsx("button", { type: "button", className: classNames('ncua-notification__action-button', 'ncua-notification__action-button--text', 'ncua-full-width-notification__link'), onClick: onHidePermanently, children: hidePermanentlyLabel })), onClose && (_jsx("button", { type: "button", className: "ncua-full-width-notification__close-button", onClick: onClose, "aria-label": "\uC54C\uB9BC \uB2EB\uAE30", children: _jsx(XClose, { ...closeIconSize, color: COLOR[iconColorMap[color]] }) }))] })] }) }) }));
|
|
55
56
|
});
|
|
56
57
|
export { FullWidthNotification };
|
|
@@ -35,6 +35,12 @@ interface MessageNotificationProps extends Omit<ComponentPropsWithoutRef<'div'>,
|
|
|
35
35
|
* 다시보지 않기 이벤트 핸들러 (선택사항)
|
|
36
36
|
*/
|
|
37
37
|
onHidePermanently?: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* 다시보지 않기 버튼의 라벨 (선택사항)
|
|
40
|
+
* `onHidePermanently`를 지정했을 때만 렌더된다.
|
|
41
|
+
* @default '다시 보지 않기'
|
|
42
|
+
*/
|
|
43
|
+
hidePermanentlyLabel?: ReactNode;
|
|
38
44
|
}
|
|
39
45
|
declare const MessageNotification: import("react").ForwardRefExoticComponent<MessageNotificationProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
40
46
|
export type { MessageNotificationProps };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AlertCircle, AlertTriangle, CheckCircle, Pin02
|
|
2
|
+
import { AlertCircle, AlertTriangle, CheckCircle, Pin02 } from '@ncds/ui-admin-icon';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import { forwardRef } from 'react';
|
|
5
5
|
import { COLOR } from '../../../../constant/color';
|
|
6
|
-
import {
|
|
7
|
-
import { FeaturedIcon, } from '../../image-and-icons/featured-icon/FeaturedIcon';
|
|
6
|
+
import { MessageNotificationActions, MessageNotificationContent } from './MessageNotificationParts';
|
|
8
7
|
const iconMap = {
|
|
9
8
|
neutral: Pin02,
|
|
10
9
|
error: AlertTriangle,
|
|
@@ -17,18 +16,10 @@ const iconColorMap = {
|
|
|
17
16
|
warning: 'orange500',
|
|
18
17
|
success: 'green600',
|
|
19
18
|
};
|
|
20
|
-
const MessageNotification = forwardRef(({ title, supportingText, icon: Icon, color = 'neutral', onClose, className, actions, onHidePermanently, ...rest }, ref) => {
|
|
19
|
+
const MessageNotification = forwardRef(({ title, supportingText, icon: Icon, color = 'neutral', onClose, className, actions, onHidePermanently, hidePermanentlyLabel, ...rest }, ref) => {
|
|
21
20
|
// message 타입은 neutral, error, warning, success 4가지 색상만 지원
|
|
22
21
|
const validColor = color === 'info' ? 'neutral' : color;
|
|
23
|
-
|
|
24
|
-
const featuredIconProps = {
|
|
25
|
-
icon: Icon || iconMap[validColor] || Pin02,
|
|
26
|
-
size: 'lg',
|
|
27
|
-
color: iconColor,
|
|
28
|
-
theme: 'light-circle',
|
|
29
|
-
};
|
|
30
|
-
const closeIconColor = COLOR[iconColorMap[validColor] || 'gray700'];
|
|
31
|
-
return (_jsx("div", { ref: ref, className: classNames('ncua-message-notification', `ncua-message-notification--${validColor}`, className), role: "alert", ...rest, children: _jsx("div", { className: "ncua-message-notification__container", children: _jsxs("div", { className: "ncua-message-notification__content", children: [_jsxs("div", { className: "ncua-message-notification__content-wrapper", children: [iconMap[validColor] && (_jsx(FeaturedIcon, { ...featuredIconProps, className: "ncua-message-notification__icon" })), _jsxs("div", { className: "ncua-message-notification__text-container", children: [_jsx("span", { className: "ncua-message-notification__title", children: title }), supportingText && _jsx("span", { className: "ncua-message-notification__supporting-text", children: supportingText })] })] }), _jsx("div", { className: "ncua-message-notification__actions-container", children: actions && (_jsx("div", { className: "ncua-message-notification__actions", children: actions.map((action) => (_jsx(Button, { size: "xs", hierarchy: action.hierarchy || 'text', label: action.label, onClick: action?.onClick }, `${action.label}-${action.hierarchy}`))) })) }), _jsxs("div", { className: "ncua-message-notification__footer-container", children: [onHidePermanently && (_jsx("button", { type: "button", className: classNames('ncua-notification__action-button', 'ncua-notification__action-button--link', 'ncua-message-notification__hide-link'), onClick: onHidePermanently, children: "\uB2E4\uC2DC \uBCF4\uC9C0 \uC54A\uAE30" })), onClose && (_jsx("button", { type: "button", className: "ncua-message-notification__close-button", onClick: onClose, "aria-label": "\uC54C\uB9BC \uB2EB\uAE30", children: _jsx(XClose, { width: 20, height: 20, color: closeIconColor }) }))] })] }) }) }));
|
|
22
|
+
return (_jsx("div", { ref: ref, className: classNames('ncua-message-notification', `ncua-message-notification--${validColor}`, className), role: "alert", ...rest, children: _jsx("div", { className: "ncua-message-notification__container", children: _jsxs("div", { className: "ncua-message-notification__content", children: [_jsx(MessageNotificationContent, { title: title, supportingText: supportingText, icon: Icon || iconMap[validColor], iconColor: validColor, showIcon: Boolean(iconMap[validColor]) }), _jsx(MessageNotificationActions, { actions: actions, onClose: onClose, onHidePermanently: onHidePermanently, hidePermanentlyLabel: hidePermanentlyLabel, closeIconColor: COLOR[iconColorMap[validColor] || 'gray700'] })] }) }) }));
|
|
32
23
|
});
|
|
33
24
|
MessageNotification.displayName = 'MessageNotification';
|
|
34
25
|
export { MessageNotification };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { SlotIconComponent } from '../../../types/side-slot';
|
|
3
|
+
import { type FeaturedIconColor } from '../../image-and-icons/featured-icon/FeaturedIcon';
|
|
4
|
+
import type { NotificationAction } from './Notification';
|
|
5
|
+
interface MessageNotificationContentProps {
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
supportingText?: ReactNode;
|
|
8
|
+
icon?: SlotIconComponent;
|
|
9
|
+
iconColor: FeaturedIconColor;
|
|
10
|
+
showIcon: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** 아이콘 + 제목/서포팅 텍스트 영역. */
|
|
13
|
+
declare const MessageNotificationContent: ({ title, supportingText, icon, iconColor, showIcon, }: MessageNotificationContentProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
interface MessageNotificationActionsProps {
|
|
15
|
+
actions?: NotificationAction[];
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
onHidePermanently?: () => void;
|
|
18
|
+
hidePermanentlyLabel?: ReactNode;
|
|
19
|
+
closeIconColor: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 액션 버튼 그룹 + 닫기 그룹을 감싸는 우측 영역.
|
|
23
|
+
* 이 컨테이너의 gap(12px)이 두 그룹 사이 간격을 만든다.
|
|
24
|
+
* 표시할 요소가 하나도 없으면 빈 컨테이너를 남기지 않는다.
|
|
25
|
+
*/
|
|
26
|
+
declare const MessageNotificationActions: ({ actions, onClose, onHidePermanently, hidePermanentlyLabel, closeIconColor, }: MessageNotificationActionsProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
27
|
+
export type { MessageNotificationActionsProps, MessageNotificationContentProps };
|
|
28
|
+
export { MessageNotificationActions, MessageNotificationContent };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Pin02, XClose } from '@ncds/ui-admin-icon';
|
|
3
|
+
import { HIDE_PERMANENTLY_LABEL } from '../../../../constant/notification';
|
|
4
|
+
import { Button } from '../../action/button';
|
|
5
|
+
import { FeaturedIcon, } from '../../image-and-icons/featured-icon/FeaturedIcon';
|
|
6
|
+
/** 아이콘 + 제목/서포팅 텍스트 영역. */
|
|
7
|
+
const MessageNotificationContent = ({ title, supportingText, icon, iconColor, showIcon, }) => {
|
|
8
|
+
const featuredIconProps = {
|
|
9
|
+
icon: icon || Pin02,
|
|
10
|
+
size: 'md',
|
|
11
|
+
color: iconColor,
|
|
12
|
+
theme: 'light-circle',
|
|
13
|
+
};
|
|
14
|
+
return (_jsxs("div", { className: "ncua-message-notification__content-wrapper", children: [showIcon && _jsx(FeaturedIcon, { ...featuredIconProps, className: "ncua-message-notification__icon" }), _jsxs("div", { className: "ncua-message-notification__text-container", children: [_jsx("span", { className: "ncua-message-notification__title", children: title }), supportingText && _jsx("span", { className: "ncua-message-notification__supporting-text", children: supportingText })] })] }));
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 액션 버튼 그룹 + 닫기 그룹을 감싸는 우측 영역.
|
|
18
|
+
* 이 컨테이너의 gap(12px)이 두 그룹 사이 간격을 만든다.
|
|
19
|
+
* 표시할 요소가 하나도 없으면 빈 컨테이너를 남기지 않는다.
|
|
20
|
+
*/
|
|
21
|
+
const MessageNotificationActions = ({ actions, onClose, onHidePermanently, hidePermanentlyLabel = HIDE_PERMANENTLY_LABEL.message, closeIconColor, }) => {
|
|
22
|
+
const hasActions = Boolean(actions?.length);
|
|
23
|
+
const hasFooter = Boolean(onHidePermanently || onClose);
|
|
24
|
+
if (!hasActions && !hasFooter)
|
|
25
|
+
return null;
|
|
26
|
+
return (_jsxs("div", { className: "ncua-message-notification__actions-container", children: [hasActions && (_jsx("div", { className: "ncua-message-notification__actions", children: actions?.map((action) => (_jsx(Button, { size: "xs", hierarchy: action.hierarchy || 'text', label: action.label, onClick: action?.onClick }, `${action.label}-${action.hierarchy}`))) })), hasFooter && (_jsxs("div", { className: "ncua-message-notification__footer-container", children: [onHidePermanently && (_jsx("button", { type: "button", className: "ncua-notification__action-button ncua-message-notification__hide-link", onClick: onHidePermanently, children: hidePermanentlyLabel })), onClose && (_jsx("button", { type: "button", className: "ncua-message-notification__close-button", onClick: onClose, "aria-label": "\uC54C\uB9BC \uB2EB\uAE30", children: _jsx(XClose, { width: 20, height: 20, color: closeIconColor }) }))] }))] }));
|
|
27
|
+
};
|
|
28
|
+
export { MessageNotificationActions, MessageNotificationContent };
|
|
@@ -16,9 +16,10 @@ interface NotificationAction {
|
|
|
16
16
|
*/
|
|
17
17
|
onClick?: () => void;
|
|
18
18
|
/**
|
|
19
|
-
* 액션 버튼
|
|
19
|
+
* 액션 버튼 색상. `ButtonTheme` 중 알림에서 허용하는 4가지만 지원한다.
|
|
20
|
+
* @default 'text'
|
|
20
21
|
*/
|
|
21
|
-
hierarchy?: Extract<ButtonTheme, 'text' | 'text-gray'>;
|
|
22
|
+
hierarchy?: Extract<ButtonTheme, 'secondary' | 'secondary-gray' | 'text' | 'text-gray'>;
|
|
22
23
|
}
|
|
23
24
|
interface NotificationProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {
|
|
24
25
|
/**
|
|
@@ -67,6 +68,14 @@ interface NotificationProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title
|
|
|
67
68
|
* message, full-width 타입에서 사용 가능
|
|
68
69
|
*/
|
|
69
70
|
onHidePermanently?: () => void;
|
|
71
|
+
/**
|
|
72
|
+
* 다시보지 않기 버튼의 라벨 (선택사항)
|
|
73
|
+
* message, full-width 타입에서 사용 가능하며 `onHidePermanently`를 지정했을 때만 렌더된다.
|
|
74
|
+
*
|
|
75
|
+
* 기본값은 타입별로 다르다 — message는 `'다시 보지 않기'`, full-width는 `'다시보지 않기'`.
|
|
76
|
+
* 표기가 통일되어 있지 않은 것은 현행 유지이며, 통일은 별도 결정 사항이다.
|
|
77
|
+
*/
|
|
78
|
+
hidePermanentlyLabel?: ReactNode;
|
|
70
79
|
/**
|
|
71
80
|
* Portal 마운트 여부. `floating` 타입에서만 동작한다.
|
|
72
81
|
*
|
package/dist/temp/src/components/overlays/notification/__tests__/MessageNotification.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|