@xfilecom/front-core 0.2.18 → 0.2.20

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 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 0 var(--xfc-space-md);
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 0 var(--xfc-space-md);
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
- return ((0, jsx_runtime_1.jsx)("span", { className: cls, ...rest, children: children }));
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 { HTMLAttributes, ReactNode } from 'react';
2
- export type BoxProps = HTMLAttributes<HTMLDivElement> & {
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
- export declare function Box({ padding, className, children, ...rest }: BoxProps): import("react/jsx-runtime").JSX.Element;
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 jsx_runtime_1 = require("react/jsx-runtime");
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 ((0, jsx_runtime_1.jsx)("div", { className: cls, ...rest, children: children }));
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 cls = ['xfc-btn', `xfc-btn--${variant}`, className].filter(Boolean).join(' ');
7
- return (0, jsx_runtime_1.jsx)("button", { type: type, className: cls, ...rest });
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
- return ((0, jsx_runtime_1.jsx)("div", { className: cls, ...rest, children: children }));
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) => ((0, jsx_runtime_1.jsxs)("div", { className: "xfc-inline-error-item", children: [(0, jsx_runtime_1.jsx)("span", { children: e.message }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "xfc-inline-error-dismiss", onClick: () => onDismiss(e.id), "aria-label": "\uB2EB\uAE30", children: "\u00D7" })] }, e.id))) }));
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
- export declare const Input: import("react").ForwardRefExoticComponent<InputProps & import("react").RefAttributes<HTMLInputElement>>;
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 cls = ['xfc-input', className].filter(Boolean).join(' ');
8
- return (0, jsx_runtime_1.jsx)("input", { ref: ref, className: cls, ...rest });
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
- return ((0, jsx_runtime_1.jsxs)("div", { className: root, role: "status", "aria-live": "polite", "aria-busy": "true", children: [(0, jsx_runtime_1.jsx)("span", { className: "xfc-loading-overlay-spinner", "aria-hidden": true }), message ? (0, jsx_runtime_1.jsx)("p", { className: "xfc-loading-overlay-message", children: message }) : null] }));
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], className].filter(Boolean).join(' ');
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 declare function ToastSeverityIcon({ severity }: {
3
+ export type ToastSeverityIconProps = {
4
4
  severity: ToastSeverity;
5
- }): import("react/jsx-runtime").JSX.Element;
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", { ...iconSvgProps, 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" })] }));
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", { ...iconSvgProps, 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" })] }));
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", { ...iconSvgProps, 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" })] }));
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", { ...iconSvgProps, 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" })] }));
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
- return ((0, jsx_runtime_1.jsxs)("div", { className: root, role: "alert", children: [(0, jsx_runtime_1.jsx)("span", { className: "xfc-toast__icon", children: (0, jsx_runtime_1.jsx)(ToastSeverityIcon, { severity: severity }) }), (0, jsx_runtime_1.jsx)("div", { className: "xfc-toast__message", children: message }), onDismiss ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "xfc-toast-dismiss", onClick: onDismiss, "aria-label": "\uB2EB\uAE30", children: (0, jsx_runtime_1.jsx)(ToastCloseIcon, {}) })) : null] }));
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, title ? ((0, jsx_runtime_1.jsx)("div", { id: titleId, className: "xfc-bottom-sheet-panel__title", children: title })) : null, (0, jsx_runtime_1.jsx)("div", { className: "xfc-bottom-sheet-panel__body", children: children })] }) }), document.body);
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 handleCancel = () => {
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", onClick: handleCancel, disabled: confirmLoading, children: cancelLabel }), (0, jsx_runtime_1.jsx)(Button_1.Button, { type: "button", variant: "primary", className: destructive ? 'xfc-confirm-dialog__confirm--danger' : '', onClick: handleConfirm, disabled: confirmLoading, "aria-busy": confirmLoading, children: confirmLoading ? '' : confirmLabel })] }) }));
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: [title ? ((0, jsx_runtime_1.jsx)("div", { id: titleId, className: "xfc-dialog-panel__title", children: title })) : null, description ? ((0, jsx_runtime_1.jsx)("div", { id: descriptionId, className: "xfc-dialog-panel__description", children: description })) : null, (0, jsx_runtime_1.jsx)("div", { className: "xfc-dialog-panel__body", children: children })] }) }), document.body);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfilecom/front-core",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "Shared design tokens, base CSS, and atomic React components (browser-only; no Nest dependency)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",