@nurihaus/web-design-system 3.5.0 → 3.7.0
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/ai/components.md +21 -2
- package/dist/components/modal/modal-type.d.ts +11 -0
- package/dist/components/skeleton/skeleton.stories.d.ts +5 -5
- package/dist/components/toast/toast.stories.d.ts +8 -8
- package/dist/components.css +62 -1
- package/dist/index.js +38 -3
- package/dist/modal-with-double-button.js +38 -3
- package/dist/styles.css +53 -1
- package/package.json +1 -1
package/dist/ai/components.md
CHANGED
|
@@ -164,7 +164,7 @@ import { Callout } from '@nurihaus/web-design-system/callout'; // 이 컴포
|
|
|
164
164
|
import { Callout } from '@nurihaus/web-design-system'; // 루트. 첫 로드 JS 가 커진다
|
|
165
165
|
```
|
|
166
166
|
|
|
167
|
-
**언제 쓴다.** 버튼 같은 대상에 붙어 늘 떠 있는 짧은 라벨 말풍선에 쓴다(예: 로그인 버튼의 "최근 사용"). 꼬리 자리는 `tail`(`bottom-start` 기본·`bottom-end`·`top-start`·`top-end`)로 고른다. 버튼에 붙일 때는 Callout 을 직접 놓지 말고 CTAButton 의 `callout` prop 을 쓴다 — 모서리 자리를 DS 가 잡는다. 그 밖의 대상에 놓을 때는 자리(`position: absolute`·위치)를 쓰는 쪽이 `className` 으로 정한다. `.base-callout` 이 꼬리 기준으로 `position: relative` 를 가지므로 한 단계 구체적인 선택자(`.slot > .x`)로 덮는다.
|
|
167
|
+
**언제 쓴다.** 버튼 같은 대상에 붙어 늘 떠 있는 짧은 라벨 말풍선에 쓴다(예: 로그인 버튼의 "최근 사용"). 꼬리 자리는 `tail`(`bottom-start` 기본·`bottom-end`·`top-start`·`top-end`)로 고른다. 나타날 때 꼬리에서 튀어나온 뒤 세 번 둥실 움직이고 멈춘다(동작 줄이기 설정이면 움직이지 않는다). 모션은 쓰는 쪽이 고르지 않는다. 버튼에 붙일 때는 Callout 을 직접 놓지 말고 CTAButton 의 `callout` prop 을 쓴다 — 모서리 자리를 DS 가 잡는다. 그 밖의 대상에 놓을 때는 자리(`position: absolute`·위치)를 쓰는 쪽이 `className` 으로 정한다. 위치는 `top`·`right` 같은 값으로 잡는다 — `transform` 은 등장 모션이 쓰므로 덮어써진다. `.base-callout` 이 꼬리 기준으로 `position: relative` 를 가지므로 한 단계 구체적인 선택자(`.slot > .x`)로 덮는다.
|
|
168
168
|
|
|
169
169
|
**쓰지 않는다.** 마우스를 올리거나 포커스했을 때만 설명을 보여 주려면 Tooltip 을 쓴다. 상태값·분류 라벨은 OptionBadge 를 쓴다.
|
|
170
170
|
|
|
@@ -1019,7 +1019,22 @@ import { ModalWithDoubleButton } from '@nurihaus/web-design-system';
|
|
|
1019
1019
|
|
|
1020
1020
|
**언제 쓴다.** 삭제 확인처럼 제목, 설명, 버튼 두 개로 된 확인 대화상자에 쓴다. `ModalWithDoubleButton.Row`는 버튼을 가로로, `ModalWithDoubleButton.Column`은 세로로 배치하며, `left`와 `right`는 CTAButton props를 그대로 받는다.
|
|
1021
1021
|
|
|
1022
|
-
|
|
1022
|
+
**열고 닫기.** `open`·`onOpenChange`를 넘기면 부모가 열림을 정한다. 열리면 포커스가 창 안으로 들어가고, Esc·바깥 누름·X 아이콘(`showCloseIcon`)·두 버튼이 `onOpenChange(false)`를 부르며, 닫히면 포커스가 연 요소로 돌아온다. 바텀시트 안의 삭제 확인처럼 키보드로 닿아야 하는 곳은 이 방식으로 쓴다.
|
|
1023
|
+
|
|
1024
|
+
```tsx
|
|
1025
|
+
<ModalWithDoubleButton.Row
|
|
1026
|
+
open
|
|
1027
|
+
onOpenChange={() => {}}
|
|
1028
|
+
showCloseIcon
|
|
1029
|
+
title="항목을 삭제할까요?"
|
|
1030
|
+
left={{ text: '취소' }}
|
|
1031
|
+
right={{ text: '삭제', onClick: () => {} }}
|
|
1032
|
+
/>
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
`open`을 넘기지 않으면 예전처럼 렌더되는 동안 항상 보이고 포커스를 가두지 않는다. 이때는 부모가 조건부 렌더링으로 정하고 버튼 `onClick`에서 언마운트하며, X 아이콘은 동작이 없다.
|
|
1036
|
+
|
|
1037
|
+
**쓰지 않는다.** 제목·설명·버튼 두 개를 벗어나는 일반 모달은 Modal 을 쓴다.
|
|
1023
1038
|
|
|
1024
1039
|
```tsx
|
|
1025
1040
|
<ModalWithDoubleButton.Row
|
|
@@ -1034,6 +1049,8 @@ import { ModalWithDoubleButton } from '@nurihaus/web-design-system';
|
|
|
1034
1049
|
|
|
1035
1050
|
| prop | 타입 | 필수 | 기본값 |
|
|
1036
1051
|
| --- | --- | --- | --- |
|
|
1052
|
+
| `open` | `boolean` | | |
|
|
1053
|
+
| `onOpenChange` | `((open: boolean) => void)` | | |
|
|
1037
1054
|
| `title` | `string` | | |
|
|
1038
1055
|
| `subTitle` | `ReactNode` | | |
|
|
1039
1056
|
| `titleIcon` | `ReactNode` | | |
|
|
@@ -1050,6 +1067,8 @@ import { ModalWithDoubleButton } from '@nurihaus/web-design-system';
|
|
|
1050
1067
|
|
|
1051
1068
|
| prop | 타입 | 필수 | 기본값 |
|
|
1052
1069
|
| --- | --- | --- | --- |
|
|
1070
|
+
| `open` | `boolean` | | |
|
|
1071
|
+
| `onOpenChange` | `((open: boolean) => void)` | | |
|
|
1053
1072
|
| `title` | `string` | | |
|
|
1054
1073
|
| `subTitle` | `ReactNode` | | |
|
|
1055
1074
|
| `titleIcon` | `ReactNode` | | |
|
|
@@ -10,6 +10,9 @@ export interface ModalRootProps {
|
|
|
10
10
|
}
|
|
11
11
|
export interface ModalContainerProps extends AsChildProps, StyleProps {
|
|
12
12
|
forceMount?: true;
|
|
13
|
+
/** 열릴 때·닫힐 때 포커스를 옮기기 직전. Radix `Dialog.Content` 에 그대로 넘긴다 */
|
|
14
|
+
onOpenAutoFocus?: (event: Event) => void;
|
|
15
|
+
onCloseAutoFocus?: (event: Event) => void;
|
|
13
16
|
}
|
|
14
17
|
export interface ModalTriggerProps extends AsChildProps {
|
|
15
18
|
className?: string;
|
|
@@ -27,6 +30,14 @@ export interface ModalCloseProps extends AsChildProps {
|
|
|
27
30
|
className?: string;
|
|
28
31
|
}
|
|
29
32
|
export interface ModalWithDoubleButtonProps {
|
|
33
|
+
/**
|
|
34
|
+
* 넘기면 열림을 부모가 정한다. 열리면 포커스가 창 안으로 들어가고 닫히면 연 요소로 돌아오며,
|
|
35
|
+
* Esc·바깥 누름·X 아이콘·두 버튼이 `onOpenChange(false)` 를 부른다.
|
|
36
|
+
* 안 넘기면 예전처럼 렌더되는 동안 항상 보인다 (포커스를 가두지 않는다).
|
|
37
|
+
* `onOpenChange` 를 함께 넘겨야 닫힌다.
|
|
38
|
+
*/
|
|
39
|
+
open?: boolean;
|
|
40
|
+
onOpenChange?: (open: boolean) => void;
|
|
30
41
|
title?: string;
|
|
31
42
|
subTitle?: string | ReactNode;
|
|
32
43
|
titleIcon?: ReactNode;
|
|
@@ -38,10 +38,9 @@ declare const meta: {
|
|
|
38
38
|
animated: true;
|
|
39
39
|
};
|
|
40
40
|
decorators: ((Story: import("storybook/internal/csf").PartialStoryFn<import("@storybook/react-webpack5").ReactRenderer, {
|
|
41
|
-
|
|
41
|
+
className?: string | undefined | undefined;
|
|
42
42
|
style?: import("react").CSSProperties | undefined;
|
|
43
|
-
|
|
44
|
-
key?: import("react").Key | null | undefined;
|
|
43
|
+
children?: import("react").ReactNode;
|
|
45
44
|
defaultChecked?: boolean | undefined | undefined;
|
|
46
45
|
defaultValue?: string | number | readonly string[] | undefined;
|
|
47
46
|
suppressContentEditableWarning?: boolean | undefined | undefined;
|
|
@@ -49,7 +48,6 @@ declare const meta: {
|
|
|
49
48
|
accessKey?: string | undefined | undefined;
|
|
50
49
|
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | undefined | (string & {}) | undefined;
|
|
51
50
|
autoFocus?: boolean | undefined | undefined;
|
|
52
|
-
className?: string | undefined | undefined;
|
|
53
51
|
contentEditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
|
|
54
52
|
contextMenu?: string | undefined | undefined;
|
|
55
53
|
dir?: string | undefined | undefined;
|
|
@@ -59,8 +57,10 @@ declare const meta: {
|
|
|
59
57
|
id?: string | undefined | undefined;
|
|
60
58
|
lang?: string | undefined | undefined;
|
|
61
59
|
nonce?: string | undefined | undefined;
|
|
60
|
+
slot?: string | undefined | undefined;
|
|
62
61
|
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
63
62
|
tabIndex?: number | undefined | undefined;
|
|
63
|
+
title?: string | undefined | undefined;
|
|
64
64
|
translate?: "yes" | "no" | undefined | undefined;
|
|
65
65
|
radioGroup?: string | undefined | undefined;
|
|
66
66
|
role?: import("react").AriaRole | undefined;
|
|
@@ -143,7 +143,6 @@ declare const meta: {
|
|
|
143
143
|
"aria-valuemin"?: number | undefined | undefined;
|
|
144
144
|
"aria-valuenow"?: number | undefined | undefined;
|
|
145
145
|
"aria-valuetext"?: string | undefined | undefined;
|
|
146
|
-
children?: import("react").ReactNode;
|
|
147
146
|
dangerouslySetInnerHTML?: {
|
|
148
147
|
__html: string | TrustedHTML;
|
|
149
148
|
} | undefined | undefined;
|
|
@@ -305,6 +304,7 @@ declare const meta: {
|
|
|
305
304
|
onAnimationIterationCapture?: import("react").AnimationEventHandler<HTMLSpanElement> | undefined;
|
|
306
305
|
onTransitionEnd?: import("react").TransitionEventHandler<HTMLSpanElement> | undefined;
|
|
307
306
|
onTransitionEndCapture?: import("react").TransitionEventHandler<HTMLSpanElement> | undefined;
|
|
307
|
+
key?: import("react").Key | null | undefined;
|
|
308
308
|
height?: number | string | undefined;
|
|
309
309
|
width?: number | string | undefined;
|
|
310
310
|
shape?: (typeof shapes)[number] | undefined;
|
|
@@ -17,11 +17,8 @@ declare const meta: {
|
|
|
17
17
|
variant: "default";
|
|
18
18
|
};
|
|
19
19
|
decorators: ((Story: import("storybook/internal/csf").PartialStoryFn<import("@storybook/react-webpack5").ReactRenderer, {
|
|
20
|
-
|
|
20
|
+
className?: string | undefined;
|
|
21
21
|
style?: import("react").CSSProperties | undefined;
|
|
22
|
-
title: import("react").ReactNode;
|
|
23
|
-
track?: string | undefined;
|
|
24
|
-
key?: import("react").Key | null | undefined;
|
|
25
22
|
defaultChecked?: boolean | undefined | undefined;
|
|
26
23
|
defaultValue?: string | number | readonly string[] | undefined;
|
|
27
24
|
suppressContentEditableWarning?: boolean | undefined | undefined;
|
|
@@ -29,7 +26,6 @@ declare const meta: {
|
|
|
29
26
|
accessKey?: string | undefined | undefined;
|
|
30
27
|
autoCapitalize?: "off" | "none" | "on" | "sentences" | "words" | "characters" | undefined | (string & {}) | undefined;
|
|
31
28
|
autoFocus?: boolean | undefined | undefined;
|
|
32
|
-
className?: string | undefined;
|
|
33
29
|
contentEditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
|
|
34
30
|
contextMenu?: string | undefined | undefined;
|
|
35
31
|
dir?: string | undefined | undefined;
|
|
@@ -39,8 +35,10 @@ declare const meta: {
|
|
|
39
35
|
id?: string | undefined | undefined;
|
|
40
36
|
lang?: string | undefined | undefined;
|
|
41
37
|
nonce?: string | undefined | undefined;
|
|
38
|
+
slot?: string | undefined | undefined;
|
|
42
39
|
spellCheck?: (boolean | "true" | "false") | undefined;
|
|
43
40
|
tabIndex?: number | undefined | undefined;
|
|
41
|
+
title: import("react").ReactNode;
|
|
44
42
|
translate?: "yes" | "no" | undefined | undefined;
|
|
45
43
|
radioGroup?: string | undefined | undefined;
|
|
46
44
|
role?: import("react").AriaRole | undefined;
|
|
@@ -284,12 +282,14 @@ declare const meta: {
|
|
|
284
282
|
onAnimationIterationCapture?: import("react").AnimationEventHandler<HTMLLIElement> | undefined;
|
|
285
283
|
onTransitionEnd?: import("react").TransitionEventHandler<HTMLLIElement> | undefined;
|
|
286
284
|
onTransitionEndCapture?: import("react").TransitionEventHandler<HTMLLIElement> | undefined;
|
|
285
|
+
track?: string | undefined;
|
|
286
|
+
key?: import("react").Key | null | undefined;
|
|
287
|
+
value?: string | number | readonly string[] | undefined;
|
|
287
288
|
variant?: (typeof variants)[number] | undefined;
|
|
288
|
-
|
|
289
|
+
trackProps?: Record<string, string | number | boolean> | undefined;
|
|
289
290
|
open?: boolean | undefined;
|
|
291
|
+
defaultOpen?: boolean | undefined;
|
|
290
292
|
onOpenChange?: ((open: boolean) => void) | undefined;
|
|
291
|
-
trackProps?: Record<string, string | number | boolean> | undefined;
|
|
292
|
-
value?: string | number | readonly string[] | undefined;
|
|
293
293
|
onResume?: (() => void) | undefined;
|
|
294
294
|
duration?: number | undefined;
|
|
295
295
|
description?: import("react").ReactNode;
|
package/dist/components.css
CHANGED
|
@@ -848,9 +848,33 @@
|
|
|
848
848
|
font-weight: var(--font-weight-bold);
|
|
849
849
|
white-space: nowrap;
|
|
850
850
|
|
|
851
|
-
/*
|
|
851
|
+
/* 나타날 때 꼬리에서 튀어나온 뒤 세 번 둥실 움직이고 멈춘다 (ADR 0007). 계속 움직이면 거슬린다.
|
|
852
|
+
둥실 지연 = 팝 지연 + 팝 시간. 팝 쪽을 바꾸면 둥실 지연도 같이 바꾼다 */
|
|
853
|
+
--callout-origin: var(--spacing-16) 100%;
|
|
854
|
+
--callout-enter: translateY(var(--spacing-4));
|
|
855
|
+
|
|
856
|
+
transform-origin: var(--callout-origin);
|
|
857
|
+
animation:
|
|
858
|
+
callout-pop var(--motion-slow) var(--motion-spring) var(--motion-fast) both,
|
|
859
|
+
callout-float calc(var(--motion-base) * 8) var(--motion-ease)
|
|
860
|
+
calc(var(--motion-fast) + var(--motion-slow)) 3;
|
|
852
861
|
}
|
|
853
862
|
|
|
863
|
+
.base-callout.tail-bottom-end {
|
|
864
|
+
--callout-origin: calc(100% - var(--spacing-16)) 100%;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.base-callout:is(.tail-top-start,.tail-top-end) {
|
|
868
|
+
--callout-origin: var(--spacing-16) 0;
|
|
869
|
+
--callout-enter: translateY(calc(var(--spacing-4) * -1));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.base-callout.tail-top-end {
|
|
873
|
+
--callout-origin: calc(100% - var(--spacing-16)) 0;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/* 꼬리: --spacing-8 정사각형을 45도 돌린다. 한 변의 절반에 테두리 두께만큼 더 밖으로 내어 몸통 테두리를 덮는다 */
|
|
877
|
+
|
|
854
878
|
.base-callout::after {
|
|
855
879
|
content: '';
|
|
856
880
|
position: absolute;
|
|
@@ -881,6 +905,35 @@
|
|
|
881
905
|
right: var(--spacing-12);
|
|
882
906
|
}
|
|
883
907
|
|
|
908
|
+
@keyframes callout-pop {
|
|
909
|
+
from {
|
|
910
|
+
opacity: 0;
|
|
911
|
+
transform: var(--callout-enter) scale(0.6);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
to {
|
|
915
|
+
opacity: 1;
|
|
916
|
+
transform: none;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
@keyframes callout-float {
|
|
921
|
+
0%,
|
|
922
|
+
100% {
|
|
923
|
+
transform: none;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
50% {
|
|
927
|
+
transform: translateY(calc(var(--spacing-6) / -2));
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
@media (prefers-reduced-motion: reduce) {
|
|
932
|
+
.base-callout {
|
|
933
|
+
animation: none;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
884
937
|
.base-check-box.label {
|
|
885
938
|
font-size: var(--font-size-sm);
|
|
886
939
|
font-weight: var(--font-weight-medium);
|
|
@@ -2185,6 +2238,14 @@
|
|
|
2185
2238
|
width: 100%;
|
|
2186
2239
|
}
|
|
2187
2240
|
|
|
2241
|
+
/* open 을 넘기면 X 가 진짜 닫기 버튼이 된다. 버튼 기본 모양을 지워 예전 아이콘과 같게 보이게 한다 */
|
|
2242
|
+
|
|
2243
|
+
button.modal-with-double-button-close-button {
|
|
2244
|
+
padding: 0;
|
|
2245
|
+
background-color: transparent;
|
|
2246
|
+
color: var(--color-grey-scale-600);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2188
2249
|
.modal-with-double-button-container.size-s {
|
|
2189
2250
|
width: var(--modal-width-s);
|
|
2190
2251
|
}
|
package/dist/index.js
CHANGED
|
@@ -3183,9 +3183,28 @@ var GHOST_SIDE = {
|
|
|
3183
3183
|
row: 'left',
|
|
3184
3184
|
column: 'right'
|
|
3185
3185
|
};
|
|
3186
|
+
// Radix 는 Dialog.Trigger 로 열었을 때만 포커스를 돌려준다. 부모가 연 경우를 위해 연 요소를 기억한다
|
|
3187
|
+
var useReturnFocus = function useReturnFocus() {
|
|
3188
|
+
var opener = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
3189
|
+
var rememberOpener = function rememberOpener() {
|
|
3190
|
+
opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
3191
|
+
};
|
|
3192
|
+
var returnFocus = function returnFocus(event) {
|
|
3193
|
+
var _a;
|
|
3194
|
+
if (!((_a = opener.current) === null || _a === void 0 ? void 0 : _a.isConnected)) return;
|
|
3195
|
+
event.preventDefault();
|
|
3196
|
+
opener.current.focus();
|
|
3197
|
+
};
|
|
3198
|
+
return {
|
|
3199
|
+
rememberOpener: rememberOpener,
|
|
3200
|
+
returnFocus: returnFocus
|
|
3201
|
+
};
|
|
3202
|
+
};
|
|
3186
3203
|
var ModalWithDoubleButtonBody = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_1__.forwardRef)(function (props, ref) {
|
|
3187
3204
|
var _a = (0,_helpers_utils_getMergedProps__WEBPACK_IMPORTED_MODULE_4__/* .getMergedProps */ .p)(props, _modal_props__WEBPACK_IMPORTED_MODULE_8__/* .modalWithDoubleButtonProps */ .r4),
|
|
3188
3205
|
layout = _a.layout,
|
|
3206
|
+
open = _a.open,
|
|
3207
|
+
onOpenChange = _a.onOpenChange,
|
|
3189
3208
|
content = _a.content,
|
|
3190
3209
|
title = _a.title,
|
|
3191
3210
|
subTitle = _a.subTitle,
|
|
@@ -3195,16 +3214,32 @@ var ModalWithDoubleButtonBody = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_1
|
|
|
3195
3214
|
right = _a.right,
|
|
3196
3215
|
className = _a.className;
|
|
3197
3216
|
var ghostSide = GHOST_SIDE[layout];
|
|
3217
|
+
// open 을 넘기면 Radix 가 열림·포커스·Esc 를 맡는다. 안 넘기면 예전처럼 forceMount 로 늘 보인다.
|
|
3218
|
+
var controlled = open !== undefined;
|
|
3219
|
+
var _b = useReturnFocus(),
|
|
3220
|
+
rememberOpener = _b.rememberOpener,
|
|
3221
|
+
returnFocus = _b.returnFocus;
|
|
3198
3222
|
return (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A, {
|
|
3223
|
+
open: open,
|
|
3224
|
+
onOpenChange: onOpenChange,
|
|
3199
3225
|
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Container, {
|
|
3200
3226
|
ref: ref,
|
|
3201
3227
|
className: (0,_helpers_utils_classNames__WEBPACK_IMPORTED_MODULE_3__/* .classNames */ .x)(className, 'modal-with-double-button-container'),
|
|
3202
|
-
forceMount: true,
|
|
3203
|
-
|
|
3228
|
+
forceMount: controlled ? undefined : true,
|
|
3229
|
+
onOpenAutoFocus: controlled ? rememberOpener : undefined,
|
|
3230
|
+
onCloseAutoFocus: controlled ? returnFocus : undefined,
|
|
3231
|
+
children: [showCloseIcon && (controlled ? (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Close, {
|
|
3232
|
+
className: 'modal-with-double-button-close-button',
|
|
3233
|
+
"aria-label": "\uB2EB\uAE30",
|
|
3234
|
+
asChild: true,
|
|
3235
|
+
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_button_base_button__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A, {
|
|
3236
|
+
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_assets_icons_multifly_svg__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A, {})
|
|
3237
|
+
})
|
|
3238
|
+
}) : (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_button_base_button__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A, {
|
|
3204
3239
|
className: 'modal-with-double-button-close-button',
|
|
3205
3240
|
asChild: true,
|
|
3206
3241
|
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_assets_icons_multifly_svg__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A, {})
|
|
3207
|
-
}), titleIcon && (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Title, {
|
|
3242
|
+
})), titleIcon && (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Title, {
|
|
3208
3243
|
className: 'modal-with-double-button-title-icon',
|
|
3209
3244
|
asChild: true,
|
|
3210
3245
|
children: titleIcon
|
|
@@ -62,9 +62,28 @@ var GHOST_SIDE = {
|
|
|
62
62
|
row: 'left',
|
|
63
63
|
column: 'right'
|
|
64
64
|
};
|
|
65
|
+
// Radix 는 Dialog.Trigger 로 열었을 때만 포커스를 돌려준다. 부모가 연 경우를 위해 연 요소를 기억한다
|
|
66
|
+
var useReturnFocus = function useReturnFocus() {
|
|
67
|
+
var opener = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
68
|
+
var rememberOpener = function rememberOpener() {
|
|
69
|
+
opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
70
|
+
};
|
|
71
|
+
var returnFocus = function returnFocus(event) {
|
|
72
|
+
var _a;
|
|
73
|
+
if (!((_a = opener.current) === null || _a === void 0 ? void 0 : _a.isConnected)) return;
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
opener.current.focus();
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
rememberOpener: rememberOpener,
|
|
79
|
+
returnFocus: returnFocus
|
|
80
|
+
};
|
|
81
|
+
};
|
|
65
82
|
var ModalWithDoubleButtonBody = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_1__.forwardRef)(function (props, ref) {
|
|
66
83
|
var _a = (0,_helpers_utils_getMergedProps__WEBPACK_IMPORTED_MODULE_4__/* .getMergedProps */ .p)(props, _modal_props__WEBPACK_IMPORTED_MODULE_8__/* .modalWithDoubleButtonProps */ .r4),
|
|
67
84
|
layout = _a.layout,
|
|
85
|
+
open = _a.open,
|
|
86
|
+
onOpenChange = _a.onOpenChange,
|
|
68
87
|
content = _a.content,
|
|
69
88
|
title = _a.title,
|
|
70
89
|
subTitle = _a.subTitle,
|
|
@@ -74,16 +93,32 @@ var ModalWithDoubleButtonBody = /*#__PURE__*/(0,react__WEBPACK_IMPORTED_MODULE_1
|
|
|
74
93
|
right = _a.right,
|
|
75
94
|
className = _a.className;
|
|
76
95
|
var ghostSide = GHOST_SIDE[layout];
|
|
96
|
+
// open 을 넘기면 Radix 가 열림·포커스·Esc 를 맡는다. 안 넘기면 예전처럼 forceMount 로 늘 보인다.
|
|
97
|
+
var controlled = open !== undefined;
|
|
98
|
+
var _b = useReturnFocus(),
|
|
99
|
+
rememberOpener = _b.rememberOpener,
|
|
100
|
+
returnFocus = _b.returnFocus;
|
|
77
101
|
return (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A, {
|
|
102
|
+
open: open,
|
|
103
|
+
onOpenChange: onOpenChange,
|
|
78
104
|
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Container, {
|
|
79
105
|
ref: ref,
|
|
80
106
|
className: (0,_helpers_utils_classNames__WEBPACK_IMPORTED_MODULE_3__/* .classNames */ .x)(className, 'modal-with-double-button-container'),
|
|
81
|
-
forceMount: true,
|
|
82
|
-
|
|
107
|
+
forceMount: controlled ? undefined : true,
|
|
108
|
+
onOpenAutoFocus: controlled ? rememberOpener : undefined,
|
|
109
|
+
onCloseAutoFocus: controlled ? returnFocus : undefined,
|
|
110
|
+
children: [showCloseIcon && (controlled ? (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Close, {
|
|
111
|
+
className: 'modal-with-double-button-close-button',
|
|
112
|
+
"aria-label": "\uB2EB\uAE30",
|
|
113
|
+
asChild: true,
|
|
114
|
+
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_button_base_button__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A, {
|
|
115
|
+
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_assets_icons_multifly_svg__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A, {})
|
|
116
|
+
})
|
|
117
|
+
}) : (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_button_base_button__WEBPACK_IMPORTED_MODULE_5__/* ["default"] */ .A, {
|
|
83
118
|
className: 'modal-with-double-button-close-button',
|
|
84
119
|
asChild: true,
|
|
85
120
|
children: (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_assets_icons_multifly_svg__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A, {})
|
|
86
|
-
}), titleIcon && (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Title, {
|
|
121
|
+
})), titleIcon && (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_base_modal__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A.Title, {
|
|
87
122
|
className: 'modal-with-double-button-title-icon',
|
|
88
123
|
asChild: true,
|
|
89
124
|
children: titleIcon
|
package/dist/styles.css
CHANGED
|
@@ -1161,8 +1161,28 @@ p {
|
|
|
1161
1161
|
font-weight: var(--font-weight-bold);
|
|
1162
1162
|
white-space: nowrap;
|
|
1163
1163
|
|
|
1164
|
-
/*
|
|
1164
|
+
/* 나타날 때 꼬리에서 튀어나온 뒤 세 번 둥실 움직이고 멈춘다 (ADR 0007). 계속 움직이면 거슬린다.
|
|
1165
|
+
둥실 지연 = 팝 지연 + 팝 시간. 팝 쪽을 바꾸면 둥실 지연도 같이 바꾼다 */
|
|
1166
|
+
--callout-origin: var(--spacing-16) 100%;
|
|
1167
|
+
--callout-enter: translateY(var(--spacing-4));
|
|
1168
|
+
|
|
1169
|
+
transform-origin: var(--callout-origin);
|
|
1170
|
+
animation:
|
|
1171
|
+
callout-pop var(--motion-slow) var(--motion-spring) var(--motion-fast) both,
|
|
1172
|
+
callout-float calc(var(--motion-base) * 8) var(--motion-ease)
|
|
1173
|
+
calc(var(--motion-fast) + var(--motion-slow)) 3;
|
|
1165
1174
|
}
|
|
1175
|
+
.base-callout.tail-bottom-end {
|
|
1176
|
+
--callout-origin: calc(100% - var(--spacing-16)) 100%;
|
|
1177
|
+
}
|
|
1178
|
+
.base-callout:is(.tail-top-start,.tail-top-end) {
|
|
1179
|
+
--callout-origin: var(--spacing-16) 0;
|
|
1180
|
+
--callout-enter: translateY(calc(var(--spacing-4) * -1));
|
|
1181
|
+
}
|
|
1182
|
+
.base-callout.tail-top-end {
|
|
1183
|
+
--callout-origin: calc(100% - var(--spacing-16)) 0;
|
|
1184
|
+
}
|
|
1185
|
+
/* 꼬리: --spacing-8 정사각형을 45도 돌린다. 한 변의 절반에 테두리 두께만큼 더 밖으로 내어 몸통 테두리를 덮는다 */
|
|
1166
1186
|
.base-callout::after {
|
|
1167
1187
|
content: '';
|
|
1168
1188
|
position: absolute;
|
|
@@ -1188,6 +1208,32 @@ p {
|
|
|
1188
1208
|
.base-callout:is(.tail-bottom-end,.tail-top-end)::after {
|
|
1189
1209
|
right: var(--spacing-12);
|
|
1190
1210
|
}
|
|
1211
|
+
@keyframes callout-pop {
|
|
1212
|
+
from {
|
|
1213
|
+
opacity: 0;
|
|
1214
|
+
transform: var(--callout-enter) scale(0.6);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
to {
|
|
1218
|
+
opacity: 1;
|
|
1219
|
+
transform: none;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
@keyframes callout-float {
|
|
1223
|
+
0%,
|
|
1224
|
+
100% {
|
|
1225
|
+
transform: none;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
50% {
|
|
1229
|
+
transform: translateY(calc(var(--spacing-6) / -2));
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1233
|
+
.base-callout {
|
|
1234
|
+
animation: none;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1191
1237
|
.base-check-box.label {
|
|
1192
1238
|
font-size: var(--font-size-sm);
|
|
1193
1239
|
font-weight: var(--font-weight-medium);
|
|
@@ -2234,6 +2280,12 @@ p {
|
|
|
2234
2280
|
align-items: center;
|
|
2235
2281
|
width: 100%;
|
|
2236
2282
|
}
|
|
2283
|
+
/* open 을 넘기면 X 가 진짜 닫기 버튼이 된다. 버튼 기본 모양을 지워 예전 아이콘과 같게 보이게 한다 */
|
|
2284
|
+
button.modal-with-double-button-close-button {
|
|
2285
|
+
padding: 0;
|
|
2286
|
+
background-color: transparent;
|
|
2287
|
+
color: var(--color-grey-scale-600);
|
|
2288
|
+
}
|
|
2237
2289
|
.modal-with-double-button-container.size-s {
|
|
2238
2290
|
width: var(--modal-width-s);
|
|
2239
2291
|
}
|