@ncds/ui-admin 1.8.21-alpha.9 → 1.8.21
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/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/cjs/src/components/forms-and-input/input-base/InputBase.js +13 -7
- package/dist/cjs/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +16 -11
- package/dist/cjs/src/components/overlays/notification/CalloutNotification.js +6 -1
- package/dist/cjs/src/components/overlays/notification/Notification.js +22 -2
- package/dist/cjs/src/components/overlays/notification/__tests__/CalloutNotification.test.js +128 -0
- package/dist/cjs/src/components/overlays/notification/__tests__/Notification.test.js +83 -0
- package/dist/cjs/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +8 -2
- package/dist/cjs/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +1 -1
- package/dist/cjs/src/generated/scoped-css.js +1 -1
- package/dist/esm/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/esm/src/components/forms-and-input/input-base/InputBase.js +13 -7
- package/dist/esm/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +16 -11
- package/dist/esm/src/components/overlays/notification/CalloutNotification.js +6 -1
- package/dist/esm/src/components/overlays/notification/Notification.js +22 -2
- package/dist/esm/src/components/overlays/notification/__tests__/CalloutNotification.test.js +125 -0
- package/dist/esm/src/components/overlays/notification/__tests__/Notification.test.js +80 -0
- package/dist/esm/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +9 -3
- package/dist/esm/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +1 -1
- package/dist/esm/src/generated/scoped-css.js +1 -1
- package/dist/temp/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
- package/dist/temp/src/components/feedback-and-status/badge/Badge.js +1 -1
- package/dist/temp/src/components/forms-and-input/input-base/InputBase.js +10 -4
- package/dist/temp/src/components/forms-and-input/input-base/__tests__/InputBase.test.js +14 -12
- package/dist/temp/src/components/overlays/notification/CalloutNotification.d.ts +3 -1
- package/dist/temp/src/components/overlays/notification/CalloutNotification.js +5 -1
- package/dist/temp/src/components/overlays/notification/Notification.d.ts +12 -2
- package/dist/temp/src/components/overlays/notification/Notification.js +9 -4
- package/dist/temp/src/components/overlays/notification/__tests__/CalloutNotification.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/notification/__tests__/CalloutNotification.test.js +108 -0
- package/dist/temp/src/components/overlays/notification/__tests__/Notification.test.d.ts +1 -0
- package/dist/temp/src/components/overlays/notification/__tests__/Notification.test.js +71 -0
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +1 -1
- package/dist/temp/src/components/overlays/postcode-search-modal/PostcodeSearchModal.js +6 -2
- package/dist/temp/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js +1 -1
- package/dist/temp/src/generated/scoped-css.js +1 -1
- package/dist/types/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
- package/dist/types/src/components/overlays/notification/CalloutNotification.d.ts +3 -1
- package/dist/types/src/components/overlays/notification/Notification.d.ts +12 -2
- package/dist/types/src/components/overlays/notification/__tests__/CalloutNotification.test.d.ts +1 -0
- package/dist/types/src/components/overlays/notification/__tests__/Notification.test.d.ts +1 -0
- package/dist/types/src/components/overlays/postcode-search-modal/PostcodeSearchModal.d.ts +1 -1
- package/dist/ui-admin/assets/styles/style.css +52 -32
- package/package.json +1 -1
- package/dist/ui-admin/assets/styles/style.scoped.css +0 -8595
|
@@ -5,12 +5,18 @@ import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
|
5
5
|
import { COLOR } from '../../../../constant/color';
|
|
6
6
|
import { HintText } from '../../shared/hintText/HintText';
|
|
7
7
|
import { Label } from '../../shared/label/Label';
|
|
8
|
-
// 슬롯 아이콘(leadingElement/trailingElement)
|
|
8
|
+
// 슬롯 아이콘(leadingElement/trailingElement)은 인풋 크기에 맞춰 커진다.
|
|
9
9
|
// slot.size 를 명시하면 그 값이 우선한다.
|
|
10
10
|
const iconSvgSize = {
|
|
11
11
|
xs: 14,
|
|
12
12
|
sm: 20
|
|
13
13
|
};
|
|
14
|
+
// 상태 아이콘(clear·validation·destructive)은 인풋 크기와 무관하게 16px 로 고정한다.
|
|
15
|
+
// 슬롯 아이콘은 sm 에서 20px 로 커지므로 두 상수를 하나로 합치지 않는다.
|
|
16
|
+
const statusIconSvgSize = {
|
|
17
|
+
xs: 14,
|
|
18
|
+
sm: 16
|
|
19
|
+
};
|
|
14
20
|
const InputBase = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
15
21
|
let {
|
|
16
22
|
size = 'xs',
|
|
@@ -118,8 +124,8 @@ const InputBase = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
118
124
|
onClick: onClearText,
|
|
119
125
|
children: _jsx(X, {
|
|
120
126
|
className: "ncua-input__clear-icon",
|
|
121
|
-
width:
|
|
122
|
-
height:
|
|
127
|
+
width: statusIconSvgSize[size],
|
|
128
|
+
height: statusIconSvgSize[size]
|
|
123
129
|
})
|
|
124
130
|
});
|
|
125
131
|
};
|
|
@@ -129,8 +135,8 @@ const InputBase = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
129
135
|
className: "ncua-input__icon-wrap ncua-input__right-icon",
|
|
130
136
|
children: _jsx(InfoCircle, {
|
|
131
137
|
className: "ncua-input__destructive-icon",
|
|
132
|
-
width:
|
|
133
|
-
height:
|
|
138
|
+
width: statusIconSvgSize[size],
|
|
139
|
+
height: statusIconSvgSize[size]
|
|
134
140
|
})
|
|
135
141
|
});
|
|
136
142
|
}
|
|
@@ -139,8 +145,8 @@ const InputBase = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
139
145
|
className: "ncua-input__icon-wrap ncua-input__right-icon",
|
|
140
146
|
children: _jsx(CheckCircle, {
|
|
141
147
|
className: "ncua-input__validation-icon",
|
|
142
|
-
width:
|
|
143
|
-
height:
|
|
148
|
+
width: statusIconSvgSize[size],
|
|
149
|
+
height: statusIconSvgSize[size]
|
|
144
150
|
})
|
|
145
151
|
});
|
|
146
152
|
}
|
|
@@ -77,8 +77,9 @@ describe('InputBase — 아이콘 사이즈', () => {
|
|
|
77
77
|
height: svg?.getAttribute('height')
|
|
78
78
|
};
|
|
79
79
|
};
|
|
80
|
-
// 슬롯
|
|
81
|
-
|
|
80
|
+
// 슬롯 아이콘은 인풋 크기에 연동돼 sm 에서 20px 로 커지지만,
|
|
81
|
+
// 상태 아이콘(clear·validation·destructive)은 16px 로 고정한다.
|
|
82
|
+
it.each([['xs', '14', '14'], ['sm', '20', '16']])('size=%s 일 때 슬롯 아이콘은 %spx, 상태 아이콘은 %spx로 렌더된다', (size, slotExpected, statusExpected) => {
|
|
82
83
|
// Given/When: 슬롯 아이콘과 clear 버튼, destructive 상태를 함께 렌더하면
|
|
83
84
|
const view = mountInputBase({
|
|
84
85
|
size,
|
|
@@ -90,23 +91,27 @@ describe('InputBase — 아이콘 사이즈', () => {
|
|
|
90
91
|
icon: SearchLg
|
|
91
92
|
}
|
|
92
93
|
});
|
|
93
|
-
// Then:
|
|
94
|
-
const
|
|
95
|
-
width:
|
|
96
|
-
height:
|
|
94
|
+
// Then: 슬롯 아이콘만 인풋 크기를 따라가고, 상태 아이콘은 16px 을 넘지 않는다
|
|
95
|
+
const slotSize = {
|
|
96
|
+
width: slotExpected,
|
|
97
|
+
height: slotExpected
|
|
98
|
+
};
|
|
99
|
+
const statusSize = {
|
|
100
|
+
width: statusExpected,
|
|
101
|
+
height: statusExpected
|
|
97
102
|
};
|
|
98
|
-
expect(iconSizeOf(view.container, '.ncua-input__left-icon')).toEqual(
|
|
99
|
-
expect(iconSizeOf(view.container, '.ncua-input__clear-icon')).toEqual(
|
|
100
|
-
expect(iconSizeOf(view.container, '.ncua-input__destructive-icon')).toEqual(
|
|
103
|
+
expect(iconSizeOf(view.container, '.ncua-input__left-icon')).toEqual(slotSize);
|
|
104
|
+
expect(iconSizeOf(view.container, '.ncua-input__clear-icon')).toEqual(statusSize);
|
|
105
|
+
expect(iconSizeOf(view.container, '.ncua-input__destructive-icon')).toEqual(statusSize);
|
|
101
106
|
view.unmount();
|
|
102
107
|
});
|
|
103
|
-
it.each([['xs', '14'], ['sm', '
|
|
108
|
+
it.each([['xs', '14'], ['sm', '16']])('size=%s 일 때 validation 아이콘은 %spx로 렌더된다', (size, expected) => {
|
|
104
109
|
// Given/When: validation 상태로 렌더하면
|
|
105
110
|
const view = mountInputBase({
|
|
106
111
|
size,
|
|
107
112
|
validation: true
|
|
108
113
|
});
|
|
109
|
-
// Then: 다른 아이콘과 같은 크기다
|
|
114
|
+
// Then: 다른 상태 아이콘과 같은 크기다
|
|
110
115
|
expect(iconSizeOf(view.container, '.ncua-input__validation-icon')).toEqual({
|
|
111
116
|
width: expected,
|
|
112
117
|
height: expected
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
|
+
const WIDTH_CLASS = {
|
|
5
|
+
full: 'ncua-callout-notification--full',
|
|
6
|
+
fit: 'ncua-callout-notification--fit'
|
|
7
|
+
};
|
|
4
8
|
const CalloutNotification = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
5
9
|
let {
|
|
6
10
|
color = 'neutral',
|
|
7
11
|
className,
|
|
8
12
|
title,
|
|
13
|
+
width,
|
|
9
14
|
...rest
|
|
10
15
|
} = _ref;
|
|
11
16
|
return _jsx("div", {
|
|
12
17
|
ref: ref,
|
|
13
|
-
className: classNames('ncua-callout-notification', `ncua-callout-notification--${color}`, className),
|
|
18
|
+
className: classNames('ncua-callout-notification', `ncua-callout-notification--${color}`, width && WIDTH_CLASS[width], className),
|
|
14
19
|
...rest,
|
|
15
20
|
children: title
|
|
16
21
|
});
|
|
@@ -4,10 +4,15 @@ import { CalloutNotification } from './CalloutNotification';
|
|
|
4
4
|
import { FloatingNotification } from './FloatingNotification';
|
|
5
5
|
import { FullWidthNotification } from './FullWidthNotification';
|
|
6
6
|
import { MessageNotification } from './MessageNotification';
|
|
7
|
-
const Notification = /*#__PURE__*/forwardRef(
|
|
7
|
+
const Notification = /*#__PURE__*/forwardRef(
|
|
8
|
+
// 단일 type 에만 유효한 prop 은 rest 에서 분리해 해당 분기로만 명시 전달한다.
|
|
9
|
+
// rest 에 남기면 다른 type 의 하위 컴포넌트가 div 로 스프레드해 DOM 속성으로 출력된다.
|
|
10
|
+
(_ref, ref) => {
|
|
8
11
|
let {
|
|
9
12
|
type = 'floating',
|
|
10
13
|
color = 'neutral',
|
|
14
|
+
width,
|
|
15
|
+
supportTextLink,
|
|
11
16
|
...rest
|
|
12
17
|
} = _ref;
|
|
13
18
|
if (type === 'floating') {
|
|
@@ -20,6 +25,7 @@ const Notification = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
20
25
|
if (type === 'full-width') {
|
|
21
26
|
return _jsx(FullWidthNotification, {
|
|
22
27
|
color: color,
|
|
28
|
+
supportTextLink: supportTextLink,
|
|
23
29
|
...rest,
|
|
24
30
|
ref: ref
|
|
25
31
|
});
|
|
@@ -32,9 +38,23 @@ const Notification = /*#__PURE__*/forwardRef((_ref, ref) => {
|
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
40
|
if (type === 'callout') {
|
|
41
|
+
// 나머지 3개 type 이 공유하는 prop 은 callout 이 지원하지 않으므로 여기서 제외한다.
|
|
42
|
+
const {
|
|
43
|
+
icon: _icon,
|
|
44
|
+
supportingText: _supportingText,
|
|
45
|
+
actions: _actions,
|
|
46
|
+
autoClose: _autoClose,
|
|
47
|
+
onClose: _onClose,
|
|
48
|
+
onHidePermanently: _onHidePermanently,
|
|
49
|
+
hidePermanentlyLabel: _hidePermanentlyLabel,
|
|
50
|
+
portal: _portal,
|
|
51
|
+
...calloutProps
|
|
52
|
+
} = rest;
|
|
35
53
|
return _jsx(CalloutNotification, {
|
|
36
54
|
color: color,
|
|
37
|
-
|
|
55
|
+
width: width,
|
|
56
|
+
...calloutProps,
|
|
57
|
+
ref: ref
|
|
38
58
|
});
|
|
39
59
|
}
|
|
40
60
|
return null;
|
|
@@ -0,0 +1,125 @@
|
|
|
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 { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { CalloutNotification } from '../CalloutNotification';
|
|
7
|
+
import { Notification } from '../Notification';
|
|
8
|
+
// React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
|
|
9
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
10
|
+
const BASE = 'ncua-callout-notification';
|
|
11
|
+
let root = null;
|
|
12
|
+
let container = null;
|
|
13
|
+
const mount = element => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
root = createRoot(container);
|
|
17
|
+
act(() => {
|
|
18
|
+
root?.render(element);
|
|
19
|
+
});
|
|
20
|
+
const rendered = container.firstElementChild;
|
|
21
|
+
if (!(rendered instanceof HTMLElement)) throw new Error('callout 이 렌더되지 않았다');
|
|
22
|
+
return rendered;
|
|
23
|
+
};
|
|
24
|
+
const renderCallout = function () {
|
|
25
|
+
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
26
|
+
return mount(/*#__PURE__*/createElement(CalloutNotification, {
|
|
27
|
+
title: '안내 문구',
|
|
28
|
+
...props
|
|
29
|
+
}));
|
|
30
|
+
};
|
|
31
|
+
const renderNotification = function () {
|
|
32
|
+
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
33
|
+
return mount(/*#__PURE__*/createElement(Notification, {
|
|
34
|
+
type: 'callout',
|
|
35
|
+
title: '안내 문구',
|
|
36
|
+
...props
|
|
37
|
+
}));
|
|
38
|
+
};
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
act(() => {
|
|
41
|
+
root?.unmount();
|
|
42
|
+
});
|
|
43
|
+
container?.remove();
|
|
44
|
+
root = null;
|
|
45
|
+
container = null;
|
|
46
|
+
});
|
|
47
|
+
describe('CalloutNotification — width prop', () => {
|
|
48
|
+
it('width 미지정 시 너비 클래스를 붙이지 않는다 (부모 너비를 채우는 기본 스타일 유지)', () => {
|
|
49
|
+
const element = renderCallout();
|
|
50
|
+
expect(element.classList.contains('ncua-callout-notification--full')).toBe(false);
|
|
51
|
+
expect(element.classList.contains('ncua-callout-notification--fit')).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it("width='full' 은 --full modifier 를 붙인다", () => {
|
|
54
|
+
const element = renderCallout({
|
|
55
|
+
width: 'full'
|
|
56
|
+
});
|
|
57
|
+
expect(element.classList.contains('ncua-callout-notification--full')).toBe(true);
|
|
58
|
+
expect(element.classList.contains('ncua-callout-notification--fit')).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it("width='fit' 은 --fit modifier 를 붙인다", () => {
|
|
61
|
+
const element = renderCallout({
|
|
62
|
+
width: 'fit'
|
|
63
|
+
});
|
|
64
|
+
expect(element.classList.contains('ncua-callout-notification--fit')).toBe(true);
|
|
65
|
+
expect(element.classList.contains('ncua-callout-notification--full')).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
it('width 는 DOM 속성으로 새지 않는다', () => {
|
|
68
|
+
const element = renderCallout({
|
|
69
|
+
width: 'fit'
|
|
70
|
+
});
|
|
71
|
+
expect(element.hasAttribute('width')).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it('width 는 베이스/컬러 클래스와 외부 className 을 유지한 채 추가된다', () => {
|
|
74
|
+
const element = renderCallout({
|
|
75
|
+
width: 'fit',
|
|
76
|
+
color: 'info',
|
|
77
|
+
className: 'custom'
|
|
78
|
+
});
|
|
79
|
+
expect(element.classList.contains(BASE)).toBe(true);
|
|
80
|
+
expect(element.classList.contains(`${BASE}--info`)).toBe(true);
|
|
81
|
+
expect(element.classList.contains('custom')).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('Notification type="callout" — 위임 계약', () => {
|
|
85
|
+
it('width 를 CalloutNotification 으로 전달한다', () => {
|
|
86
|
+
const element = renderNotification({
|
|
87
|
+
width: 'fit'
|
|
88
|
+
});
|
|
89
|
+
expect(element.classList.contains(BASE)).toBe(true);
|
|
90
|
+
expect(element.classList.contains('ncua-callout-notification--fit')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
// Notification 의 callout 분기가 제외하는 prop 전체. 여기 누락된 prop 은 DOM 속성으로 출력된다.
|
|
93
|
+
it('callout 이 지원하지 않는 prop 은 DOM 속성으로 새지 않는다', () => {
|
|
94
|
+
const element = renderNotification({
|
|
95
|
+
icon: () => null,
|
|
96
|
+
supportingText: '보조 문구',
|
|
97
|
+
supportTextLink: 'https://example.com',
|
|
98
|
+
actions: [{
|
|
99
|
+
label: '확인',
|
|
100
|
+
onClick: () => undefined
|
|
101
|
+
}],
|
|
102
|
+
autoClose: 3000,
|
|
103
|
+
portal: true,
|
|
104
|
+
onClose: () => undefined,
|
|
105
|
+
onHidePermanently: () => undefined,
|
|
106
|
+
hidePermanentlyLabel: '다시 보지 않기'
|
|
107
|
+
});
|
|
108
|
+
for (const attr of ['icon', 'supportingtext', 'supporttextlink', 'actions', 'autoclose', 'portal', 'onclose', 'onhidepermanently', 'hidepermanentlylabel']) {
|
|
109
|
+
expect(element.hasAttribute(attr), attr).toBe(false);
|
|
110
|
+
}
|
|
111
|
+
expect(element.textContent).toBe('안내 문구');
|
|
112
|
+
});
|
|
113
|
+
it('ref 를 callout 루트 엘리먼트로 전달한다', () => {
|
|
114
|
+
let captured = null;
|
|
115
|
+
mount(/*#__PURE__*/createElement(Notification, {
|
|
116
|
+
type: 'callout',
|
|
117
|
+
title: '안내 문구',
|
|
118
|
+
ref: node => {
|
|
119
|
+
captured = node;
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
122
|
+
expect(captured).not.toBeNull();
|
|
123
|
+
expect(captured.classList.contains(BASE)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
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 { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
6
|
+
import { Notification } from '../Notification';
|
|
7
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
8
|
+
let root = null;
|
|
9
|
+
let container = null;
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
// jsdom 미구현 API 보정 — FloatingNotification 의 모바일 리스너가 window.matchMedia 를 호출한다.
|
|
12
|
+
if (typeof window.matchMedia !== 'function') {
|
|
13
|
+
const stub = query => ({
|
|
14
|
+
matches: false,
|
|
15
|
+
media: query,
|
|
16
|
+
onchange: null,
|
|
17
|
+
addEventListener: () => undefined,
|
|
18
|
+
removeEventListener: () => undefined,
|
|
19
|
+
addListener: () => undefined,
|
|
20
|
+
removeListener: () => undefined,
|
|
21
|
+
dispatchEvent: () => false
|
|
22
|
+
});
|
|
23
|
+
window.matchMedia = stub;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
// 정의되지 않은 type 의 런타임 동작까지 고정하므로 props 를 넓게 받는다.
|
|
27
|
+
const render = props => {
|
|
28
|
+
container = document.createElement('div');
|
|
29
|
+
document.body.appendChild(container);
|
|
30
|
+
root = createRoot(container);
|
|
31
|
+
act(() => {
|
|
32
|
+
root?.render(/*#__PURE__*/createElement(Notification, {
|
|
33
|
+
title: '안내 문구',
|
|
34
|
+
...props
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
const rendered = container.firstElementChild;
|
|
38
|
+
return rendered instanceof HTMLElement ? rendered : null;
|
|
39
|
+
};
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
act(() => {
|
|
42
|
+
root?.unmount();
|
|
43
|
+
});
|
|
44
|
+
container?.remove();
|
|
45
|
+
root = null;
|
|
46
|
+
container = null;
|
|
47
|
+
});
|
|
48
|
+
describe('Notification — type 별 위임', () => {
|
|
49
|
+
it.each([['floating', 'ncua-floating-notification'], ['full-width', 'ncua-full-width-notification'], ['message', 'ncua-message-notification'], ['callout', 'ncua-callout-notification']])('type=%s 는 %s 를 렌더한다', (type, baseClass) => {
|
|
50
|
+
const element = render({
|
|
51
|
+
type
|
|
52
|
+
});
|
|
53
|
+
expect(element?.classList.contains(baseClass)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('type 미지정은 floating 으로 렌더한다', () => {
|
|
56
|
+
const element = render({});
|
|
57
|
+
expect(element?.classList.contains('ncua-floating-notification')).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
it.each(['floating', 'full-width', 'message'])('type=%s 에 callout 전용 width 를 주어도 DOM 속성으로 새지 않는다', type => {
|
|
60
|
+
const element = render({
|
|
61
|
+
type,
|
|
62
|
+
width: 'fit'
|
|
63
|
+
});
|
|
64
|
+
expect(element?.hasAttribute('width')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
it.each(['floating', 'message'])('type=%s 에 full-width 전용 supportTextLink 를 주어도 DOM 속성으로 새지 않는다', type => {
|
|
67
|
+
const element = render({
|
|
68
|
+
type,
|
|
69
|
+
supportingText: '보조',
|
|
70
|
+
supportTextLink: 'https://example.com'
|
|
71
|
+
});
|
|
72
|
+
expect(element?.hasAttribute('supporttextlink')).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
it('정의되지 않은 type 은 아무것도 렌더하지 않는다', () => {
|
|
75
|
+
const element = render({
|
|
76
|
+
type: 'banner'
|
|
77
|
+
});
|
|
78
|
+
expect(element).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -7,11 +7,13 @@ import { InputBase } from '../../forms-and-input/input-base';
|
|
|
7
7
|
import { Radio } from '../../forms-and-input/radio';
|
|
8
8
|
import { Pagination } from '../../navigation/pagination';
|
|
9
9
|
import { Modal } from '../modal';
|
|
10
|
-
import {
|
|
10
|
+
import { Tip } from './PostcodeSearchModalTip';
|
|
11
11
|
/** 기획서 기준값 */
|
|
12
12
|
const PAGE_SIZE = 15;
|
|
13
13
|
/** 페이지 번호 그룹 크기 — 초과 시 Pagination 이 점프 버튼을 노출한다 */
|
|
14
14
|
const PAGER_WINDOW = 10;
|
|
15
|
+
/** 검색 조합 안내 문구를 띄우는 기준 — 결과가 이 수를 넘을 때만 노출한다 */
|
|
16
|
+
const SEARCH_HINT_MIN_TOTAL_COUNT = 30;
|
|
15
17
|
/** 반응형 전환 기준 — assets/styles/base/_variable.scss 의 $breakpoint 와 동일 */
|
|
16
18
|
const MOBILE_BREAKPOINT = 768;
|
|
17
19
|
/** 본문 스크롤 컨테이너. Modal.Content 가 overflow-y:auto 를 갖는다. */
|
|
@@ -117,6 +119,8 @@ const PostcodeSearchModal = _ref2 => {
|
|
|
117
119
|
}, [open]);
|
|
118
120
|
const hasError = Boolean(errorCode);
|
|
119
121
|
const hasResults = !hasError && results.length > 0;
|
|
122
|
+
// 결과가 적으면 굳이 검색 조합을 안내하지 않는다.
|
|
123
|
+
const showSearchHint = hasResults && totalCount > SEARCH_HINT_MIN_TOTAL_COUNT;
|
|
120
124
|
// 검색을 실행한 검색어와 현재 입력값이 같을 때만 "결과 없음"으로 본다.
|
|
121
125
|
// 입력 중에는 두 값이 달라지고, 응답을 기다리는 동안에는 loading 이 막는다.
|
|
122
126
|
const showEmptyState = !hasError && !loading && results.length === 0 && searchQuery !== '' && searchQuery === searchedQuery;
|
|
@@ -160,6 +164,8 @@ const PostcodeSearchModal = _ref2 => {
|
|
|
160
164
|
size: "sm",
|
|
161
165
|
fullWidth: true,
|
|
162
166
|
value: searchQuery,
|
|
167
|
+
clearText: !!searchQuery,
|
|
168
|
+
onClearText: () => onSearchQueryChange(''),
|
|
163
169
|
onChange: handleSearchQueryChange,
|
|
164
170
|
onKeyDown: handleSearchKeyDown,
|
|
165
171
|
placeholder: SEARCH_PLACEHOLDER,
|
|
@@ -172,10 +178,10 @@ const PostcodeSearchModal = _ref2 => {
|
|
|
172
178
|
hierarchy: "secondary",
|
|
173
179
|
onClick: handleSearch
|
|
174
180
|
})]
|
|
175
|
-
}),
|
|
181
|
+
}), showSearchHint && _jsxs("div", {
|
|
176
182
|
children: [_jsx("p", {
|
|
177
183
|
className: "ncua-postcode-search-modal__result-title",
|
|
178
|
-
children:
|
|
184
|
+
children: "\uC544\uB798 \uC870\uD569\uC73C\uB85C \uAC80\uC0C9 \uC2DC \uC8FC\uC18C\uB97C \uB354 \uC27D\uAC8C \uCC3E\uC744 \uC218 \uC788\uC5B4\uC694."
|
|
179
185
|
}), _jsx("p", {
|
|
180
186
|
className: "ncua-postcode-search-modal__result-desc",
|
|
181
187
|
children: "\uB3C4\uB85C\uBA85+\uAC74\uBB3C\uBC88\uD638, \uC9C0\uC5ED\uBA85+\uC9C0\uBC88, \uC9C0\uC5ED\uBA85+\uAC74\uBB3C\uBA85(\uC544\uD30C\uD2B8\uBA85)"
|
package/dist/esm/src/components/overlays/postcode-search-modal/__tests__/PostcodeSearchModal.test.js
CHANGED
|
@@ -284,7 +284,7 @@ describe('PostcodeSearchModal — Case 7: 결과 여러 페이지', () => {
|
|
|
284
284
|
expect(countLabelOf(el)?.textContent).toBe(`검색 ${LARGE_RESULT_COUNT}개`);
|
|
285
285
|
expect(itemsOf(el)).toHaveLength(PAGE_SIZE);
|
|
286
286
|
expect(tipOf(el)).toBeNull();
|
|
287
|
-
expect(resultTitleOf(el)?.textContent).toBe('아래 조합으로
|
|
287
|
+
expect(resultTitleOf(el)?.textContent).toBe('아래 조합으로 검색 시 주소를 더 쉽게 찾을 수 있어요.');
|
|
288
288
|
expect(resultDescOf(el)?.textContent).toBe('도로명+건물번호, 지역명+지번, 지역명+건물명(아파트명)');
|
|
289
289
|
});
|
|
290
290
|
it('검색 인풋과 안내 문구가 같은 고정 영역 안에 묶이고 결과 목록은 그 밖에 있다', () => {
|