@designbasekorea/ui 0.1.46 → 0.1.47

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/index.umd.js CHANGED
@@ -2456,7 +2456,7 @@
2456
2456
  return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Spinner, { type: "circular", size: size === 'xs' ? 'xs' : size === 's' ? 's' : 'm', color: getIconColor(), speed: 1, showLabel: false }), !iconOnly && jsxRuntime.jsx("span", { children: "\uB85C\uB529 \uC911..." })] }));
2457
2457
  }
2458
2458
  // iconOnly 버튼일 때는 children을 아이콘으로 처리
2459
- if (iconOnly && children) {
2459
+ if (iconOnly && children && React.isValidElement(children)) {
2460
2460
  return React.cloneElement(children, {
2461
2461
  size: iconSize,
2462
2462
  color: getIconColor(),
@@ -2575,7 +2575,77 @@
2575
2575
  };
2576
2576
  RandomGradient.displayName = 'RandomGradient';
2577
2577
 
2578
- const AdBanner = ({ type = 'hero', variant = 'primary', title, subtitle, ctaText, onCtaClick, onClose, autoClose = false, closeDelay = 5000, icon, useRandomGradient = false, gradientScheme, gradientTone = 'vivid', gradientAnimated = true, className, }) => {
2578
+ const Countdown = ({ endDate, duration, size = 'm', variant = 'default', showDays = true, showHours = true, showMinutes = true, showSeconds = true, showLabels = true, labels = {
2579
+ days: '일',
2580
+ hours: '시간',
2581
+ minutes: '분',
2582
+ seconds: '초',
2583
+ }, onComplete, onTick, className, }) => {
2584
+ // 종료 시간 계산
2585
+ const targetTime = React.useMemo(() => {
2586
+ if (endDate) {
2587
+ return typeof endDate === 'string' ? new Date(endDate).getTime() : endDate.getTime();
2588
+ }
2589
+ if (duration) {
2590
+ return Date.now() + duration * 1000;
2591
+ }
2592
+ return Date.now();
2593
+ }, [endDate, duration]);
2594
+ // 남은 시간 계산 함수
2595
+ const calculateTimeRemaining = () => {
2596
+ const now = Date.now();
2597
+ const total = targetTime - now;
2598
+ if (total <= 0) {
2599
+ return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 };
2600
+ }
2601
+ const seconds = Math.floor((total / 1000) % 60);
2602
+ const minutes = Math.floor((total / 1000 / 60) % 60);
2603
+ const hours = Math.floor((total / (1000 * 60 * 60)) % 24);
2604
+ const days = Math.floor(total / (1000 * 60 * 60 * 24));
2605
+ return { days, hours, minutes, seconds, total };
2606
+ };
2607
+ const [timeRemaining, setTimeRemaining] = React.useState(calculateTimeRemaining());
2608
+ React.useEffect(() => {
2609
+ const timer = setInterval(() => {
2610
+ const newTimeRemaining = calculateTimeRemaining();
2611
+ setTimeRemaining(newTimeRemaining);
2612
+ onTick?.(newTimeRemaining);
2613
+ if (newTimeRemaining.total <= 0) {
2614
+ clearInterval(timer);
2615
+ onComplete?.();
2616
+ }
2617
+ }, 1000);
2618
+ return () => clearInterval(timer);
2619
+ }, [targetTime, onComplete, onTick]);
2620
+ const classes = clsx('designbase-countdown', `designbase-countdown--${size}`, `designbase-countdown--${variant}`, {
2621
+ 'designbase-countdown--with-labels': showLabels,
2622
+ 'designbase-countdown--expired': timeRemaining.total <= 0,
2623
+ }, className);
2624
+ // 숫자를 두 자리로 포맷
2625
+ const formatNumber = (num) => {
2626
+ return num.toString().padStart(2, '0');
2627
+ };
2628
+ const renderTimeUnit = (value, label, show) => {
2629
+ if (!show)
2630
+ return null;
2631
+ return (jsxRuntime.jsxs("div", { className: "designbase-countdown__unit", children: [jsxRuntime.jsx("div", { className: "designbase-countdown__value", children: formatNumber(value) }), showLabels && (jsxRuntime.jsx("div", { className: "designbase-countdown__label", children: label }))] }));
2632
+ };
2633
+ const renderSeparator = () => {
2634
+ if (variant === 'minimal')
2635
+ return null;
2636
+ return jsxRuntime.jsx("div", { className: "designbase-countdown__separator", children: ":" });
2637
+ };
2638
+ const units = [
2639
+ { value: timeRemaining.days, label: labels.days || '일', show: showDays },
2640
+ { value: timeRemaining.hours, label: labels.hours || '시간', show: showHours },
2641
+ { value: timeRemaining.minutes, label: labels.minutes || '분', show: showMinutes },
2642
+ { value: timeRemaining.seconds, label: labels.seconds || '초', show: showSeconds },
2643
+ ].filter(unit => unit.show);
2644
+ return (jsxRuntime.jsx("div", { className: classes, children: units.map((unit, index) => (jsxRuntime.jsxs(React.Fragment, { children: [renderTimeUnit(unit.value, unit.label, unit.show), index < units.length - 1 && renderSeparator()] }, unit.label))) }));
2645
+ };
2646
+ Countdown.displayName = 'Countdown';
2647
+
2648
+ const AdBanner = ({ type = 'hero', variant = 'primary', title, subtitle, ctaText, onCtaClick, onClose, autoClose = false, closeDelay = 5000, icon, useRandomGradient = false, gradientScheme, gradientTone = 'vivid', gradientAnimated = true, countdownEndDate, showCountdown = false, className, }) => {
2579
2649
  const [isVisible, setIsVisible] = React.useState(true);
2580
2650
  const [progress, setProgress] = React.useState(100);
2581
2651
  React.useEffect(() => {
@@ -2606,6 +2676,7 @@
2606
2676
  const classes = clsx('designbase-ad-banner', `designbase-ad-banner--type-${type}`, `designbase-ad-banner--variant-${variant}`, {
2607
2677
  'designbase-ad-banner--auto-close': autoClose,
2608
2678
  'designbase-ad-banner--with-gradient': useRandomGradient,
2679
+ 'designbase-ad-banner--gradient-light': useRandomGradient && gradientTone === 'light',
2609
2680
  }, className);
2610
2681
  // 기본 아이콘
2611
2682
  const defaultIcons = {
@@ -2628,12 +2699,13 @@
2628
2699
  success: 'success',
2629
2700
  warning: 'warning',
2630
2701
  error: 'error',
2631
- gradient: 'sunset',
2632
2702
  };
2633
2703
  return schemeMap[variant];
2634
2704
  };
2635
2705
  // 배너 컨텐츠
2636
- const bannerContent = (jsxRuntime.jsxs("div", { className: "designbase-ad-banner__content", children: [jsxRuntime.jsx("button", { className: "designbase-ad-banner__close", onClick: handleClose, "aria-label": "\uB2EB\uAE30", children: jsxRuntime.jsx(icons.CloseIcon, { size: type === 'hero' ? 20 : 18 }) }), type === 'hero' && !useRandomGradient && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "designbase-ad-banner__decoration designbase-ad-banner__decoration--top" }), jsxRuntime.jsx("div", { className: "designbase-ad-banner__decoration designbase-ad-banner__decoration--bottom" })] })), jsxRuntime.jsxs("div", { className: "designbase-ad-banner__main", children: [jsxRuntime.jsxs("div", { className: "designbase-ad-banner__icon", children: [displayIcon, type === 'topbar' && (jsxRuntime.jsx("span", { className: "designbase-ad-banner__label", children: "\uD2B9\uBCC4 \uC774\uBCA4\uD2B8" })), type === 'floating' && (jsxRuntime.jsx("span", { className: "designbase-ad-banner__badge", children: "HOT DEAL" }))] }), jsxRuntime.jsxs("div", { className: "designbase-ad-banner__text", children: [jsxRuntime.jsx("h2", { className: "designbase-ad-banner__title", children: displayTitle }), jsxRuntime.jsx("p", { className: "designbase-ad-banner__subtitle", children: displaySubtitle })] }), jsxRuntime.jsx("div", { className: "designbase-ad-banner__actions", children: jsxRuntime.jsxs(Button, { variant: "primary", size: type === 'hero' ? 'l' : type === 'card' ? 's' : 'm', onPress: handleCtaClick, className: "designbase-ad-banner__cta", fullWidth: type === 'floating', children: [displayCtaText, jsxRuntime.jsx(icons.ArrowRightIcon, { size: type === 'hero' ? 20 : 16 })] }) })] }), autoClose && (jsxRuntime.jsx("div", { className: "designbase-ad-banner__progress", children: jsxRuntime.jsx("div", { className: "designbase-ad-banner__progress-bar", style: { width: `${progress}%` } }) }))] }));
2706
+ const bannerContent = (jsxRuntime.jsxs("div", { className: "designbase-ad-banner__content", children: [jsxRuntime.jsx("button", { className: "designbase-ad-banner__close", onClick: handleClose, "aria-label": "\uB2EB\uAE30", children: jsxRuntime.jsx(icons.CloseIcon, { size: 20 }) }), jsxRuntime.jsxs("div", { className: "designbase-ad-banner__main", children: [jsxRuntime.jsxs("div", { className: "designbase-ad-banner__icon", children: [displayIcon, type === 'topbar' && (jsxRuntime.jsx("span", { className: "designbase-ad-banner__label", children: "\uD2B9\uBCC4 \uC774\uBCA4\uD2B8" })), type === 'floating' && (jsxRuntime.jsx("span", { className: "designbase-ad-banner__badge", children: "HOT DEAL" }))] }), jsxRuntime.jsxs("div", { className: "designbase-ad-banner__text", children: [jsxRuntime.jsx("h2", { className: "designbase-ad-banner__title", children: displayTitle }), jsxRuntime.jsx("p", { className: "designbase-ad-banner__subtitle", children: displaySubtitle }), showCountdown && countdownEndDate && (jsxRuntime.jsx("div", { className: "designbase-ad-banner__countdown", children: jsxRuntime.jsx(Countdown, { endDate: countdownEndDate, size: "s", variant: "compact", showLabels: false, onComplete: () => {
2707
+ console.log('카운트다운 완료!');
2708
+ } }) }))] }), jsxRuntime.jsx("div", { className: "designbase-ad-banner__actions", children: jsxRuntime.jsxs(Button, { variant: "primary", size: type === 'hero' ? 'l' : type === 'card' ? 's' : 'm', onPress: handleCtaClick, className: "designbase-ad-banner__cta", fullWidth: type === 'floating', children: [displayCtaText, jsxRuntime.jsx(icons.ArrowRightIcon, { size: type === 'hero' ? 20 : 16 })] }) })] }), autoClose && (jsxRuntime.jsx("div", { className: "designbase-ad-banner__progress", children: jsxRuntime.jsx("div", { className: "designbase-ad-banner__progress-bar", style: { width: `${progress}%` } }) }))] }));
2637
2709
  return (jsxRuntime.jsx("div", { className: classes, children: useRandomGradient ? (jsxRuntime.jsx(RandomGradient, { scheme: getGradientScheme(), tone: gradientTone, animated: gradientAnimated, height: "100%", width: "100%", className: "designbase-ad-banner__gradient-bg", children: bannerContent })) : (bannerContent) }));
2638
2710
  };
2639
2711
  // 기본 텍스트 헬퍼
@@ -4773,6 +4845,58 @@
4773
4845
  });
4774
4846
  Card.displayName = 'Card';
4775
4847
 
4848
+ const Indicator = ({ current, total, type = 'dots', direction = 'horizontal', size = 'm', clickable = false, onStepClick, timer = false, timerDuration = 3000, onTimerComplete, className, ...props }) => {
4849
+ const [progress, setProgress] = React.useState(0);
4850
+ const timerRef = React.useRef(null);
4851
+ const startRef = React.useRef(0);
4852
+ React.useEffect(() => {
4853
+ if (!timer)
4854
+ return;
4855
+ setProgress(0);
4856
+ startRef.current = Date.now();
4857
+ const animate = () => {
4858
+ const elapsed = Date.now() - startRef.current;
4859
+ const ratio = Math.min(elapsed / timerDuration, 1);
4860
+ setProgress(ratio);
4861
+ if (ratio < 1) {
4862
+ timerRef.current = requestAnimationFrame(animate);
4863
+ }
4864
+ else {
4865
+ onTimerComplete?.();
4866
+ }
4867
+ };
4868
+ timerRef.current = requestAnimationFrame(animate);
4869
+ return () => {
4870
+ if (timerRef.current)
4871
+ cancelAnimationFrame(timerRef.current);
4872
+ };
4873
+ }, [current, timer, timerDuration, onTimerComplete]);
4874
+ const handleStepClick = (i) => {
4875
+ if (clickable && onStepClick)
4876
+ onStepClick(i);
4877
+ };
4878
+ const classes = clsx('designbase-indicator', `designbase-indicator--${type}`, `designbase-indicator--${direction}`, `designbase-indicator--${size}`, { 'designbase-indicator--clickable': clickable }, className);
4879
+ // 숫자 타입
4880
+ if (type === 'numbers') {
4881
+ return (jsxRuntime.jsxs("div", { className: classes, role: "progressbar", "aria-valuenow": current + 1, "aria-valuemin": 1, "aria-valuemax": total, children: [jsxRuntime.jsx("span", { className: "designbase-indicator__current", children: current + 1 }), jsxRuntime.jsx("span", { className: "designbase-indicator__separator", children: "/" }), jsxRuntime.jsx("span", { className: "designbase-indicator__total", children: total })] }));
4882
+ }
4883
+ // 라인 타입 (가로 progress bar)
4884
+ if (type === 'line') {
4885
+ return (jsxRuntime.jsx("div", { className: classes, role: "progressbar", "aria-valuenow": current + 1, "aria-valuemin": 1, "aria-valuemax": total, children: Array.from({ length: total }, (_, i) => (jsxRuntime.jsx("div", { className: clsx('designbase-indicator__line-segment', {
4886
+ 'designbase-indicator__line-segment--active': i < current,
4887
+ 'designbase-indicator__line-segment--current': i === current
4888
+ }), children: timer && i === current && (jsxRuntime.jsx("div", { className: "designbase-indicator__line-fill", style: { transform: `scaleX(${progress})` } })) }, i))) }));
4889
+ }
4890
+ // 점(dot) 타입 + 원형 애니메이션
4891
+ return (jsxRuntime.jsx("div", { className: classes, role: "progressbar", "aria-valuenow": current + 1, "aria-valuemin": 1, "aria-valuemax": total, children: Array.from({ length: total }, (_, i) => {
4892
+ const active = i === current;
4893
+ const circumference = 2 * Math.PI * 15.9155;
4894
+ const offset = circumference * (1 - progress);
4895
+ return (jsxRuntime.jsx("button", { className: clsx('designbase-indicator__dot', { 'designbase-indicator__dot--active': active }), onClick: () => handleStepClick(i), disabled: !clickable, children: timer && active && (jsxRuntime.jsxs("svg", { className: "designbase-indicator__circle", viewBox: "0 0 36 36", children: [jsxRuntime.jsx("circle", { className: "designbase-indicator__circle-bg", cx: "18", cy: "18", r: "15.9155", fill: "none", stroke: "var(--db-border-base)", strokeWidth: "2" }), jsxRuntime.jsx("circle", { className: "designbase-indicator__circle-progress", cx: "18", cy: "18", r: "15.9155", fill: "none", stroke: "var(--db-brand-primary)", strokeWidth: "2", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round" })] })) }, i));
4896
+ }) }));
4897
+ };
4898
+ Indicator.displayName = 'Indicator';
4899
+
4776
4900
  const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', transition = 'slide', autoPlay = false, autoPlayInterval = 5000, infinite = true, itemsPerView = 1, gap = 16, showNavigation = true, showIndicators = true, indicatorStyle = 'dots', showAutoPlayControl = true, showTitle = true, showDescription = true, enableTouch = true, enableKeyboard = true, enableWheel = false, enableFullscreen = false, enableDownload = false, enableShare = false, enableLike = false, enableBookmark = false, readonly = false, disabled = false, enablePaging = false, swipeSensitivity = 50, transitionDuration = 300, onItemClick, onSlideChange, onLike, onBookmark, onShare, onDownload, onFullscreenChange, className, }) => {
4777
4901
  // 아이콘 크기 계산 (m이 기본값)
4778
4902
  const iconSize = size === 's' ? 16 : size === 'l' ? 24 : size === 'xl' ? 28 : 20;
@@ -5041,10 +5165,6 @@
5041
5165
  willChange: isDragging ? 'transform' : 'auto',
5042
5166
  };
5043
5167
  };
5044
- // 인디케이터 활성 상태 확인
5045
- const isIndicatorActive = (index) => {
5046
- return index === currentIndex;
5047
- };
5048
5168
  // 네비게이션 버튼 비활성화 상태
5049
5169
  const isPrevDisabled = !infinite && currentIndex === 0;
5050
5170
  const isNextDisabled = !infinite && currentIndex === items.length - 1;
@@ -5083,9 +5203,7 @@
5083
5203
  }, title: "\uACF5\uC720", children: jsxRuntime.jsx(icons.ShareAltIcon, { size: iconSize, color: "currentColor" }) })), enableDownload && item.image && (jsxRuntime.jsx("button", { className: "designbase-carousel__action-button designbase-carousel__action-button--download", onClick: (e) => {
5084
5204
  e.stopPropagation();
5085
5205
  handleDownload(item, index);
5086
- }, title: "\uB2E4\uC6B4\uB85C\uB4DC", children: jsxRuntime.jsx(icons.DownloadIcon, { size: iconSize, color: "currentColor" }) }))] })] }, item.id))) }), showNavigation && items.length > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { className: clsx('designbase-carousel__nav-button', 'designbase-carousel__nav-button--prev'), onClick: goToPrevious, disabled: disabled || readonly || isPrevDisabled, title: "\uC774\uC804 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntime.jsx("button", { className: clsx('designbase-carousel__nav-button', 'designbase-carousel__nav-button--next'), onClick: goToNext, disabled: disabled || readonly || isNextDisabled, title: "\uB2E4\uC74C \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] })), enablePaging && items.length > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { className: "designbase-carousel__quick-nav-button designbase-carousel__quick-nav-button--first", onClick: goToFirst, disabled: disabled || readonly || currentIndex === 0, title: "\uCCAB \uBC88\uC9F8 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ArrowLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntime.jsx("button", { className: "designbase-carousel__quick-nav-button designbase-carousel__quick-nav-button--last", onClick: goToLast, disabled: disabled || readonly || currentIndex === items.length - 1, title: "\uB9C8\uC9C0\uB9C9 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ArrowRightIcon, { size: iconSize, color: "currentColor" }) })] })), enableFullscreen && (jsxRuntime.jsx("button", { className: "designbase-carousel__fullscreen-button", onClick: handleFullscreenToggle, disabled: disabled || readonly, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsxRuntime.jsx(icons.ShrinkIcon, { size: iconSize, color: "currentColor" }) : jsxRuntime.jsx(icons.ExpandIcon, { size: iconSize, color: "currentColor" }) }))] }), jsxRuntime.jsxs("div", { className: "designbase-carousel__controls", children: [showIndicators && items.length > 1 && (jsxRuntime.jsx("div", { className: "designbase-carousel__indicators", children: items.map((item, index) => (jsxRuntime.jsx("button", { className: clsx('designbase-carousel__indicator', {
5087
- 'designbase-carousel__indicator--active': isIndicatorActive(index),
5088
- }), onClick: () => goToSlide(index), disabled: disabled || readonly, title: `슬라이드 ${index + 1}로 이동`, type: "button", children: indicatorStyle === 'thumbnails' && item.thumbnail ? (jsxRuntime.jsx("img", { src: item.thumbnail, alt: item.title || `썸네일 ${index + 1}`, className: "designbase-carousel__indicator-thumbnail" })) : indicatorStyle === 'numbers' ? (jsxRuntime.jsx("span", { className: "designbase-carousel__indicator-number", children: index + 1 })) : indicatorStyle === 'lines' ? (jsxRuntime.jsx("span", { className: "designbase-carousel__indicator-line" })) : (jsxRuntime.jsx("span", { className: "designbase-carousel__indicator-dot" })) }, index))) })), showAutoPlayControl && items.length > 1 && (jsxRuntime.jsx("button", { className: "designbase-carousel__autoplay-button", onClick: handleAutoPlayToggle, disabled: disabled || readonly, title: isAutoPlaying ? "자동 재생 정지" : "자동 재생 시작", type: "button", children: isAutoPlaying ? jsxRuntime.jsx(icons.PauseIcon, { size: iconSize, color: "currentColor" }) : jsxRuntime.jsx(icons.PlayIcon, { size: iconSize, color: "currentColor" }) }))] })] }));
5206
+ }, title: "\uB2E4\uC6B4\uB85C\uB4DC", children: jsxRuntime.jsx(icons.DownloadIcon, { size: iconSize, color: "currentColor" }) }))] })] }, item.id))) }), showNavigation && items.length > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { className: clsx('designbase-carousel__nav-button', 'designbase-carousel__nav-button--prev'), onClick: goToPrevious, disabled: disabled || readonly || isPrevDisabled, title: "\uC774\uC804 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntime.jsx("button", { className: clsx('designbase-carousel__nav-button', 'designbase-carousel__nav-button--next'), onClick: goToNext, disabled: disabled || readonly || isNextDisabled, title: "\uB2E4\uC74C \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] })), enablePaging && items.length > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { className: "designbase-carousel__quick-nav-button designbase-carousel__quick-nav-button--first", onClick: goToFirst, disabled: disabled || readonly || currentIndex === 0, title: "\uCCAB \uBC88\uC9F8 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ArrowLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntime.jsx("button", { className: "designbase-carousel__quick-nav-button designbase-carousel__quick-nav-button--last", onClick: goToLast, disabled: disabled || readonly || currentIndex === items.length - 1, title: "\uB9C8\uC9C0\uB9C9 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ArrowRightIcon, { size: iconSize, color: "currentColor" }) })] })), enableFullscreen && (jsxRuntime.jsx("button", { className: "designbase-carousel__fullscreen-button", onClick: handleFullscreenToggle, disabled: disabled || readonly, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsxRuntime.jsx(icons.ShrinkIcon, { size: iconSize, color: "currentColor" }) : jsxRuntime.jsx(icons.ExpandIcon, { size: iconSize, color: "currentColor" }) }))] }), jsxRuntime.jsxs("div", { className: "designbase-carousel__controls", children: [showIndicators && items.length > 1 && (jsxRuntime.jsx("div", { className: "designbase-carousel__indicators", children: jsxRuntime.jsx(Indicator, { current: currentIndex, total: items.length, type: indicatorStyle === 'lines' ? 'line' : indicatorStyle === 'numbers' ? 'numbers' : 'dots', size: "s", clickable: true, onStepClick: goToSlide }) })), showAutoPlayControl && items.length > 1 && (jsxRuntime.jsx("button", { className: "designbase-carousel__autoplay-button", onClick: handleAutoPlayToggle, disabled: disabled || readonly, title: isAutoPlaying ? "자동 재생 정지" : "자동 재생 시작", type: "button", children: isAutoPlaying ? jsxRuntime.jsx(icons.PauseIcon, { size: iconSize, color: "currentColor" }) : jsxRuntime.jsx(icons.PlayIcon, { size: iconSize, color: "currentColor" }) }))] })] }));
5089
5207
  };
5090
5208
 
5091
5209
  const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', deletable = false, onDelete, onClick, startIcon, endIcon, disabled = false, selected = false, fullWidth = false, className, ...props }) => {
@@ -5503,76 +5621,6 @@
5503
5621
  };
5504
5622
  Confirm.displayName = 'Confirm';
5505
5623
 
5506
- const Countdown = ({ endDate, duration, size = 'm', variant = 'default', showDays = true, showHours = true, showMinutes = true, showSeconds = true, showLabels = true, labels = {
5507
- days: '일',
5508
- hours: '시간',
5509
- minutes: '분',
5510
- seconds: '초',
5511
- }, onComplete, onTick, className, }) => {
5512
- // 종료 시간 계산
5513
- const targetTime = React.useMemo(() => {
5514
- if (endDate) {
5515
- return typeof endDate === 'string' ? new Date(endDate).getTime() : endDate.getTime();
5516
- }
5517
- if (duration) {
5518
- return Date.now() + duration * 1000;
5519
- }
5520
- return Date.now();
5521
- }, [endDate, duration]);
5522
- // 남은 시간 계산 함수
5523
- const calculateTimeRemaining = () => {
5524
- const now = Date.now();
5525
- const total = targetTime - now;
5526
- if (total <= 0) {
5527
- return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 };
5528
- }
5529
- const seconds = Math.floor((total / 1000) % 60);
5530
- const minutes = Math.floor((total / 1000 / 60) % 60);
5531
- const hours = Math.floor((total / (1000 * 60 * 60)) % 24);
5532
- const days = Math.floor(total / (1000 * 60 * 60 * 24));
5533
- return { days, hours, minutes, seconds, total };
5534
- };
5535
- const [timeRemaining, setTimeRemaining] = React.useState(calculateTimeRemaining());
5536
- React.useEffect(() => {
5537
- const timer = setInterval(() => {
5538
- const newTimeRemaining = calculateTimeRemaining();
5539
- setTimeRemaining(newTimeRemaining);
5540
- onTick?.(newTimeRemaining);
5541
- if (newTimeRemaining.total <= 0) {
5542
- clearInterval(timer);
5543
- onComplete?.();
5544
- }
5545
- }, 1000);
5546
- return () => clearInterval(timer);
5547
- }, [targetTime, onComplete, onTick]);
5548
- const classes = clsx('designbase-countdown', `designbase-countdown--${size}`, `designbase-countdown--${variant}`, {
5549
- 'designbase-countdown--with-labels': showLabels,
5550
- 'designbase-countdown--expired': timeRemaining.total <= 0,
5551
- }, className);
5552
- // 숫자를 두 자리로 포맷
5553
- const formatNumber = (num) => {
5554
- return num.toString().padStart(2, '0');
5555
- };
5556
- const renderTimeUnit = (value, label, show) => {
5557
- if (!show)
5558
- return null;
5559
- return (jsxRuntime.jsxs("div", { className: "designbase-countdown__unit", children: [jsxRuntime.jsx("div", { className: "designbase-countdown__value", children: formatNumber(value) }), showLabels && (jsxRuntime.jsx("div", { className: "designbase-countdown__label", children: label }))] }));
5560
- };
5561
- const renderSeparator = () => {
5562
- if (variant === 'minimal')
5563
- return null;
5564
- return jsxRuntime.jsx("div", { className: "designbase-countdown__separator", children: ":" });
5565
- };
5566
- const units = [
5567
- { value: timeRemaining.days, label: labels.days || '일', show: showDays },
5568
- { value: timeRemaining.hours, label: labels.hours || '시간', show: showHours },
5569
- { value: timeRemaining.minutes, label: labels.minutes || '분', show: showMinutes },
5570
- { value: timeRemaining.seconds, label: labels.seconds || '초', show: showSeconds },
5571
- ].filter(unit => unit.show);
5572
- return (jsxRuntime.jsx("div", { className: classes, children: units.map((unit, index) => (jsxRuntime.jsxs(React.Fragment, { children: [renderTimeUnit(unit.value, unit.label, unit.show), index < units.length - 1 && renderSeparator()] }, unit.label))) }));
5573
- };
5574
- Countdown.displayName = 'Countdown';
5575
-
5576
5624
  const Container = ({ size = 'l', maxWidth, variant = 'plain', padding = 'm', margin = 'none', backgroundColor, border = false, rounded = false, shadow = false, className, children, }) => {
5577
5625
  // 크기별 최대 폭 설정 (토큰 기반)
5578
5626
  const getMaxWidth = () => {
@@ -6591,67 +6639,70 @@
6591
6639
  const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multiple = false, showFileList = true, showProgress = true, disabled = false, readonly = false, onUpload, onRemove, onRetry, className, }) => {
6592
6640
  const [uploadedFiles, setUploadedFiles] = React.useState([]);
6593
6641
  const [isUploading, setIsUploading] = React.useState(false);
6594
- // 파일 선택 처리
6595
- const handleFileSelect = React.useCallback((files) => {
6596
- if (disabled || readonly)
6597
- return;
6598
- const newUploadFiles = files.map(file => ({
6599
- file,
6600
- id: `${Date.now()}-${Math.random()}`,
6601
- status: 'pending',
6602
- progress: 0,
6603
- }));
6604
- if (multiple) {
6605
- setUploadedFiles(prev => [...prev, ...newUploadFiles]);
6606
- }
6607
- else {
6608
- setUploadedFiles(newUploadFiles);
6609
- }
6610
- // 시뮬레이션된 업로드 진행
6611
- simulateUpload(newUploadFiles);
6612
- }, [disabled, readonly, multiple]);
6613
- // 업로드 시뮬레이션
6642
+ // ────────────────────────────────────────────────
6643
+ // 업로드 시뮬레이션 (먼저 정의해야 함!)
6644
+ // ────────────────────────────────────────────────
6614
6645
  const simulateUpload = React.useCallback((files) => {
6615
6646
  setIsUploading(true);
6616
6647
  files.forEach((uploadFile, index) => {
6617
6648
  // 업로드 시작
6618
- setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id
6619
- ? { ...f, status: 'uploading' }
6620
- : f));
6621
- // 진행률 시뮬레이션
6649
+ setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id ? { ...f, status: 'uploading' } : f));
6622
6650
  let progress = 0;
6623
6651
  const interval = setInterval(() => {
6624
6652
  progress += Math.random() * 20;
6625
6653
  if (progress >= 100) {
6626
6654
  progress = 100;
6627
6655
  clearInterval(interval);
6628
- // 업로드 완료
6656
+ // 완료 처리
6629
6657
  setTimeout(() => {
6630
6658
  setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id
6631
6659
  ? {
6632
6660
  ...f,
6633
6661
  status: 'success',
6634
6662
  progress: 100,
6635
- uploadedAt: new Date()
6663
+ uploadedAt: new Date(),
6636
6664
  }
6637
6665
  : f));
6638
- // 모든 파일 업로드 완료 확인
6666
+ // 모든 업로드 완료 확인
6639
6667
  setTimeout(() => {
6640
- const allCompleted = uploadedFiles.every(f => f.status === 'success');
6641
- if (allCompleted) {
6642
- setIsUploading(false);
6643
- }
6644
- }, 500);
6668
+ setUploadedFiles(prev => {
6669
+ const allDone = prev.every(f => f.status === 'success');
6670
+ if (allDone)
6671
+ setIsUploading(false);
6672
+ return prev;
6673
+ });
6674
+ }, 300);
6645
6675
  }, 200);
6646
6676
  }
6647
6677
  else {
6648
- setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id
6649
- ? { ...f, progress }
6650
- : f));
6678
+ setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id ? { ...f, progress } : f));
6651
6679
  }
6652
- }, 100 + index * 50); // 각 파일마다 약간의 지연
6680
+ }, 100 + index * 50);
6653
6681
  });
6654
- }, [uploadedFiles]);
6682
+ }, []);
6683
+ // ────────────────────────────────────────────────
6684
+ // 파일 선택 시 처리
6685
+ // ────────────────────────────────────────────────
6686
+ const handleFileSelect = React.useCallback((files) => {
6687
+ if (disabled || readonly)
6688
+ return;
6689
+ const newUploadFiles = files.map(file => ({
6690
+ file,
6691
+ id: `${Date.now()}-${Math.random()}`,
6692
+ status: 'pending',
6693
+ progress: 0,
6694
+ }));
6695
+ if (multiple) {
6696
+ setUploadedFiles(prev => [...prev, ...newUploadFiles]);
6697
+ }
6698
+ else {
6699
+ setUploadedFiles(newUploadFiles);
6700
+ }
6701
+ // ✅ 부모 콜백 즉시 실행 (파일 업로드 감지용)
6702
+ onUpload?.(newUploadFiles);
6703
+ // 진행률 애니메이션
6704
+ simulateUpload(newUploadFiles);
6705
+ }, [disabled, readonly, multiple, onUpload, simulateUpload]);
6655
6706
  // 파일 제거
6656
6707
  const handleRemove = React.useCallback((fileId) => {
6657
6708
  if (disabled || readonly)
@@ -6659,17 +6710,16 @@
6659
6710
  setUploadedFiles(prev => prev.filter(f => f.id !== fileId));
6660
6711
  onRemove?.(fileId);
6661
6712
  }, [disabled, readonly, onRemove]);
6662
- // 파일 재시도
6713
+ // 재시도
6663
6714
  const handleRetry = React.useCallback((fileId) => {
6664
6715
  if (disabled || readonly)
6665
6716
  return;
6666
6717
  const file = uploadedFiles.find(f => f.id === fileId);
6667
- if (file) {
6718
+ if (file)
6668
6719
  simulateUpload([file]);
6669
- }
6670
6720
  onRetry?.(fileId);
6671
6721
  }, [disabled, readonly, uploadedFiles, simulateUpload, onRetry]);
6672
- // 파일 크기 포맷팅
6722
+ // 파일 크기 표시
6673
6723
  const formatFileSize = React.useCallback((bytes) => {
6674
6724
  if (bytes === 0)
6675
6725
  return '0 Bytes';
@@ -6682,7 +6732,7 @@
6682
6732
  const getStatusIcon = React.useCallback((status) => {
6683
6733
  switch (status) {
6684
6734
  case 'pending':
6685
- return jsxRuntime.jsx(icons.ClockIcon, { size: 20 });
6735
+ return jsxRuntime.jsx(icons.FileBlankIcon, { size: 20 });
6686
6736
  case 'uploading':
6687
6737
  return jsxRuntime.jsx(Spinner, { type: "circular", size: "s" });
6688
6738
  case 'success':
@@ -6693,10 +6743,13 @@
6693
6743
  return null;
6694
6744
  }
6695
6745
  }, []);
6746
+ // ────────────────────────────────────────────────
6747
+ // 렌더링
6748
+ // ────────────────────────────────────────────────
6696
6749
  return (jsxRuntime.jsxs("div", { className: clsx('designbase-file-uploader', `designbase-file-uploader--size-${size}`, `designbase-file-uploader--variant-${variant}`, {
6697
6750
  'designbase-file-uploader--disabled': disabled,
6698
6751
  'designbase-file-uploader--readonly': readonly,
6699
- }, className), children: [jsxRuntime.jsx(Dropzone, { size: size, variant: variant, accept: accept, maxSize: maxSize, multiple: multiple, disabled: disabled, readonly: readonly, onFileSelect: handleFileSelect }), showFileList && uploadedFiles.length > 0 && (jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-list", children: [jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-list-title", children: ["\uC5C5\uB85C\uB4DC\uB41C \uD30C\uC77C\uB4E4 (", uploadedFiles.length, "\uAC1C)"] }), jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-items", children: uploadedFiles.map((uploadFile) => (jsxRuntime.jsxs("div", { className: clsx('designbase-file-uploader__file-item', `designbase-file-uploader__file-item--${uploadFile.status}`), children: [jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-info", children: [jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-icon", children: getStatusIcon(uploadFile.status) }), jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-details", children: [jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-name", children: uploadFile.file.name }), jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-size", children: formatFileSize(uploadFile.file.size) }), uploadFile.error && (jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-error", children: uploadFile.error }))] })] }), showProgress && uploadFile.status === 'uploading' && (jsxRuntime.jsx("div", { className: "designbase-file-uploader__progress", children: jsxRuntime.jsx(Progressbar, { value: uploadFile.progress || 0, size: "s", variant: "primary", style: "solid", showLabel: true, labelPosition: "inside", fullWidth: true }) })), jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-actions", children: [uploadFile.status === 'error' && (jsxRuntime.jsx("button", { className: "designbase-file-uploader__retry-button", onClick: () => handleRetry(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC7AC\uC2DC\uB3C4" })), jsxRuntime.jsx("button", { className: "designbase-file-uploader__remove-button", onClick: () => handleRemove(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC0AD\uC81C" })] })] }, uploadFile.id))) })] }))] }));
6752
+ }, className), children: [jsxRuntime.jsx(Dropzone, { size: size, variant: variant, accept: accept, maxSize: maxSize, multiple: multiple, disabled: disabled, readonly: readonly, onFileSelect: handleFileSelect }), showFileList && uploadedFiles.length > 0 && (jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-list", children: [jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-list-title", children: ["\uC5C5\uB85C\uB4DC\uB41C \uD30C\uC77C\uB4E4 (", uploadedFiles.length, "\uAC1C)"] }), jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-items", children: uploadedFiles.map(uploadFile => (jsxRuntime.jsxs("div", { className: clsx('designbase-file-uploader__file-item', `designbase-file-uploader__file-item--${uploadFile.status}`), children: [jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-info", children: [jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-icon", children: getStatusIcon(uploadFile.status) }), jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-details", children: [jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-name", children: uploadFile.file.name }), jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-size", children: formatFileSize(uploadFile.file.size) }), uploadFile.error && (jsxRuntime.jsx("div", { className: "designbase-file-uploader__file-error", children: uploadFile.error }))] })] }), showProgress && uploadFile.status === 'uploading' && (jsxRuntime.jsx("div", { className: "designbase-file-uploader__progress", children: jsxRuntime.jsx(Progressbar, { value: uploadFile.progress || 0, size: "s", variant: "primary", style: "solid", showLabel: true, labelPosition: "inside", fullWidth: true }) })), jsxRuntime.jsxs("div", { className: "designbase-file-uploader__file-actions", children: [uploadFile.status === 'error' && (jsxRuntime.jsx("button", { className: "designbase-file-uploader__retry-button", onClick: () => handleRetry(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC7AC\uC2DC\uB3C4" })), jsxRuntime.jsx("button", { className: "designbase-file-uploader__remove-button", onClick: () => handleRemove(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC0AD\uC81C" })] })] }, uploadFile.id))) })] }))] }));
6700
6753
  };
6701
6754
 
6702
6755
  const FloatingActionButton = React.forwardRef(({ size = 'm', variant = 'primary', icon, loading = false, disabled = false, onClick, className, style, ...props }, forwardedRef) => {
@@ -8358,6 +8411,229 @@
8358
8411
  })), currentIndex: selectedImageIndex, isOpen: true, onOpenChange: handleLightboxClose, onImageChange: handleLightboxNavigate }))] }));
8359
8412
  };
8360
8413
 
8414
+ const OnboardingModal = ({ steps, currentStep = 0, isOpen, onClose, onStepChange, onComplete, showCloseButton = true, showIndicator = true, closeIcon: CloseIconComponent = icons.CloseIcon, prevIcon: PrevIconComponent = icons.ChevronLeftIcon, nextIcon: NextIconComponent = icons.ChevronRightIcon, indicatorType = 'dots', indicatorSize = 'm', className, ...props }) => {
8415
+ const [internalStep, setInternalStep] = React.useState(currentStep);
8416
+ React.useEffect(() => {
8417
+ setInternalStep(currentStep);
8418
+ }, [currentStep]);
8419
+ const currentStepData = steps[internalStep];
8420
+ const isFirstStep = internalStep === 0;
8421
+ const isLastStep = internalStep === steps.length - 1;
8422
+ const handlePrev = () => {
8423
+ if (!isFirstStep) {
8424
+ const newStep = internalStep - 1;
8425
+ setInternalStep(newStep);
8426
+ onStepChange?.(newStep);
8427
+ }
8428
+ };
8429
+ const handleNext = () => {
8430
+ if (isLastStep) {
8431
+ onComplete?.();
8432
+ }
8433
+ else {
8434
+ const newStep = internalStep + 1;
8435
+ setInternalStep(newStep);
8436
+ onStepChange?.(newStep);
8437
+ }
8438
+ };
8439
+ const handleKeyDown = (e) => {
8440
+ if (e.key === 'Escape') {
8441
+ onClose();
8442
+ }
8443
+ else if (e.key === 'ArrowLeft' && !isFirstStep) {
8444
+ handlePrev();
8445
+ }
8446
+ else if (e.key === 'ArrowRight' && !isLastStep) {
8447
+ handleNext();
8448
+ }
8449
+ };
8450
+ if (!isOpen || !currentStepData) {
8451
+ return null;
8452
+ }
8453
+ const classes = clsx('designbase-onboarding-modal', className);
8454
+ return (jsxRuntime.jsxs("div", { className: classes, onKeyDown: handleKeyDown, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-labelledby": "onboarding-title", "aria-describedby": "onboarding-description", ...props, children: [jsxRuntime.jsx("div", { className: "designbase-onboarding-modal__overlay", onClick: onClose }), jsxRuntime.jsxs("div", { className: "designbase-onboarding-modal__content", children: [showCloseButton && (jsxRuntime.jsx("button", { type: "button", className: "designbase-onboarding-modal__close-button", onClick: onClose, "aria-label": "\uB2EB\uAE30", children: jsxRuntime.jsx(CloseIconComponent, { size: 20 }) })), jsxRuntime.jsxs("div", { className: "designbase-onboarding-modal__main", children: [currentStepData.image && (jsxRuntime.jsx("div", { className: "designbase-onboarding-modal__image", children: typeof currentStepData.image === 'string' ? (jsxRuntime.jsx("img", { src: currentStepData.image, alt: currentStepData.title || '온보딩 이미지' })) : (currentStepData.image) })), jsxRuntime.jsxs("div", { className: "designbase-onboarding-modal__text", children: [currentStepData.title && (jsxRuntime.jsx("h2", { id: "onboarding-title", className: "designbase-onboarding-modal__title", children: currentStepData.title })), currentStepData.description && (jsxRuntime.jsx("p", { id: "onboarding-description", className: "designbase-onboarding-modal__description", children: currentStepData.description })), currentStepData.content && (jsxRuntime.jsx("div", { className: "designbase-onboarding-modal__content-area", children: currentStepData.content }))] })] }), jsxRuntime.jsxs("div", { className: "designbase-onboarding-modal__navigation", children: [jsxRuntime.jsx(Button, { variant: "secondary", size: "m", startIcon: PrevIconComponent, onClick: handlePrev, disabled: isFirstStep, "aria-label": "\uC774\uC804", children: "\uC774\uC804" }), showIndicator && (jsxRuntime.jsx(Indicator, { current: internalStep, total: steps.length, type: indicatorType, size: indicatorSize, clickable: true, onStepClick: (step) => {
8455
+ setInternalStep(step);
8456
+ onStepChange?.(step);
8457
+ } })), jsxRuntime.jsx(Button, { variant: "primary", size: "m", endIcon: !isLastStep ? NextIconComponent : undefined, onClick: handleNext, "aria-label": isLastStep ? '완료' : '다음', children: isLastStep ? '완료' : '다음' })] })] })] }));
8458
+ };
8459
+ OnboardingModal.displayName = 'OnboardingModal';
8460
+
8461
+ const Tutorial = ({ steps, currentStep = 0, isActive, onStart, onEnd, onStepChange, onNext, onPrev, indicatorType = 'numbers', indicatorSize = 'm', closeIcon: CloseIconComponent = icons.CloseIcon, prevIcon: PrevIconComponent = icons.ChevronLeftIcon, nextIcon: NextIconComponent = icons.ChevronRightIcon, className, ...props }) => {
8462
+ const [internalStep, setInternalStep] = React.useState(currentStep);
8463
+ const [targetElement, setTargetElement] = React.useState(null);
8464
+ const [popoverPosition, setPopoverPosition] = React.useState({ top: 0, left: 0 });
8465
+ const [popoverPlacement, setPopoverPlacement] = React.useState('bottom');
8466
+ const overlayRef = React.useRef(null);
8467
+ const popoverRef = React.useRef(null);
8468
+ const currentStepData = steps[internalStep];
8469
+ const isFirstStep = internalStep === 0;
8470
+ const isLastStep = internalStep === steps.length - 1;
8471
+ // 타겟 요소 찾기 및 위치 계산
8472
+ const updateTargetElement = React.useCallback(() => {
8473
+ if (!currentStepData?.target)
8474
+ return;
8475
+ // 이전 타겟 요소의 강조 스타일 제거
8476
+ if (targetElement) {
8477
+ targetElement.classList.remove('designbase-tutorial__target');
8478
+ targetElement.style.position = '';
8479
+ targetElement.style.zIndex = '';
8480
+ }
8481
+ const element = document.querySelector(currentStepData.target);
8482
+ if (!element)
8483
+ return;
8484
+ setTargetElement(element);
8485
+ // 타겟 요소에 강조 스타일 적용
8486
+ element.style.position = 'relative';
8487
+ element.style.zIndex = '1000';
8488
+ element.classList.add('designbase-tutorial__target');
8489
+ // 팝오버 위치 계산
8490
+ const rect = element.getBoundingClientRect();
8491
+ const viewportWidth = window.innerWidth;
8492
+ const viewportHeight = window.innerHeight;
8493
+ const popoverWidth = 300;
8494
+ const popoverHeight = 200;
8495
+ const margin = 16;
8496
+ let placement = 'bottom';
8497
+ let top = rect.bottom + margin;
8498
+ let left = rect.left + (rect.width / 2) - (popoverWidth / 2);
8499
+ // 자동 배치 로직
8500
+ if (currentStepData.placement === 'auto') {
8501
+ // 위쪽 공간 확인
8502
+ if (rect.top - popoverHeight - margin > 0) {
8503
+ placement = 'top';
8504
+ top = rect.top - popoverHeight - margin;
8505
+ }
8506
+ // 아래쪽 공간 확인
8507
+ else if (rect.bottom + popoverHeight + margin < viewportHeight) {
8508
+ placement = 'bottom';
8509
+ top = rect.bottom + margin;
8510
+ }
8511
+ // 왼쪽 공간 확인
8512
+ else if (rect.left - popoverWidth - margin > 0) {
8513
+ placement = 'left';
8514
+ top = rect.top + (rect.height / 2) - (popoverHeight / 2);
8515
+ left = rect.left - popoverWidth - margin;
8516
+ }
8517
+ // 오른쪽 공간 확인
8518
+ else if (rect.right + popoverWidth + margin < viewportWidth) {
8519
+ placement = 'right';
8520
+ top = rect.top + (rect.height / 2) - (popoverHeight / 2);
8521
+ left = rect.right + margin;
8522
+ }
8523
+ }
8524
+ else {
8525
+ placement = currentStepData.placement || 'bottom';
8526
+ switch (placement) {
8527
+ case 'top':
8528
+ top = rect.top - popoverHeight - margin;
8529
+ break;
8530
+ case 'bottom':
8531
+ top = rect.bottom + margin;
8532
+ break;
8533
+ case 'left':
8534
+ top = rect.top + (rect.height / 2) - (popoverHeight / 2);
8535
+ left = rect.left - popoverWidth - margin;
8536
+ break;
8537
+ case 'right':
8538
+ top = rect.top + (rect.height / 2) - (popoverHeight / 2);
8539
+ left = rect.right + margin;
8540
+ break;
8541
+ }
8542
+ }
8543
+ // 뷰포트 경계 확인 및 조정
8544
+ left = Math.max(margin, Math.min(left, viewportWidth - popoverWidth - margin));
8545
+ top = Math.max(margin, Math.min(top, viewportHeight - popoverHeight - margin));
8546
+ setPopoverPosition({ top, left });
8547
+ setPopoverPlacement(placement);
8548
+ }, [currentStepData]);
8549
+ // 단계 변경 시 타겟 업데이트
8550
+ React.useEffect(() => {
8551
+ if (isActive && currentStepData) {
8552
+ updateTargetElement();
8553
+ }
8554
+ }, [internalStep, isActive, updateTargetElement]);
8555
+ // 튜토리얼 비활성화 시 모든 강조 스타일 제거
8556
+ React.useEffect(() => {
8557
+ if (!isActive && targetElement) {
8558
+ targetElement.classList.remove('designbase-tutorial__target');
8559
+ targetElement.style.position = '';
8560
+ targetElement.style.zIndex = '';
8561
+ setTargetElement(null);
8562
+ }
8563
+ }, [isActive, targetElement]);
8564
+ // 윈도우 리사이즈 시 위치 재계산
8565
+ React.useEffect(() => {
8566
+ if (!isActive)
8567
+ return;
8568
+ const handleResize = () => {
8569
+ updateTargetElement();
8570
+ };
8571
+ window.addEventListener('resize', handleResize);
8572
+ return () => window.removeEventListener('resize', handleResize);
8573
+ }, [isActive, updateTargetElement]);
8574
+ const handleNext = () => {
8575
+ if (isLastStep) {
8576
+ handleEnd();
8577
+ }
8578
+ else {
8579
+ // 이전 타겟 요소의 강조 스타일 제거
8580
+ if (targetElement) {
8581
+ targetElement.classList.remove('designbase-tutorial__target');
8582
+ targetElement.style.position = '';
8583
+ targetElement.style.zIndex = '';
8584
+ }
8585
+ const newStep = internalStep + 1;
8586
+ setInternalStep(newStep);
8587
+ onStepChange?.(newStep);
8588
+ onNext?.();
8589
+ }
8590
+ };
8591
+ const handlePrev = () => {
8592
+ if (!isFirstStep) {
8593
+ // 이전 타겟 요소의 강조 스타일 제거
8594
+ if (targetElement) {
8595
+ targetElement.classList.remove('designbase-tutorial__target');
8596
+ targetElement.style.position = '';
8597
+ targetElement.style.zIndex = '';
8598
+ }
8599
+ const newStep = internalStep - 1;
8600
+ setInternalStep(newStep);
8601
+ onStepChange?.(newStep);
8602
+ onPrev?.();
8603
+ }
8604
+ };
8605
+ const handleEnd = () => {
8606
+ // 타겟 요소에서 강조 스타일 제거
8607
+ if (targetElement) {
8608
+ targetElement.classList.remove('designbase-tutorial__target');
8609
+ targetElement.style.position = '';
8610
+ targetElement.style.zIndex = '';
8611
+ }
8612
+ setTargetElement(null);
8613
+ onEnd?.();
8614
+ };
8615
+ const handleKeyDown = (e) => {
8616
+ if (e.key === 'Escape') {
8617
+ handleEnd();
8618
+ }
8619
+ else if (e.key === 'ArrowLeft' && !isFirstStep) {
8620
+ handlePrev();
8621
+ }
8622
+ else if (e.key === 'ArrowRight' && !isLastStep) {
8623
+ handleNext();
8624
+ }
8625
+ };
8626
+ if (!isActive || !currentStepData) {
8627
+ return null;
8628
+ }
8629
+ const classes = clsx('designbase-tutorial', className);
8630
+ return (jsxRuntime.jsxs("div", { className: classes, onKeyDown: handleKeyDown, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-labelledby": "tutorial-title", "aria-describedby": "tutorial-content", ...props, children: [jsxRuntime.jsx("div", { ref: overlayRef, className: "designbase-tutorial__overlay", onClick: handleEnd }), jsxRuntime.jsxs("div", { ref: popoverRef, className: clsx('designbase-tutorial__popover', `designbase-tutorial__popover--${popoverPlacement}`), style: {
8631
+ top: popoverPosition.top,
8632
+ left: popoverPosition.left
8633
+ }, children: [jsxRuntime.jsx("button", { type: "button", className: "designbase-tutorial__close-button", onClick: handleEnd, "aria-label": "\uD29C\uD1A0\uB9AC\uC5BC \uC885\uB8CC", children: jsxRuntime.jsx(CloseIconComponent, { size: 16 }) }), jsxRuntime.jsxs("div", { className: "designbase-tutorial__content", children: [(currentStepData.title || currentStepData.content || currentStepData.children) && (jsxRuntime.jsxs("div", { className: "designbase-tutorial__text", children: [currentStepData.title && (jsxRuntime.jsx("h3", { id: "tutorial-title", className: "designbase-tutorial__title", children: currentStepData.title })), currentStepData.content && (jsxRuntime.jsx("p", { id: "tutorial-content", className: "designbase-tutorial__description", children: currentStepData.content })), currentStepData.children && (jsxRuntime.jsx("div", { className: "designbase-tutorial__children", children: currentStepData.children }))] })), jsxRuntime.jsxs("div", { className: "designbase-tutorial__navigation", children: [!isFirstStep && (jsxRuntime.jsx(Button, { variant: "secondary", size: "s", startIcon: jsxRuntime.jsx(PrevIconComponent, { size: 16 }), onPress: handlePrev, "aria-label": "\uC774\uC804 \uB2E8\uACC4", children: "\uC774\uC804" })), jsxRuntime.jsx(Indicator, { current: internalStep, total: steps.length, type: indicatorType, size: indicatorSize }), jsxRuntime.jsx(Button, { variant: "primary", size: "s", endIcon: !isLastStep ? jsxRuntime.jsx(NextIconComponent, { size: 16 }) : undefined, onPress: handleNext, "aria-label": isLastStep ? '완료' : '다음 단계', children: isLastStep ? '완료' : '다음' })] })] })] })] }));
8634
+ };
8635
+ Tutorial.displayName = 'Tutorial';
8636
+
8361
8637
  const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size = 'm', variant = 'default', disabled = false, readOnly = false, fullWidth = false, searchIcon: SearchIconComponent = icons.SearchIcon, clearIcon: ClearIconComponent = icons.CloseIcon, enableRecentSearches = false, recentSearchesKey = 'searchbar-recent-searches', suggestedSearches = [], suggestionRollingInterval = 5000, onChange, onSearch, onFocus, onBlur, onKeyDown, className, ...props }) => {
8362
8638
  const [internalValue, setInternalValue] = React.useState(defaultValue);
8363
8639
  const [recentSearches, setRecentSearches] = React.useState([]);
@@ -11413,6 +11689,7 @@
11413
11689
  exports.HeroFeature = HeroFeature;
11414
11690
  exports.Image = Image$1;
11415
11691
  exports.ImageList = ImageList;
11692
+ exports.Indicator = Indicator;
11416
11693
  exports.Input = Input;
11417
11694
  exports.Label = Label;
11418
11695
  exports.Lightbox = Lightbox;
@@ -11426,6 +11703,7 @@
11426
11703
  exports.ModalFooter = ModalFooter;
11427
11704
  exports.ModalHeader = ModalHeader;
11428
11705
  exports.Navbar = Navbar;
11706
+ exports.OnboardingModal = OnboardingModal;
11429
11707
  exports.Pagination = Pagination;
11430
11708
  exports.Popover = Popover;
11431
11709
  exports.Progress = Progress;
@@ -11459,6 +11737,7 @@
11459
11737
  exports.Toggle = Toggle;
11460
11738
  exports.Toolbar = Toolbar;
11461
11739
  exports.Tooltip = Tooltip;
11740
+ exports.Tutorial = Tutorial;
11462
11741
  exports.VideoPlayer = VideoPlayer;
11463
11742
  exports.YouTubePlayer = YouTubePlayer;
11464
11743
  exports.getTheme = getTheme;