@ncds/ui-admin 1.8.21-alpha.1 → 1.8.21-alpha.2
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/forms-and-input/input-base/InputBase.js +11 -5
- 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/generated/scoped-css.js +1 -1
- package/dist/esm/src/components/forms-and-input/input-base/InputBase.js +11 -5
- 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/generated/scoped-css.js +1 -1
- 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 +9 -5
- 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/generated/scoped-css.js +1 -1
- 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/ui-admin/assets/styles/style.css +35 -2
- package/dist/ui-admin/assets/styles/style.scoped.css +35 -2
- package/package.json +1 -1
|
@@ -59,7 +59,7 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
|
|
|
59
59
|
input.removeEventListener('input', syncFromInput);
|
|
60
60
|
};
|
|
61
61
|
}, [props.value]);
|
|
62
|
-
const renderOutsideSlot = slot => {
|
|
62
|
+
const renderOutsideSlot = (slot, position) => {
|
|
63
63
|
if (slot.type === 'custom' && slot.placement !== 'outside') {
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
@@ -73,7 +73,7 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
|
|
|
73
73
|
});
|
|
74
74
|
case 'text':
|
|
75
75
|
return (0, _jsxRuntime.jsx)("span", {
|
|
76
|
-
className: (0, _classnames.default)(
|
|
76
|
+
className: (0, _classnames.default)(`ncua-input__${position}-text`, `ncua-input__${position}-text--${size}`),
|
|
77
77
|
children: slot.text
|
|
78
78
|
});
|
|
79
79
|
case 'custom':
|
|
@@ -90,7 +90,12 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
|
|
|
90
90
|
return (0, _jsxRuntime.jsx)("div", {
|
|
91
91
|
className: "ncua-input__icon-wrap",
|
|
92
92
|
children: (0, _jsxRuntime.jsx)(SlotIcon, {
|
|
93
|
-
className: (0, _classnames.default)(`ncua-input__${position}-icon`,
|
|
93
|
+
className: (0, _classnames.default)(`ncua-input__${position}-icon`,
|
|
94
|
+
// color 를 지정하지 않았을 때만 상태별 색(Placeholder/Filled/Focused/Disabled)을 CSS 에 맡긴다.
|
|
95
|
+
// 지정한 경우에는 이 클래스를 붙이지 않아 지정 색이 그대로 유지된다.
|
|
96
|
+
{
|
|
97
|
+
'ncua-input__slot-icon': !slot.color
|
|
98
|
+
}, slot.className),
|
|
94
99
|
color: slot.color ? _color.COLOR[slot.color] : undefined,
|
|
95
100
|
width: slot.size ?? iconSvgSize[size],
|
|
96
101
|
height: slot.size ?? iconSvgSize[size]
|
|
@@ -162,13 +167,14 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
|
|
|
162
167
|
'full-width': fullWidth,
|
|
163
168
|
'ncua-input__trailing-button': trailingElement?.type === 'button',
|
|
164
169
|
'ncua-input__leading-text-wrap': leadingElement?.type === 'text',
|
|
170
|
+
'ncua-input__trailing-text-wrap': trailingElement?.type === 'text',
|
|
165
171
|
'has-value': hasValue
|
|
166
172
|
}, className),
|
|
167
173
|
children: [renderLabel(), (0, _jsxRuntime.jsxs)("div", {
|
|
168
174
|
className: "ncua-input__content-wrap",
|
|
169
175
|
children: [(0, _jsxRuntime.jsxs)("div", {
|
|
170
176
|
className: "ncua-input__content",
|
|
171
|
-
children: [leadingElement && renderOutsideSlot(leadingElement), (0, _jsxRuntime.jsxs)("div", {
|
|
177
|
+
children: [leadingElement && renderOutsideSlot(leadingElement, 'leading'), (0, _jsxRuntime.jsxs)("div", {
|
|
172
178
|
className: (0, _classnames.default)('ncua-input__field', `ncua-input__field--${size}`),
|
|
173
179
|
children: [leadingElement && renderInsideSlot(leadingElement, 'left'), (0, _jsxRuntime.jsx)("input", {
|
|
174
180
|
ref: node => {
|
|
@@ -184,7 +190,7 @@ const InputBase = exports.InputBase = /*#__PURE__*/(0, _react.forwardRef)((_ref,
|
|
|
184
190
|
maxLength: maxLength,
|
|
185
191
|
...props
|
|
186
192
|
}), renderClearButton(), renderStatusIcon(), trailingElement && renderInsideSlot(trailingElement, 'right')]
|
|
187
|
-
}), trailingElement && renderOutsideSlot(trailingElement)]
|
|
193
|
+
}), trailingElement && renderOutsideSlot(trailingElement, 'trailing')]
|
|
188
194
|
}), showTextCount && maxLength && (0, _jsxRuntime.jsxs)("div", {
|
|
189
195
|
className: "ncua-input__field-text-count",
|
|
190
196
|
children: [(0, _jsxRuntime.jsx)("output", {
|
|
@@ -5,6 +5,7 @@ var _react = require("react");
|
|
|
5
5
|
var _client = require("react-dom/client");
|
|
6
6
|
var _testUtils = require("react-dom/test-utils");
|
|
7
7
|
var _vitest = require("vitest");
|
|
8
|
+
var _color = require("../../../../../constant/color");
|
|
8
9
|
var _InputBase = require("../InputBase");
|
|
9
10
|
// @vitest-environment jsdom
|
|
10
11
|
|
|
@@ -133,6 +134,55 @@ function mountInputBase() {
|
|
|
133
134
|
view.unmount();
|
|
134
135
|
});
|
|
135
136
|
});
|
|
137
|
+
(0, _vitest.describe)('InputBase — 슬롯 아이콘 상태 색상', () => {
|
|
138
|
+
// 상태별 색은 CSS 가 결정한다. jsdom 에서는 스타일시트를 로드하지 않으므로
|
|
139
|
+
// "상태 규칙을 타는 클래스가 붙었는지" 와 "지정 색이 인라인으로 남았는지" 로 검증한다.
|
|
140
|
+
const leftIconOf = container => container.querySelector('.ncua-input__left-icon');
|
|
141
|
+
(0, _vitest.it)('color 를 지정하지 않으면 상태 색상 클래스가 붙는다', () => {
|
|
142
|
+
// Given/When: color 없이 슬롯 아이콘을 렌더하면
|
|
143
|
+
const view = mountInputBase({
|
|
144
|
+
leadingElement: {
|
|
145
|
+
type: 'icon',
|
|
146
|
+
icon: _uiAdminIcon.SearchLg
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
// Then: 상태 규칙을 타는 클래스가 붙고, 인라인 색은 지정되지 않는다
|
|
150
|
+
const icon = leftIconOf(view.container);
|
|
151
|
+
(0, _vitest.expect)(icon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
152
|
+
(0, _vitest.expect)(icon?.hasAttribute('color')).toBe(false);
|
|
153
|
+
view.unmount();
|
|
154
|
+
});
|
|
155
|
+
(0, _vitest.it)('color 를 지정하면 상태 색상 클래스가 붙지 않고 지정 색이 유지된다', () => {
|
|
156
|
+
// Given/When: color 를 지정해 렌더하면
|
|
157
|
+
const view = mountInputBase({
|
|
158
|
+
leadingElement: {
|
|
159
|
+
type: 'icon',
|
|
160
|
+
icon: _uiAdminIcon.SearchLg,
|
|
161
|
+
color: 'blue500'
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
// Then: 상태 규칙에서 제외되고 지정한 색이 그대로 남는다
|
|
165
|
+
const icon = leftIconOf(view.container);
|
|
166
|
+
(0, _vitest.expect)(icon?.classList.contains('ncua-input__slot-icon')).toBe(false);
|
|
167
|
+
(0, _vitest.expect)(icon?.getAttribute('color')).toBe(_color.COLOR.blue500);
|
|
168
|
+
view.unmount();
|
|
169
|
+
});
|
|
170
|
+
(0, _vitest.it)('slot.className 을 함께 주어도 상태 색상 클래스가 유지된다', () => {
|
|
171
|
+
// Given/When: className 을 추가로 넘기면
|
|
172
|
+
const view = mountInputBase({
|
|
173
|
+
leadingElement: {
|
|
174
|
+
type: 'icon',
|
|
175
|
+
icon: _uiAdminIcon.SearchLg,
|
|
176
|
+
className: 'my-icon'
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
// Then: 두 클래스가 모두 붙는다
|
|
180
|
+
const icon = leftIconOf(view.container);
|
|
181
|
+
(0, _vitest.expect)(icon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
182
|
+
(0, _vitest.expect)(icon?.classList.contains('my-icon')).toBe(true);
|
|
183
|
+
view.unmount();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
136
186
|
(0, _vitest.describe)('InputBase — has-value 상태 클래스', () => {
|
|
137
187
|
const typeInto = (container, text) => {
|
|
138
188
|
const input = container.querySelector('input');
|
|
@@ -181,4 +231,70 @@ function mountInputBase() {
|
|
|
181
231
|
(0, _vitest.expect)(view.container.querySelector('.ncua-input')?.classList.contains('has-value')).toBe(true);
|
|
182
232
|
view.unmount();
|
|
183
233
|
});
|
|
234
|
+
});
|
|
235
|
+
(0, _vitest.describe)('InputBase — 텍스트 슬롯', () => {
|
|
236
|
+
(0, _vitest.it)('leadingElement 텍스트는 leading 클래스와 wrap 클래스가 붙는다', () => {
|
|
237
|
+
const {
|
|
238
|
+
container,
|
|
239
|
+
unmount
|
|
240
|
+
} = mountInputBase({
|
|
241
|
+
leadingElement: {
|
|
242
|
+
type: 'text',
|
|
243
|
+
text: 'https://'
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
const text = container.querySelector('.ncua-input__leading-text');
|
|
247
|
+
(0, _vitest.expect)(text?.textContent).toBe('https://');
|
|
248
|
+
(0, _vitest.expect)(container.querySelector('.ncua-input__leading-text-wrap')).not.toBeNull();
|
|
249
|
+
unmount();
|
|
250
|
+
});
|
|
251
|
+
(0, _vitest.it)('trailingElement 텍스트는 trailing 클래스와 wrap 클래스가 붙는다', () => {
|
|
252
|
+
const {
|
|
253
|
+
container,
|
|
254
|
+
unmount
|
|
255
|
+
} = mountInputBase({
|
|
256
|
+
trailingElement: {
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: 'px'
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const text = container.querySelector('.ncua-input__trailing-text');
|
|
262
|
+
(0, _vitest.expect)(text?.textContent).toBe('px');
|
|
263
|
+
(0, _vitest.expect)(container.querySelector('.ncua-input__trailing-text-wrap')).not.toBeNull();
|
|
264
|
+
// leading 클래스가 섞이면 모서리·테두리 방향이 반대가 된다
|
|
265
|
+
(0, _vitest.expect)(container.querySelector('.ncua-input__leading-text')).toBeNull();
|
|
266
|
+
unmount();
|
|
267
|
+
});
|
|
268
|
+
(0, _vitest.it)('trailingElement 텍스트는 입력 필드 뒤에 온다', () => {
|
|
269
|
+
const {
|
|
270
|
+
container,
|
|
271
|
+
unmount
|
|
272
|
+
} = mountInputBase({
|
|
273
|
+
trailingElement: {
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: 'px'
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
const content = container.querySelector('.ncua-input__content');
|
|
279
|
+
const children = Array.from(content?.children ?? []);
|
|
280
|
+
const fieldIndex = children.findIndex(el => el.classList.contains('ncua-input__field'));
|
|
281
|
+
const textIndex = children.findIndex(el => el.classList.contains('ncua-input__trailing-text'));
|
|
282
|
+
(0, _vitest.expect)(fieldIndex).toBeGreaterThanOrEqual(0);
|
|
283
|
+
(0, _vitest.expect)(textIndex).toBeGreaterThan(fieldIndex);
|
|
284
|
+
unmount();
|
|
285
|
+
});
|
|
286
|
+
(0, _vitest.it)('사이즈 modifier 가 함께 붙는다', () => {
|
|
287
|
+
const {
|
|
288
|
+
container,
|
|
289
|
+
unmount
|
|
290
|
+
} = mountInputBase({
|
|
291
|
+
size: 'sm',
|
|
292
|
+
trailingElement: {
|
|
293
|
+
type: 'text',
|
|
294
|
+
text: 'px'
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
(0, _vitest.expect)(container.querySelector('.ncua-input__trailing-text--sm')).not.toBeNull();
|
|
298
|
+
unmount();
|
|
299
|
+
});
|
|
184
300
|
});
|
|
@@ -33,9 +33,9 @@ const PasswordInput = exports.PasswordInput = /*#__PURE__*/(0, _react.forwardRef
|
|
|
33
33
|
type: isVisible ? 'text' : 'password',
|
|
34
34
|
leadingElement: {
|
|
35
35
|
type: 'icon',
|
|
36
|
-
icon: _uiAdminIcon.Lock01
|
|
37
|
-
//
|
|
38
|
-
|
|
36
|
+
icon: _uiAdminIcon.Lock01
|
|
37
|
+
// color 를 넘기지 않아 슬롯 아이콘 공통 규칙(상태별 색)을 그대로 따른다.
|
|
38
|
+
// 여기서 props.value 로 판정하면 uncontrolled Input 에서 영원히 빈 값 취급된다.
|
|
39
39
|
},
|
|
40
40
|
trailingElement: {
|
|
41
41
|
type: 'custom',
|
package/dist/cjs/src/components/forms-and-input/password-input/__tests__/PasswordInput.test.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _react = require("react");
|
|
4
|
+
var _client = require("react-dom/client");
|
|
5
|
+
var _testUtils = require("react-dom/test-utils");
|
|
6
|
+
var _vitest = require("vitest");
|
|
7
|
+
var _PasswordInput = require("../PasswordInput");
|
|
8
|
+
// @vitest-environment jsdom
|
|
9
|
+
|
|
10
|
+
// React 18에서 act() 사용 시 필요한 플래그 (미설정 시 console.error 경고 발생)
|
|
11
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
12
|
+
function mountPasswordInput() {
|
|
13
|
+
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
14
|
+
const container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
const root = (0, _client.createRoot)(container);
|
|
17
|
+
(0, _testUtils.act)(() => {
|
|
18
|
+
root.render(/*#__PURE__*/(0, _react.createElement)(_PasswordInput.PasswordInput, props));
|
|
19
|
+
});
|
|
20
|
+
const unmount = () => {
|
|
21
|
+
(0, _testUtils.act)(() => {
|
|
22
|
+
root.unmount();
|
|
23
|
+
});
|
|
24
|
+
container.remove();
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
container,
|
|
28
|
+
unmount
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
(0, _vitest.describe)('PasswordInput — 자물쇠 아이콘 색상', () => {
|
|
32
|
+
// 자물쇠 색은 CSS 가 결정한다. 인라인 color 를 넘기면 상태 규칙이 무시되므로,
|
|
33
|
+
// "인라인 색이 없고 슬롯 아이콘 공통 클래스를 탄다" 는 것이 색상 반영의 전제다.
|
|
34
|
+
(0, _vitest.it)('자물쇠에 인라인 색을 넘기지 않고 슬롯 아이콘 공통 클래스를 탄다', () => {
|
|
35
|
+
// Given/When: 기본 상태로 렌더하면
|
|
36
|
+
const view = mountPasswordInput();
|
|
37
|
+
// Then: 상태 규칙을 타는 클래스가 붙고 인라인 색은 없다
|
|
38
|
+
const lockIcon = view.container.querySelector('.ncua-input__left-icon');
|
|
39
|
+
(0, _vitest.expect)(lockIcon).not.toBeNull();
|
|
40
|
+
(0, _vitest.expect)(lockIcon?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
41
|
+
(0, _vitest.expect)(lockIcon?.hasAttribute('color')).toBe(false);
|
|
42
|
+
view.unmount();
|
|
43
|
+
});
|
|
44
|
+
// 값이 있는 채로 비활성화되면 has-value 가 살아 있어 자물쇠가 진하게 남던 문제
|
|
45
|
+
(0, _vitest.it)('값이 있는 채로 disabled 여도 자물쇠가 상태 규칙 대상으로 남는다', () => {
|
|
46
|
+
// Given/When: 값이 있는 비활성화 상태로 렌더하면
|
|
47
|
+
const view = mountPasswordInput({
|
|
48
|
+
value: 'secret',
|
|
49
|
+
disabled: true,
|
|
50
|
+
onChange: _vitest.vi.fn()
|
|
51
|
+
});
|
|
52
|
+
// Then: 루트에 is-disabled 와 has-value 가 함께 붙고, 자물쇠는 공통 클래스를 유지한다
|
|
53
|
+
const root = view.container.querySelector('.ncua-input');
|
|
54
|
+
(0, _vitest.expect)(root?.classList.contains('is-disabled')).toBe(true);
|
|
55
|
+
(0, _vitest.expect)(root?.classList.contains('has-value')).toBe(true);
|
|
56
|
+
(0, _vitest.expect)(view.container.querySelector('.ncua-input__left-icon')?.classList.contains('ncua-input__slot-icon')).toBe(true);
|
|
57
|
+
view.unmount();
|
|
58
|
+
});
|
|
59
|
+
});
|