@xfilecom/front-core 0.2.19 → 0.2.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/base.css +190 -2
- package/dist/components/atoms/Badge.d.ts +10 -3
- package/dist/components/atoms/Badge.js +7 -2
- package/dist/components/atoms/Box.d.ts +6 -4
- package/dist/components/atoms/Box.js +3 -3
- package/dist/components/atoms/Button.d.ts +9 -2
- package/dist/components/atoms/Button.js +12 -3
- package/dist/components/atoms/Card.d.ts +5 -1
- package/dist/components/atoms/Card.js +3 -2
- package/dist/components/atoms/InlineErrorList.d.ts +8 -3
- package/dist/components/atoms/InlineErrorList.js +15 -2
- package/dist/components/atoms/Input.d.ts +21 -3
- package/dist/components/atoms/Input.js +20 -3
- package/dist/components/atoms/LoadingOverlay.d.ts +11 -1
- package/dist/components/atoms/LoadingOverlay.js +4 -3
- package/dist/components/atoms/Stack.d.ts +5 -1
- package/dist/components/atoms/Stack.js +3 -1
- package/dist/components/atoms/Text.d.ts +3 -1
- package/dist/components/atoms/Text.js +4 -2
- package/dist/components/atoms/Toast.d.ts +14 -5
- package/dist/components/atoms/Toast.js +13 -9
- package/dist/components/atoms/index.d.ts +1 -1
- package/dist/components/overlays/BottomSheet.d.ts +5 -1
- package/dist/components/overlays/BottomSheet.js +11 -3
- package/dist/components/overlays/ConfirmDialog.d.ts +6 -1
- package/dist/components/overlays/ConfirmDialog.js +10 -4
- package/dist/components/overlays/Dialog.d.ts +7 -1
- package/dist/components/overlays/Dialog.js +6 -3
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/base.css
CHANGED
|
@@ -93,6 +93,13 @@ a:hover {
|
|
|
93
93
|
color: var(--xfc-fg-label);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
.xfc-text--truncate {
|
|
97
|
+
overflow: hidden;
|
|
98
|
+
max-width: 100%;
|
|
99
|
+
text-overflow: ellipsis;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
96
103
|
/* —— Layout —— */
|
|
97
104
|
|
|
98
105
|
.xfc-stack {
|
|
@@ -131,6 +138,26 @@ a:hover {
|
|
|
131
138
|
align-items: stretch;
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
.xfc-stack--justify-start {
|
|
142
|
+
justify-content: flex-start;
|
|
143
|
+
}
|
|
144
|
+
.xfc-stack--justify-end {
|
|
145
|
+
justify-content: flex-end;
|
|
146
|
+
}
|
|
147
|
+
.xfc-stack--justify-center {
|
|
148
|
+
justify-content: center;
|
|
149
|
+
}
|
|
150
|
+
.xfc-stack--justify-between {
|
|
151
|
+
justify-content: space-between;
|
|
152
|
+
}
|
|
153
|
+
.xfc-stack--justify-around {
|
|
154
|
+
justify-content: space-around;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.xfc-stack--nowrap {
|
|
158
|
+
flex-wrap: nowrap;
|
|
159
|
+
}
|
|
160
|
+
|
|
134
161
|
.xfc-box {
|
|
135
162
|
display: block;
|
|
136
163
|
}
|
|
@@ -153,6 +180,25 @@ a:hover {
|
|
|
153
180
|
box-shadow: var(--xfc-card-shadow);
|
|
154
181
|
}
|
|
155
182
|
|
|
183
|
+
.xfc-card__header {
|
|
184
|
+
padding: var(--xfc-space-lg) var(--xfc-space-lg) var(--xfc-space-md);
|
|
185
|
+
border-bottom: 1px solid var(--xfc-border);
|
|
186
|
+
font-size: var(--xfc-text-section);
|
|
187
|
+
font-weight: 700;
|
|
188
|
+
color: var(--xfc-fg-strong);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.xfc-card__body {
|
|
192
|
+
min-width: 0;
|
|
193
|
+
padding: var(--xfc-space-lg);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.xfc-card__footer {
|
|
197
|
+
padding: 0 var(--xfc-space-lg) var(--xfc-space-lg);
|
|
198
|
+
border-top: 1px solid var(--xfc-border);
|
|
199
|
+
padding-top: var(--xfc-space-md);
|
|
200
|
+
}
|
|
201
|
+
|
|
156
202
|
/* —— Buttons (피그마: 80×34 primary, radius 5) —— */
|
|
157
203
|
|
|
158
204
|
.xfc-btn {
|
|
@@ -186,6 +232,21 @@ a:hover {
|
|
|
186
232
|
cursor: not-allowed;
|
|
187
233
|
}
|
|
188
234
|
|
|
235
|
+
.xfc-btn--loading {
|
|
236
|
+
cursor: wait;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.xfc-btn__icon {
|
|
240
|
+
display: inline-flex;
|
|
241
|
+
flex-shrink: 0;
|
|
242
|
+
align-items: center;
|
|
243
|
+
justify-content: center;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.xfc-btn__label {
|
|
247
|
+
min-width: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
189
250
|
.xfc-btn--primary {
|
|
190
251
|
background: var(--xfc-accent);
|
|
191
252
|
color: var(--xfc-accent-fg);
|
|
@@ -268,6 +329,22 @@ a:hover {
|
|
|
268
329
|
cursor: not-allowed;
|
|
269
330
|
}
|
|
270
331
|
|
|
332
|
+
.xfc-input--invalid {
|
|
333
|
+
border-color: var(--xfc-danger);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.xfc-input--invalid:focus {
|
|
337
|
+
border-color: var(--xfc-danger);
|
|
338
|
+
box-shadow: var(--xfc-input-shadow), 0 0 0 2px color-mix(in srgb, var(--xfc-danger) 25%, transparent);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.xfc-input-description {
|
|
342
|
+
margin: var(--xfc-space-xs) 0 0;
|
|
343
|
+
font-size: var(--xfc-text-small);
|
|
344
|
+
line-height: var(--xfc-leading-normal);
|
|
345
|
+
color: var(--xfc-fg-muted);
|
|
346
|
+
}
|
|
347
|
+
|
|
271
348
|
/* —— Badges —— */
|
|
272
349
|
|
|
273
350
|
.xfc-badge {
|
|
@@ -300,6 +377,29 @@ a:hover {
|
|
|
300
377
|
color: var(--xfc-danger);
|
|
301
378
|
}
|
|
302
379
|
|
|
380
|
+
span.xfc-badge[aria-disabled='true'] {
|
|
381
|
+
opacity: 0.55;
|
|
382
|
+
pointer-events: none;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
button.xfc-badge {
|
|
386
|
+
cursor: pointer;
|
|
387
|
+
font: inherit;
|
|
388
|
+
border: none;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
button.xfc-badge:disabled,
|
|
392
|
+
button.xfc-badge[aria-disabled='true'] {
|
|
393
|
+
opacity: 0.55;
|
|
394
|
+
cursor: not-allowed;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.xfc-badge__icon {
|
|
398
|
+
display: inline-flex;
|
|
399
|
+
margin-inline-end: var(--xfc-space-xs);
|
|
400
|
+
vertical-align: middle;
|
|
401
|
+
}
|
|
402
|
+
|
|
303
403
|
/* —— Global loading overlay (business-promotion global-indicator) —— */
|
|
304
404
|
|
|
305
405
|
.xfc-loading-overlay {
|
|
@@ -332,6 +432,20 @@ a:hover {
|
|
|
332
432
|
filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.08));
|
|
333
433
|
}
|
|
334
434
|
|
|
435
|
+
.xfc-loading-overlay-title {
|
|
436
|
+
margin: 0 0 var(--xfc-space-xs);
|
|
437
|
+
max-width: min(280px, 100%);
|
|
438
|
+
font-size: var(--xfc-text-body);
|
|
439
|
+
font-weight: 700;
|
|
440
|
+
line-height: 1.3;
|
|
441
|
+
color: var(--xfc-fg-strong);
|
|
442
|
+
text-align: center;
|
|
443
|
+
text-shadow:
|
|
444
|
+
0 0 10px rgba(255, 255, 255, 1),
|
|
445
|
+
0 0 3px rgba(255, 255, 255, 1),
|
|
446
|
+
0 1px 2px rgba(0, 0, 0, 0.08);
|
|
447
|
+
}
|
|
448
|
+
|
|
335
449
|
.xfc-loading-overlay-message {
|
|
336
450
|
margin: 0;
|
|
337
451
|
max-width: min(280px, 100%);
|
|
@@ -496,6 +610,19 @@ a:hover {
|
|
|
496
610
|
box-shadow: var(--xfc-toast-shadow);
|
|
497
611
|
}
|
|
498
612
|
|
|
613
|
+
.xfc-inline-error-item__main {
|
|
614
|
+
display: flex;
|
|
615
|
+
min-width: 0;
|
|
616
|
+
flex: 1 1 auto;
|
|
617
|
+
align-items: center;
|
|
618
|
+
gap: var(--xfc-space-sm);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.xfc-inline-error-item__icon {
|
|
622
|
+
display: inline-flex;
|
|
623
|
+
flex-shrink: 0;
|
|
624
|
+
}
|
|
625
|
+
|
|
499
626
|
.xfc-inline-error-dismiss {
|
|
500
627
|
background: none;
|
|
501
628
|
border: none;
|
|
@@ -586,12 +713,45 @@ a:hover {
|
|
|
586
713
|
animation: xfc-dialog-panel-in 0.22s ease-out;
|
|
587
714
|
}
|
|
588
715
|
|
|
716
|
+
.xfc-dialog-panel__title-row {
|
|
717
|
+
display: flex;
|
|
718
|
+
align-items: flex-start;
|
|
719
|
+
justify-content: space-between;
|
|
720
|
+
gap: var(--xfc-space-md);
|
|
721
|
+
margin: 0 0 var(--xfc-space-md);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.xfc-dialog-panel__title-block {
|
|
725
|
+
display: flex;
|
|
726
|
+
flex: 1 1 auto;
|
|
727
|
+
min-width: 0;
|
|
728
|
+
align-items: center;
|
|
729
|
+
gap: var(--xfc-space-sm);
|
|
730
|
+
flex-wrap: wrap;
|
|
731
|
+
}
|
|
732
|
+
|
|
589
733
|
.xfc-dialog-panel__title {
|
|
590
734
|
font-size: var(--xfc-text-section);
|
|
591
735
|
font-weight: 700;
|
|
592
736
|
line-height: var(--xfc-leading-tight);
|
|
593
|
-
margin: 0
|
|
737
|
+
margin: 0;
|
|
594
738
|
color: var(--xfc-fg-strong);
|
|
739
|
+
flex: 1 1 auto;
|
|
740
|
+
min-width: 0;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.xfc-dialog-panel__title-adornment {
|
|
744
|
+
display: inline-flex;
|
|
745
|
+
flex-shrink: 0;
|
|
746
|
+
align-items: center;
|
|
747
|
+
color: var(--xfc-fg-muted);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.xfc-dialog-panel__title-extra {
|
|
751
|
+
display: inline-flex;
|
|
752
|
+
flex-shrink: 0;
|
|
753
|
+
align-items: center;
|
|
754
|
+
gap: var(--xfc-space-xs);
|
|
595
755
|
}
|
|
596
756
|
|
|
597
757
|
.xfc-dialog-panel__description {
|
|
@@ -651,11 +811,39 @@ button.xfc-btn.xfc-btn--primary.xfc-confirm-dialog__confirm--danger:hover:not(:d
|
|
|
651
811
|
flex-shrink: 0;
|
|
652
812
|
}
|
|
653
813
|
|
|
814
|
+
.xfc-bottom-sheet-panel__head {
|
|
815
|
+
display: flex;
|
|
816
|
+
align-items: flex-start;
|
|
817
|
+
justify-content: space-between;
|
|
818
|
+
gap: var(--xfc-space-md);
|
|
819
|
+
margin: 0 0 var(--xfc-space-sm);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.xfc-bottom-sheet-panel__head--only-extra {
|
|
823
|
+
justify-content: flex-end;
|
|
824
|
+
}
|
|
825
|
+
|
|
654
826
|
.xfc-bottom-sheet-panel__title {
|
|
655
827
|
font-size: var(--xfc-text-section);
|
|
656
828
|
font-weight: 700;
|
|
657
|
-
margin: 0
|
|
829
|
+
margin: 0;
|
|
658
830
|
color: var(--xfc-fg-strong);
|
|
831
|
+
flex: 1 1 auto;
|
|
832
|
+
min-width: 0;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.xfc-bottom-sheet-panel__head-extra {
|
|
836
|
+
display: inline-flex;
|
|
837
|
+
flex-shrink: 0;
|
|
838
|
+
align-items: center;
|
|
839
|
+
gap: var(--xfc-space-xs);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.xfc-bottom-sheet-panel__subtitle {
|
|
843
|
+
margin: 0 0 var(--xfc-space-md);
|
|
844
|
+
font-size: var(--xfc-text-small);
|
|
845
|
+
line-height: var(--xfc-leading-normal);
|
|
846
|
+
color: var(--xfc-fg-muted);
|
|
659
847
|
}
|
|
660
848
|
|
|
661
849
|
.xfc-bottom-sheet-panel__body {
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
-
export type BadgeProps = HTMLAttributes<HTMLSpanElement> & {
|
|
1
|
+
import type { HTMLAttributes, MouseEventHandler, ReactNode } from 'react';
|
|
2
|
+
export type BadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, 'onClick'> & {
|
|
3
3
|
tone?: 'neutral' | 'accent' | 'success' | 'danger';
|
|
4
4
|
children?: ReactNode;
|
|
5
|
+
/** 앞쪽 아이콘·이모지 등 */
|
|
6
|
+
icon?: ReactNode;
|
|
7
|
+
/** false 이거나 disabled 시 스타일만 비활성 (span 인 경우 클릭 불가) */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** 있으면 `<button type="button">` 로 렌더 (키보드·스크린리더 친화) */
|
|
11
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
5
12
|
};
|
|
6
|
-
export declare function Badge({ tone, className, children, ...rest }: BadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function Badge({ tone, className, children, icon, enabled, onClick, disabled, ...rest }: BadgeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Badge = Badge;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
function Badge({ tone = 'neutral', className = '', children, ...rest }) {
|
|
5
|
+
function Badge({ tone = 'neutral', className = '', children, icon, enabled = true, onClick, disabled, ...rest }) {
|
|
6
6
|
const cls = ['xfc-badge', `xfc-badge--${tone}`, className].filter(Boolean).join(' ');
|
|
7
|
-
|
|
7
|
+
const isDisabled = disabled === true || !enabled;
|
|
8
|
+
const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [icon ? (0, jsx_runtime_1.jsx)("span", { className: "xfc-badge__icon", children: icon }) : null, children] }));
|
|
9
|
+
if (onClick) {
|
|
10
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", className: cls, disabled: isDisabled, "aria-disabled": isDisabled || undefined, onClick: onClick, ...rest, children: content }));
|
|
11
|
+
}
|
|
12
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: cls, "aria-disabled": isDisabled || undefined, ...rest, children: content }));
|
|
8
13
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export type BoxProps =
|
|
1
|
+
import { type ElementType, type HTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export type BoxProps = {
|
|
3
|
+
as?: ElementType;
|
|
3
4
|
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
4
5
|
children?: ReactNode;
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
className?: string;
|
|
7
|
+
} & HTMLAttributes<HTMLElement>;
|
|
8
|
+
export declare function Box({ as, padding, className, children, ...rest }: BoxProps): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Box = Box;
|
|
4
|
-
const
|
|
5
|
-
function Box({ padding = 'none', className = '', children, ...rest }) {
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function Box({ as = 'div', padding = 'none', className = '', children, ...rest }) {
|
|
6
6
|
const padClass = padding === 'none' ? '' : padding === 'sm' ? 'xfc-box--p-sm' : padding === 'md' ? 'xfc-box--p-md' : 'xfc-box--p-lg';
|
|
7
7
|
const cls = ['xfc-box', padClass, className].filter(Boolean).join(' ');
|
|
8
|
-
return (
|
|
8
|
+
return (0, react_1.createElement)(as, { className: cls, ...rest }, children);
|
|
9
9
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import type { ButtonHTMLAttributes } from 'react';
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
3
3
|
variant?: 'primary' | 'secondary' | 'outline' | 'muted' | 'ghost';
|
|
4
|
+
/** false 이면 disabled 와 동일 (스타일·상호작용 끔) */
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** true 이면 비활성 + 로딩 표시(aria-busy) */
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
/** 버튼 텍스트 앞/뒤 아이콘 등 */
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
iconPosition?: 'start' | 'end';
|
|
4
11
|
};
|
|
5
|
-
export declare function Button({ variant, className, type, ...rest }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function Button({ variant, className, type, enabled, loading, icon, iconPosition, disabled, children, ...rest }: ButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Button = Button;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
function Button({ variant = 'primary', className = '', type = 'button', ...rest }) {
|
|
6
|
-
const
|
|
7
|
-
|
|
5
|
+
function Button({ variant = 'primary', className = '', type = 'button', enabled = true, loading = false, icon, iconPosition = 'start', disabled, children, ...rest }) {
|
|
6
|
+
const isDisabled = Boolean(disabled || !enabled || loading);
|
|
7
|
+
const cls = [
|
|
8
|
+
'xfc-btn',
|
|
9
|
+
`xfc-btn--${variant}`,
|
|
10
|
+
loading ? 'xfc-btn--loading' : '',
|
|
11
|
+
className,
|
|
12
|
+
]
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.join(' ');
|
|
15
|
+
const iconEl = icon ? ((0, jsx_runtime_1.jsx)("span", { className: "xfc-btn__icon", "aria-hidden": typeof icon === 'string' ? undefined : true, children: icon })) : null;
|
|
16
|
+
return ((0, jsx_runtime_1.jsxs)("button", { type: type, className: cls, disabled: isDisabled, "aria-busy": loading || undefined, ...rest, children: [icon && iconPosition === 'start' ? iconEl : null, loading ? ((0, jsx_runtime_1.jsx)("span", { className: "xfc-btn__label", children: "\u2026" })) : children != null && children !== false ? ((0, jsx_runtime_1.jsx)("span", { className: "xfc-btn__label", children: children })) : null, icon && iconPosition === 'end' ? iconEl : null] }));
|
|
8
17
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
export type CardProps = HTMLAttributes<HTMLDivElement> & {
|
|
3
3
|
children?: ReactNode;
|
|
4
|
+
/** 상단 헤더 슬롯 (있으면 본문은 .xfc-card__body 로 감쌈) */
|
|
5
|
+
title?: ReactNode;
|
|
6
|
+
/** 하단 푸터 슬롯 */
|
|
7
|
+
footer?: ReactNode;
|
|
4
8
|
};
|
|
5
|
-
export declare function Card({ className, children, ...rest }: CardProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export declare function Card({ className, children, title, footer, ...rest }: CardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Card = Card;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
function Card({ className = '', children, ...rest }) {
|
|
5
|
+
function Card({ className = '', children, title, footer, ...rest }) {
|
|
6
6
|
const cls = ['xfc-card', className].filter(Boolean).join(' ');
|
|
7
|
-
|
|
7
|
+
const structured = title != null || footer != null;
|
|
8
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: cls, ...rest, children: [title != null ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-card__header", children: title }) : null, structured ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-card__body", children: children }) : children, footer != null ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-card__footer", children: footer }) : null] }));
|
|
8
9
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
export type InlineErrorEntry = {
|
|
3
3
|
id: string;
|
|
4
4
|
message: ReactNode;
|
|
5
|
+
icon?: ReactNode;
|
|
6
|
+
/** 행 클릭 시 (배너 액션 등) */
|
|
7
|
+
onClick?: () => void;
|
|
5
8
|
};
|
|
6
9
|
export type InlineErrorListProps = {
|
|
7
10
|
errors: InlineErrorEntry[];
|
|
8
11
|
onDismiss: (id: string) => void;
|
|
9
12
|
className?: string;
|
|
10
|
-
|
|
13
|
+
dismissAriaLabel?: string;
|
|
14
|
+
itemClassName?: string;
|
|
15
|
+
} & Pick<HTMLAttributes<HTMLDivElement>, 'style'>;
|
|
11
16
|
/** 상단 고정 에러 배너 목록 (business-promotion `ErrorList` UI, MobX 없음). */
|
|
12
|
-
export declare function InlineErrorList({ errors, onDismiss, className }: InlineErrorListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
17
|
+
export declare function InlineErrorList({ errors, onDismiss, className, dismissAriaLabel, itemClassName, style, }: InlineErrorListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -3,9 +3,22 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.InlineErrorList = InlineErrorList;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
/** 상단 고정 에러 배너 목록 (business-promotion `ErrorList` UI, MobX 없음). */
|
|
6
|
-
function InlineErrorList({ errors, onDismiss, className = '' }) {
|
|
6
|
+
function InlineErrorList({ errors, onDismiss, className = '', dismissAriaLabel = '닫기', itemClassName = '', style, }) {
|
|
7
7
|
if (!errors.length)
|
|
8
8
|
return null;
|
|
9
9
|
const listCls = ['xfc-inline-error-list', className].filter(Boolean).join(' ');
|
|
10
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: listCls, "aria-live": "assertive", role: "alert", children: errors.map((e) =>
|
|
10
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: listCls, style: style, "aria-live": "assertive", role: "alert", children: errors.map((e) => {
|
|
11
|
+
const itemCls = ['xfc-inline-error-item', itemClassName].filter(Boolean).join(' ');
|
|
12
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: itemCls, onClick: e.onClick, onKeyDown: e.onClick
|
|
13
|
+
? (ev) => {
|
|
14
|
+
if (ev.key === 'Enter' || ev.key === ' ') {
|
|
15
|
+
ev.preventDefault();
|
|
16
|
+
e.onClick?.();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
: undefined, role: e.onClick ? 'button' : undefined, tabIndex: e.onClick ? 0 : undefined, children: [(0, jsx_runtime_1.jsxs)("div", { className: "xfc-inline-error-item__main", children: [e.icon ? (0, jsx_runtime_1.jsx)("span", { className: "xfc-inline-error-item__icon", children: e.icon }) : null, (0, jsx_runtime_1.jsx)("span", { children: e.message })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "xfc-inline-error-dismiss", onClick: (ev) => {
|
|
20
|
+
ev.stopPropagation();
|
|
21
|
+
onDismiss(e.id);
|
|
22
|
+
}, "aria-label": dismissAriaLabel, children: "\u00D7" })] }, e.id));
|
|
23
|
+
}) }));
|
|
11
24
|
}
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
import { type InputHTMLAttributes } from 'react';
|
|
2
|
-
export type InputProps = InputHTMLAttributes<HTMLInputElement
|
|
3
|
-
|
|
1
|
+
import { type InputHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
export type InputProps = InputHTMLAttributes<HTMLInputElement> & {
|
|
3
|
+
/** false 이면 disabled */
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** 검증 실패 등 시각·aria-invalid */
|
|
6
|
+
invalid?: boolean;
|
|
7
|
+
/** 입력 아래 도움말 (id 자동 연결) */
|
|
8
|
+
description?: ReactNode;
|
|
9
|
+
/** 루트 래퍼 class (description 포함 시 div 로 감쌈) */
|
|
10
|
+
wrapperClassName?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const Input: import("react").ForwardRefExoticComponent<InputHTMLAttributes<HTMLInputElement> & {
|
|
13
|
+
/** false 이면 disabled */
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
/** 검증 실패 등 시각·aria-invalid */
|
|
16
|
+
invalid?: boolean;
|
|
17
|
+
/** 입력 아래 도움말 (id 자동 연결) */
|
|
18
|
+
description?: ReactNode;
|
|
19
|
+
/** 루트 래퍼 class (description 포함 시 div 로 감쌈) */
|
|
20
|
+
wrapperClassName?: string;
|
|
21
|
+
} & import("react").RefAttributes<HTMLInputElement>>;
|
|
@@ -3,7 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Input = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const react_1 = require("react");
|
|
6
|
-
exports.Input = (0, react_1.forwardRef)(function Input({ className = '', ...rest }, ref) {
|
|
7
|
-
const
|
|
8
|
-
|
|
6
|
+
exports.Input = (0, react_1.forwardRef)(function Input({ className = '', enabled = true, invalid = false, description, wrapperClassName = '', disabled, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-invalid': ariaInvalidProp, ...rest }, ref) {
|
|
7
|
+
const genId = (0, react_1.useId)();
|
|
8
|
+
const inputId = idProp ?? genId;
|
|
9
|
+
const descId = `${inputId}-desc`;
|
|
10
|
+
const hasDesc = Boolean(description);
|
|
11
|
+
const cls = [
|
|
12
|
+
'xfc-input',
|
|
13
|
+
invalid ? 'xfc-input--invalid' : '',
|
|
14
|
+
className,
|
|
15
|
+
]
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join(' ');
|
|
18
|
+
const isDisabled = disabled === true || !enabled;
|
|
19
|
+
const ariaInvalid = ariaInvalidProp ?? invalid;
|
|
20
|
+
const ariaDescribedBy = [ariaDescribedByProp, hasDesc ? descId : null].filter(Boolean).join(' ') || undefined;
|
|
21
|
+
const inputEl = ((0, jsx_runtime_1.jsx)("input", { ref: ref, id: inputId, className: cls, disabled: isDisabled, "aria-invalid": ariaInvalid || undefined, "aria-describedby": ariaDescribedBy, ...rest }));
|
|
22
|
+
if (!hasDesc) {
|
|
23
|
+
return inputEl;
|
|
24
|
+
}
|
|
25
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: wrapperClassName || undefined, children: [inputEl, (0, jsx_runtime_1.jsx)("p", { id: descId, className: "xfc-input-description", children: description })] }));
|
|
9
26
|
});
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
export type LoadingOverlayProps = {
|
|
3
|
+
/** 표시 여부 */
|
|
3
4
|
active: boolean;
|
|
5
|
+
/** false 이면 active 여도 렌더하지 않음 (상위에서 토글) */
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
/** 본문 메시지 */
|
|
4
8
|
message?: ReactNode;
|
|
9
|
+
/** 메시지 위 짧은 제목·상태 텍스트 */
|
|
10
|
+
title?: ReactNode;
|
|
5
11
|
className?: string;
|
|
12
|
+
/** 스피너 대신 커스텀. `null` 이면 스피너 숨김 */
|
|
13
|
+
spinner?: ReactNode | null;
|
|
14
|
+
/** 루트 `aria-label` (스크린리더용) */
|
|
15
|
+
ariaLabel?: string;
|
|
6
16
|
};
|
|
7
17
|
/** 전역 로딩 오버레이 (business-promotion `GlobalIndicator` UI, 상태는 부모에서 주입). */
|
|
8
|
-
export declare function LoadingOverlay({ active, message, className }: LoadingOverlayProps): import("react/jsx-runtime").JSX.Element | null;
|
|
18
|
+
export declare function LoadingOverlay({ active, enabled, message, title, className, spinner, ariaLabel, }: LoadingOverlayProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.LoadingOverlay = LoadingOverlay;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
/** 전역 로딩 오버레이 (business-promotion `GlobalIndicator` UI, 상태는 부모에서 주입). */
|
|
6
|
-
function LoadingOverlay({ active, message, className = '' }) {
|
|
7
|
-
if (!active)
|
|
6
|
+
function LoadingOverlay({ active, enabled = true, message, title, className = '', spinner, ariaLabel = '로딩 중', }) {
|
|
7
|
+
if (!active || !enabled)
|
|
8
8
|
return null;
|
|
9
9
|
const root = ['xfc-loading-overlay', className].filter(Boolean).join(' ');
|
|
10
|
-
|
|
10
|
+
const spinnerEl = spinner === null ? null : spinner !== undefined ? (spinner) : ((0, jsx_runtime_1.jsx)("span", { className: "xfc-loading-overlay-spinner", "aria-hidden": true }));
|
|
11
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: root, role: "status", "aria-live": "polite", "aria-busy": "true", "aria-label": ariaLabel, children: [spinnerEl, title ? (0, jsx_runtime_1.jsx)("p", { className: "xfc-loading-overlay-title", children: title }) : null, message ? (0, jsx_runtime_1.jsx)("p", { className: "xfc-loading-overlay-message", children: message }) : null] }));
|
|
11
12
|
}
|
|
@@ -3,6 +3,10 @@ export type StackProps = HTMLAttributes<HTMLDivElement> & {
|
|
|
3
3
|
direction?: 'row' | 'column';
|
|
4
4
|
gap?: 'none' | 'sm' | 'md' | 'lg';
|
|
5
5
|
align?: 'start' | 'center' | 'stretch';
|
|
6
|
+
/** 주축 정렬 (flex justify-content) */
|
|
7
|
+
justify?: 'start' | 'end' | 'center' | 'between' | 'around';
|
|
8
|
+
/** row 일 때 줄바꿈 (기본 true = 기존 .xfc-stack--row 와 동일) */
|
|
9
|
+
wrap?: boolean;
|
|
6
10
|
children?: ReactNode;
|
|
7
11
|
};
|
|
8
|
-
export declare function Stack({ direction, gap, align, className, children, ...rest }: StackProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function Stack({ direction, gap, align, justify, wrap, className, children, ...rest }: StackProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Stack = Stack;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
function Stack({ direction = 'column', gap = 'md', align = 'start', className = '', children, ...rest }) {
|
|
5
|
+
function Stack({ direction = 'column', gap = 'md', align = 'start', justify = 'start', wrap = true, className = '', children, ...rest }) {
|
|
6
6
|
const cls = [
|
|
7
7
|
'xfc-stack',
|
|
8
8
|
direction === 'column' ? 'xfc-stack--column' : 'xfc-stack--row',
|
|
9
9
|
`xfc-stack--gap-${gap}`,
|
|
10
10
|
`xfc-stack--align-${align}`,
|
|
11
|
+
`xfc-stack--justify-${justify}`,
|
|
12
|
+
direction === 'row' && !wrap ? 'xfc-stack--nowrap' : '',
|
|
11
13
|
className,
|
|
12
14
|
]
|
|
13
15
|
.filter(Boolean)
|
|
@@ -17,6 +17,8 @@ export type TextProps = {
|
|
|
17
17
|
variant?: TextVariant;
|
|
18
18
|
children?: ReactNode;
|
|
19
19
|
className?: string;
|
|
20
|
+
/** 한 줄 말줄임 */
|
|
21
|
+
truncate?: boolean;
|
|
20
22
|
} & HTMLAttributes<HTMLElement>;
|
|
21
|
-
export declare function Text({ as, variant, className, children, ...rest }: TextProps): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
23
|
+
export declare function Text({ as, variant, className, truncate, children, ...rest }: TextProps): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
22
24
|
export {};
|
|
@@ -14,7 +14,9 @@ const variantClass = {
|
|
|
14
14
|
labelBlock: 'xfc-text xfc-text--label-block',
|
|
15
15
|
accent: 'xfc-text xfc-text--body xfc-text--accent',
|
|
16
16
|
};
|
|
17
|
-
function Text({ as = 'p', variant = 'body', className = '', children, ...rest }) {
|
|
18
|
-
const cls = [variantClass[variant],
|
|
17
|
+
function Text({ as = 'p', variant = 'body', className = '', truncate = false, children, ...rest }) {
|
|
18
|
+
const cls = [variantClass[variant], truncate ? 'xfc-text--truncate' : '', className]
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ');
|
|
19
21
|
return (0, react_1.createElement)(as, { className: cls, ...rest }, children);
|
|
20
22
|
}
|
|
@@ -1,25 +1,34 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { ReactNode, SVGAttributes } from 'react';
|
|
2
2
|
export type ToastSeverity = 'info' | 'success' | 'warn' | 'error';
|
|
3
|
-
export
|
|
3
|
+
export type ToastSeverityIconProps = {
|
|
4
4
|
severity: ToastSeverity;
|
|
5
|
-
}
|
|
5
|
+
} & Pick<SVGAttributes<SVGSVGElement>, 'className' | 'style'>;
|
|
6
|
+
export declare function ToastSeverityIcon({ severity, className, style }: ToastSeverityIconProps): import("react/jsx-runtime").JSX.Element;
|
|
6
7
|
export type ToastProps = {
|
|
7
8
|
severity: ToastSeverity;
|
|
8
9
|
message: ReactNode;
|
|
9
10
|
onDismiss?: () => void;
|
|
10
11
|
className?: string;
|
|
12
|
+
/** 기본 심각도 아이콘 대체. `null` 이면 아이콘 칸 숨김 */
|
|
13
|
+
icon?: ReactNode | null;
|
|
14
|
+
dismissAriaLabel?: string;
|
|
15
|
+
/** false 이면 닫기 버튼 숨김 */
|
|
16
|
+
dismissible?: boolean;
|
|
11
17
|
};
|
|
12
18
|
/** 단일 토스트 행 (목록·커스텀 레이아웃용). */
|
|
13
|
-
export declare function Toast({ severity, message, onDismiss, className }: ToastProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function Toast({ severity, message, onDismiss, className, icon, dismissAriaLabel, dismissible, }: ToastProps): import("react/jsx-runtime").JSX.Element;
|
|
14
20
|
export type ToastEntry = {
|
|
15
21
|
id: string;
|
|
16
22
|
severity: ToastSeverity;
|
|
17
23
|
message: ReactNode;
|
|
24
|
+
icon?: ReactNode | null;
|
|
18
25
|
};
|
|
19
26
|
export type ToastListProps = {
|
|
20
27
|
toasts: ToastEntry[];
|
|
21
28
|
onDismiss: (id: string) => void;
|
|
22
29
|
className?: string;
|
|
30
|
+
dismissAriaLabel?: string;
|
|
31
|
+
dismissible?: boolean;
|
|
23
32
|
};
|
|
24
33
|
/** 하단 고정 토스트 스택 (business-promotion `ToastList` UI, 상태는 부모에서 주입). */
|
|
25
|
-
export declare function ToastList({ toasts, onDismiss, className }: ToastListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
34
|
+
export declare function ToastList({ toasts, onDismiss, className, dismissAriaLabel, dismissible, }: ToastListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -12,31 +12,35 @@ const iconSvgProps = {
|
|
|
12
12
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
13
13
|
'aria-hidden': true,
|
|
14
14
|
};
|
|
15
|
-
function ToastSeverityIcon({ severity }) {
|
|
15
|
+
function ToastSeverityIcon({ severity, className, style }) {
|
|
16
|
+
const svgCls = className ? `${className}` : undefined;
|
|
17
|
+
const common = { ...iconSvgProps, className: svgCls, style };
|
|
16
18
|
switch (severity) {
|
|
17
19
|
case 'success':
|
|
18
|
-
return ((0, jsx_runtime_1.jsxs)("svg", { ...
|
|
20
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { ...common, children: [(0, jsx_runtime_1.jsx)("path", { d: "M10 18.5a8.5 8.5 0 100-17 8.5 8.5 0 000 17z", stroke: "currentColor", strokeWidth: "1.33" }), (0, jsx_runtime_1.jsx)("path", { d: "M6.2 10.1l2.4 2.4 5.2-5.2", stroke: "currentColor", strokeWidth: "1.33", strokeLinecap: "round", strokeLinejoin: "round" })] }));
|
|
19
21
|
case 'error':
|
|
20
|
-
return ((0, jsx_runtime_1.jsxs)("svg", { ...
|
|
22
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { ...common, children: [(0, jsx_runtime_1.jsx)("path", { d: "M10 18.5a8.5 8.5 0 100-17 8.5 8.5 0 000 17z", stroke: "currentColor", strokeWidth: "1.33" }), (0, jsx_runtime_1.jsx)("path", { d: "M10 6.2v5.2M10 13.7v.1", stroke: "currentColor", strokeWidth: "1.33", strokeLinecap: "round" })] }));
|
|
21
23
|
case 'warn':
|
|
22
|
-
return ((0, jsx_runtime_1.jsxs)("svg", { ...
|
|
24
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { ...common, children: [(0, jsx_runtime_1.jsx)("path", { d: "M10 3.2L17.3 16H2.7L10 3.2z", stroke: "currentColor", strokeWidth: "1.33", strokeLinejoin: "round" }), (0, jsx_runtime_1.jsx)("path", { d: "M10 8v3.2M10 13.5v.1", stroke: "currentColor", strokeWidth: "1.33", strokeLinecap: "round" })] }));
|
|
23
25
|
case 'info':
|
|
24
26
|
default:
|
|
25
|
-
return ((0, jsx_runtime_1.jsxs)("svg", { ...
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { ...common, children: [(0, jsx_runtime_1.jsx)("path", { d: "M10 18.5a8.5 8.5 0 100-17 8.5 8.5 0 000 17z", stroke: "currentColor", strokeWidth: "1.33" }), (0, jsx_runtime_1.jsx)("path", { d: "M10 8.8V14M10 5.8v.1", stroke: "currentColor", strokeWidth: "1.33", strokeLinecap: "round" })] }));
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
function ToastCloseIcon() {
|
|
29
31
|
return ((0, jsx_runtime_1.jsx)("svg", { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, children: (0, jsx_runtime_1.jsx)("path", { d: "M5 5l10 10M15 5L5 15", stroke: "currentColor", strokeWidth: "1.33", strokeLinecap: "round" }) }));
|
|
30
32
|
}
|
|
31
33
|
/** 단일 토스트 행 (목록·커스텀 레이아웃용). */
|
|
32
|
-
function Toast({ severity, message, onDismiss, className = '' }) {
|
|
34
|
+
function Toast({ severity, message, onDismiss, className = '', icon, dismissAriaLabel = '닫기', dismissible = true, }) {
|
|
33
35
|
const root = ['xfc-toast', `xfc-toast--${severity}`, className].filter(Boolean).join(' ');
|
|
34
|
-
|
|
36
|
+
const showDismiss = dismissible && onDismiss;
|
|
37
|
+
const iconContent = icon === null ? null : icon !== undefined ? (icon) : ((0, jsx_runtime_1.jsx)(ToastSeverityIcon, { severity: severity }));
|
|
38
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: root, role: "alert", children: [iconContent != null ? (0, jsx_runtime_1.jsx)("span", { className: "xfc-toast__icon", children: iconContent }) : null, (0, jsx_runtime_1.jsx)("div", { className: "xfc-toast__message", children: message }), showDismiss ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "xfc-toast-dismiss", onClick: onDismiss, "aria-label": dismissAriaLabel, children: (0, jsx_runtime_1.jsx)(ToastCloseIcon, {}) })) : null] }));
|
|
35
39
|
}
|
|
36
40
|
/** 하단 고정 토스트 스택 (business-promotion `ToastList` UI, 상태는 부모에서 주입). */
|
|
37
|
-
function ToastList({ toasts, onDismiss, className = '' }) {
|
|
41
|
+
function ToastList({ toasts, onDismiss, className = '', dismissAriaLabel, dismissible, }) {
|
|
38
42
|
if (!toasts.length)
|
|
39
43
|
return null;
|
|
40
44
|
const listCls = ['xfc-toast-list', className].filter(Boolean).join(' ');
|
|
41
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: listCls, "aria-live": "polite", children: toasts.map((t) => ((0, jsx_runtime_1.jsx)(Toast, { severity: t.severity, message: t.message, onDismiss: () => onDismiss(t.id) }, t.id))) }));
|
|
45
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: listCls, "aria-live": "polite", children: toasts.map((t) => ((0, jsx_runtime_1.jsx)(Toast, { severity: t.severity, message: t.message, icon: t.icon, dismissAriaLabel: dismissAriaLabel, dismissible: dismissible, onDismiss: () => onDismiss(t.id) }, t.id))) }));
|
|
42
46
|
}
|
|
@@ -10,5 +10,5 @@ export { Input, type InputProps } from './Input';
|
|
|
10
10
|
export { InlineErrorList, type InlineErrorEntry, type InlineErrorListProps, } from './InlineErrorList';
|
|
11
11
|
export { LoadingOverlay, type LoadingOverlayProps } from './LoadingOverlay';
|
|
12
12
|
export { Stack, type StackProps } from './Stack';
|
|
13
|
-
export { Toast, ToastList, ToastSeverityIcon, type ToastEntry, type ToastListProps, type ToastProps, type ToastSeverity, } from './Toast';
|
|
13
|
+
export { Toast, ToastList, ToastSeverityIcon, type ToastEntry, type ToastListProps, type ToastProps, type ToastSeverity, type ToastSeverityIconProps, } from './Toast';
|
|
14
14
|
export { Text, type TextProps, type TextVariant } from './Text';
|
|
@@ -4,11 +4,15 @@ export type BottomSheetProps = {
|
|
|
4
4
|
onOpenChange?: (open: boolean) => void;
|
|
5
5
|
children?: ReactNode;
|
|
6
6
|
title?: ReactNode;
|
|
7
|
+
subtitle?: ReactNode;
|
|
8
|
+
/** 헤더 오른쪽 (아이콘 버튼 등) */
|
|
9
|
+
headerExtra?: ReactNode;
|
|
7
10
|
/** 상단 드래그 핸들 막대(시각용) */
|
|
8
11
|
showHandle?: boolean;
|
|
9
12
|
closeOnBackdropClick?: boolean;
|
|
10
13
|
closeOnEscape?: boolean;
|
|
11
14
|
backdropClassName?: string;
|
|
12
15
|
panelClassName?: string;
|
|
16
|
+
bodyClassName?: string;
|
|
13
17
|
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'>;
|
|
14
|
-
export declare function BottomSheet({ open, onOpenChange, children, title, showHandle, closeOnBackdropClick, closeOnEscape, backdropClassName, panelClassName, className, onMouseDown: onPanelMouseDown, ...panelRest }: BottomSheetProps): import("react").ReactPortal | null;
|
|
18
|
+
export declare function BottomSheet({ open, onOpenChange, children, title, subtitle, headerExtra, showHandle, closeOnBackdropClick, closeOnEscape, backdropClassName, panelClassName, bodyClassName, className, onMouseDown: onPanelMouseDown, ...panelRest }: BottomSheetProps): import("react").ReactPortal | null;
|
|
@@ -5,7 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_dom_1 = require("react-dom");
|
|
7
7
|
const overlayHooks_1 = require("./overlayHooks");
|
|
8
|
-
function BottomSheet({ open, onOpenChange, children, title, showHandle = true, closeOnBackdropClick = true, closeOnEscape = true, backdropClassName = '', panelClassName = '', className = '', onMouseDown: onPanelMouseDown, ...panelRest }) {
|
|
8
|
+
function BottomSheet({ open, onOpenChange, children, title, subtitle, headerExtra, showHandle = true, closeOnBackdropClick = true, closeOnEscape = true, backdropClassName = '', panelClassName = '', bodyClassName = '', className = '', onMouseDown: onPanelMouseDown, ...panelRest }) {
|
|
9
9
|
const titleId = (0, react_1.useId)();
|
|
10
10
|
const panelRef = (0, react_1.useRef)(null);
|
|
11
11
|
const prevFocusRef = (0, react_1.useRef)(null);
|
|
@@ -40,11 +40,19 @@ function BottomSheet({ open, onOpenChange, children, title, showHandle = true, c
|
|
|
40
40
|
]
|
|
41
41
|
.filter(Boolean)
|
|
42
42
|
.join(' ');
|
|
43
|
+
const bodyCls = ['xfc-bottom-sheet-panel__body', bodyClassName].filter(Boolean).join(' ');
|
|
44
|
+
const showHead = title != null || headerExtra != null;
|
|
45
|
+
const headOnlyExtra = title == null && headerExtra != null;
|
|
43
46
|
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { className: backdropCls, role: "presentation", onMouseDown: (e) => {
|
|
44
47
|
if (e.target === e.currentTarget && closeOnBackdropClick)
|
|
45
48
|
requestClose();
|
|
46
|
-
}, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, ...panelRest, className: panelCls, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined, tabIndex: -1, onMouseDown: (e) => {
|
|
49
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, ...panelRest, className: panelCls, role: "dialog", "aria-modal": "true", "aria-labelledby": title != null ? titleId : undefined, tabIndex: -1, onMouseDown: (e) => {
|
|
47
50
|
onPanelMouseDown?.(e);
|
|
48
51
|
e.stopPropagation();
|
|
49
|
-
}, children: [showHandle ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-bottom-sheet-handle", "aria-hidden": true }) : null,
|
|
52
|
+
}, children: [showHandle ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-bottom-sheet-handle", "aria-hidden": true }) : null, showHead ? ((0, jsx_runtime_1.jsxs)("div", { className: [
|
|
53
|
+
'xfc-bottom-sheet-panel__head',
|
|
54
|
+
headOnlyExtra ? 'xfc-bottom-sheet-panel__head--only-extra' : '',
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(' '), children: [title != null ? ((0, jsx_runtime_1.jsx)("div", { id: titleId, className: "xfc-bottom-sheet-panel__title", children: title })) : null, headerExtra ? ((0, jsx_runtime_1.jsx)("div", { className: "xfc-bottom-sheet-panel__head-extra", children: headerExtra })) : null] })) : null, subtitle != null ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-bottom-sheet-panel__subtitle", children: subtitle }) : null, (0, jsx_runtime_1.jsx)("div", { className: bodyCls, children: children })] }) }), document.body);
|
|
50
58
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ButtonProps } from '../atoms/Button';
|
|
2
3
|
export type ConfirmDialogProps = {
|
|
3
4
|
open: boolean;
|
|
4
5
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -13,5 +14,9 @@ export type ConfirmDialogProps = {
|
|
|
13
14
|
confirmLoading?: boolean;
|
|
14
15
|
closeOnBackdropClick?: boolean;
|
|
15
16
|
panelClassName?: string;
|
|
17
|
+
/** 확인 버튼에 추가 props (icon, className, aria-* 등). `onClick` 은 확인 핸들러 뒤에 호출됩니다. */
|
|
18
|
+
confirmButtonProps?: Omit<ButtonProps, 'children' | 'variant' | 'type' | 'loading'>;
|
|
19
|
+
/** 취소 버튼에 추가 props. `onClick` 은 취소 핸들러 뒤에 호출됩니다. */
|
|
20
|
+
cancelButtonProps?: Omit<ButtonProps, 'children' | 'variant' | 'type'>;
|
|
16
21
|
};
|
|
17
|
-
export declare function ConfirmDialog({ open, onOpenChange, onConfirm, onCancel, title, message, confirmLabel, cancelLabel, destructive, confirmLoading, closeOnBackdropClick, panelClassName, }: ConfirmDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function ConfirmDialog({ open, onOpenChange, onConfirm, onCancel, title, message, confirmLabel, cancelLabel, destructive, confirmLoading, closeOnBackdropClick, panelClassName, confirmButtonProps, cancelButtonProps, }: ConfirmDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -4,15 +4,21 @@ exports.ConfirmDialog = ConfirmDialog;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const Button_1 = require("../atoms/Button");
|
|
6
6
|
const Dialog_1 = require("./Dialog");
|
|
7
|
-
function ConfirmDialog({ open, onOpenChange, onConfirm, onCancel, title, message, confirmLabel = '확인', cancelLabel = '취소', destructive = false, confirmLoading = false, closeOnBackdropClick = true, panelClassName = '', }) {
|
|
8
|
-
const
|
|
7
|
+
function ConfirmDialog({ open, onOpenChange, onConfirm, onCancel, title, message, confirmLabel = '확인', cancelLabel = '취소', destructive = false, confirmLoading = false, closeOnBackdropClick = true, panelClassName = '', confirmButtonProps, cancelButtonProps, }) {
|
|
8
|
+
const { className: confirmClassName, onClick: confirmOnClick, disabled: confirmDisabled, ...confirmRest } = confirmButtonProps ?? {};
|
|
9
|
+
const { className: cancelClassName, onClick: cancelOnClick, disabled: cancelDisabled, ...cancelRest } = cancelButtonProps ?? {};
|
|
10
|
+
const handleCancel = (e) => {
|
|
11
|
+
cancelOnClick?.(e);
|
|
9
12
|
onCancel?.();
|
|
10
13
|
onOpenChange?.(false);
|
|
11
14
|
};
|
|
12
|
-
const handleConfirm = () => {
|
|
15
|
+
const handleConfirm = (e) => {
|
|
16
|
+
confirmOnClick?.(e);
|
|
13
17
|
if (confirmLoading)
|
|
14
18
|
return;
|
|
15
19
|
onConfirm?.();
|
|
16
20
|
};
|
|
17
|
-
return ((0, jsx_runtime_1.jsx)(Dialog_1.Dialog, { open: open, onOpenChange: onOpenChange, title: title, description: message, closeOnBackdropClick: closeOnBackdropClick, panelClassName: ['xfc-confirm-dialog', panelClassName].filter(Boolean).join(' '), children: (0, jsx_runtime_1.jsxs)("div", { className: "xfc-dialog-footer", children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { type: "button", variant: "muted",
|
|
21
|
+
return ((0, jsx_runtime_1.jsx)(Dialog_1.Dialog, { open: open, onOpenChange: onOpenChange, title: title, description: message, closeOnBackdropClick: closeOnBackdropClick, panelClassName: ['xfc-confirm-dialog', panelClassName].filter(Boolean).join(' '), children: (0, jsx_runtime_1.jsxs)("div", { className: "xfc-dialog-footer", children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { type: "button", variant: "muted", ...cancelRest, disabled: confirmLoading || cancelDisabled, className: cancelClassName, onClick: handleCancel, children: cancelLabel }), (0, jsx_runtime_1.jsx)(Button_1.Button, { type: "button", variant: "primary", ...confirmRest, className: [destructive ? 'xfc-confirm-dialog__confirm--danger' : '', confirmClassName ?? '']
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.join(' '), onClick: handleConfirm, loading: confirmLoading, disabled: confirmDisabled, children: confirmLabel })] }) }));
|
|
18
24
|
}
|
|
@@ -5,11 +5,17 @@ export type DialogProps = {
|
|
|
5
5
|
children?: ReactNode;
|
|
6
6
|
/** 보이는 제목(패널 상단). 스크린 리더용 id 자동 연결 */
|
|
7
7
|
title?: ReactNode;
|
|
8
|
+
/** 제목 앞 장식(아이콘 등) */
|
|
9
|
+
titleAdornment?: ReactNode;
|
|
10
|
+
/** 헤더 오른쪽 액션(닫기 버튼 등) */
|
|
11
|
+
headerExtra?: ReactNode;
|
|
8
12
|
/** 부가 설명 */
|
|
9
13
|
description?: ReactNode;
|
|
10
14
|
closeOnBackdropClick?: boolean;
|
|
11
15
|
closeOnEscape?: boolean;
|
|
12
16
|
backdropClassName?: string;
|
|
13
17
|
panelClassName?: string;
|
|
18
|
+
bodyClassName?: string;
|
|
19
|
+
descriptionClassName?: string;
|
|
14
20
|
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'>;
|
|
15
|
-
export declare function Dialog({ open, onOpenChange, children, title, description, closeOnBackdropClick, closeOnEscape, backdropClassName, panelClassName, className, onMouseDown: onPanelMouseDown, ...panelRest }: DialogProps): import("react").ReactPortal | null;
|
|
21
|
+
export declare function Dialog({ open, onOpenChange, children, title, titleAdornment, headerExtra, description, closeOnBackdropClick, closeOnEscape, backdropClassName, panelClassName, bodyClassName, descriptionClassName, className, onMouseDown: onPanelMouseDown, ...panelRest }: DialogProps): import("react").ReactPortal | null;
|
|
@@ -5,7 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const react_dom_1 = require("react-dom");
|
|
7
7
|
const overlayHooks_1 = require("./overlayHooks");
|
|
8
|
-
function Dialog({ open, onOpenChange, children, title, description, closeOnBackdropClick = true, closeOnEscape = true, backdropClassName = '', panelClassName = '', className = '', onMouseDown: onPanelMouseDown, ...panelRest }) {
|
|
8
|
+
function Dialog({ open, onOpenChange, children, title, titleAdornment, headerExtra, description, closeOnBackdropClick = true, closeOnEscape = true, backdropClassName = '', panelClassName = '', bodyClassName = '', descriptionClassName = '', className = '', onMouseDown: onPanelMouseDown, ...panelRest }) {
|
|
9
9
|
const titleId = (0, react_1.useId)();
|
|
10
10
|
const descriptionId = (0, react_1.useId)();
|
|
11
11
|
const panelRef = (0, react_1.useRef)(null);
|
|
@@ -32,11 +32,14 @@ function Dialog({ open, onOpenChange, children, title, description, closeOnBackd
|
|
|
32
32
|
return null;
|
|
33
33
|
const backdropCls = ['xfc-dialog-backdrop', backdropClassName].filter(Boolean).join(' ');
|
|
34
34
|
const panelCls = ['xfc-dialog-panel', panelClassName, className].filter(Boolean).join(' ');
|
|
35
|
+
const bodyCls = ['xfc-dialog-panel__body', bodyClassName].filter(Boolean).join(' ');
|
|
36
|
+
const descCls = ['xfc-dialog-panel__description', descriptionClassName].filter(Boolean).join(' ');
|
|
37
|
+
const showTitleRow = title != null || titleAdornment != null || headerExtra != null;
|
|
35
38
|
return (0, react_dom_1.createPortal)((0, jsx_runtime_1.jsx)("div", { className: backdropCls, role: "presentation", onMouseDown: (e) => {
|
|
36
39
|
if (e.target === e.currentTarget && closeOnBackdropClick)
|
|
37
40
|
requestClose();
|
|
38
|
-
}, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, ...panelRest, className: panelCls, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? titleId : undefined, "aria-describedby": description ? descriptionId : undefined, tabIndex: -1, onMouseDown: (e) => {
|
|
41
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, ...panelRest, className: panelCls, role: "dialog", "aria-modal": "true", "aria-labelledby": title != null ? titleId : undefined, "aria-describedby": description ? descriptionId : undefined, tabIndex: -1, onMouseDown: (e) => {
|
|
39
42
|
onPanelMouseDown?.(e);
|
|
40
43
|
e.stopPropagation();
|
|
41
|
-
}, children: [
|
|
44
|
+
}, children: [showTitleRow ? ((0, jsx_runtime_1.jsxs)("div", { className: "xfc-dialog-panel__title-row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "xfc-dialog-panel__title-block", children: [titleAdornment ? ((0, jsx_runtime_1.jsx)("span", { className: "xfc-dialog-panel__title-adornment", children: titleAdornment })) : null, title != null ? ((0, jsx_runtime_1.jsx)("div", { id: titleId, className: "xfc-dialog-panel__title", children: title })) : null] }), headerExtra ? (0, jsx_runtime_1.jsx)("div", { className: "xfc-dialog-panel__title-extra", children: headerExtra }) : null] })) : null, description ? ((0, jsx_runtime_1.jsx)("div", { id: descriptionId, className: descCls, children: description })) : null, (0, jsx_runtime_1.jsx)("div", { className: bodyCls, children: children })] }) }), document.body);
|
|
42
45
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,4 +43,4 @@ export declare const tokenVars: {
|
|
|
43
43
|
readonly toastWarnBg: "--xfc-toast-warn-bg";
|
|
44
44
|
readonly toastErrorBg: "--xfc-toast-error-bg";
|
|
45
45
|
};
|
|
46
|
-
export { Badge, BottomSheet, Box, Button, Card, ConfirmDialog, Dialog, InlineErrorList, Input, LoadingOverlay, Stack, Text, Toast, ToastList, ToastSeverityIcon, type BadgeProps, type BottomSheetProps, type BoxProps, type ButtonProps, type CardProps, type ConfirmDialogProps, type DialogProps, type InlineErrorEntry, type InlineErrorListProps, type InputProps, type LoadingOverlayProps, type StackProps, type TextProps, type TextVariant, type ToastEntry, type ToastListProps, type ToastProps, type ToastSeverity, } from './components';
|
|
46
|
+
export { Badge, BottomSheet, Box, Button, Card, ConfirmDialog, Dialog, InlineErrorList, Input, LoadingOverlay, Stack, Text, Toast, ToastList, ToastSeverityIcon, type BadgeProps, type BottomSheetProps, type BoxProps, type ButtonProps, type CardProps, type ConfirmDialogProps, type DialogProps, type InlineErrorEntry, type InlineErrorListProps, type InputProps, type LoadingOverlayProps, type StackProps, type TextProps, type TextVariant, type ToastEntry, type ToastListProps, type ToastProps, type ToastSeverity, type ToastSeverityIconProps, } from './components';
|
package/package.json
CHANGED