@designbasekorea/ui 0.1.45 → 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.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +134 -20
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +411 -130
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +412 -128
- package/dist/index.js.map +1 -1
- package/dist/index.umd.css +1 -1
- package/dist/index.umd.css.map +1 -1
- package/dist/index.umd.js +412 -128
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, useLayoutEffect, forwardRef, useId } from 'react';
|
|
3
|
-
import { ChevronDownIcon, StarIcon, TrendingUpIcon, StarFilledIcon, CartIcon, CloseIcon, ArrowRightIcon, InfoFilledIcon, ErrorFilledIcon, WarningFilledIcon, CircleCheckFilledIcon, RefreshIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, RepeatIcon, MuteFilledIcon, VolumeUpIcon, SettingsIcon, UserIcon, HideIcon, ShowIcon, SearchIcon, ChevronUpIcon, GalleryIcon, HeartIcon, BookmarkIcon, ShareAltIcon, DownloadIcon, ArrowLeftIcon, ShrinkIcon, ExpandIcon, DoneIcon as DoneIcon$1, CopyIcon, BulbIcon, CloudCloseIcon, BellActiveIcon, AwardIcon, CalendarIcon, PlusIcon, ErrorIcon, ClockIcon, MinusIcon as MinusIcon$1, VideoIcon, CodeIcon, WriteIcon, UploadIcon, ArrowBarLeftIcon, ArrowBarRightIcon, StarHalfIcon, MoveIcon, MoreVerticalIcon, ArrowDownIcon, ArrowUpLeftIcon, ArrowUpRightIcon, ArrowDownLeftIcon, ArrowDownRightIcon, FacebookIcon, XIcon, InstagramIcon, LinkedinIcon, PinterestIcon, WhatsappIcon, TelegramIcon, MailIcon, LinkIcon, ScanQrcodeIcon, VerticalArrowsIcon, CaretUpdownFilledIcon } from '@designbasekorea/icons';
|
|
3
|
+
import { ChevronDownIcon, StarIcon, TrendingUpIcon, StarFilledIcon, CartIcon, CloseIcon, ArrowRightIcon, InfoFilledIcon, ErrorFilledIcon, WarningFilledIcon, CircleCheckFilledIcon, RefreshIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, RepeatIcon, MuteFilledIcon, VolumeUpIcon, SettingsIcon, UserIcon, HideIcon, ShowIcon, SearchIcon, ChevronUpIcon, GalleryIcon, HeartIcon, BookmarkIcon, ShareAltIcon, DownloadIcon, ArrowLeftIcon, ShrinkIcon, ExpandIcon, DoneIcon as DoneIcon$1, CopyIcon, BulbIcon, CloudCloseIcon, BellActiveIcon, AwardIcon, CalendarIcon, PlusIcon, ErrorIcon, FileBlankIcon, ClockIcon, MinusIcon as MinusIcon$1, VideoIcon, CodeIcon, WriteIcon, UploadIcon, ArrowBarLeftIcon, ArrowBarRightIcon, StarHalfIcon, MoveIcon, MoreVerticalIcon, ArrowDownIcon, ArrowUpLeftIcon, ArrowUpRightIcon, ArrowDownLeftIcon, ArrowDownRightIcon, FacebookIcon, XIcon, InstagramIcon, LinkedinIcon, PinterestIcon, WhatsappIcon, TelegramIcon, MailIcon, LinkIcon, ScanQrcodeIcon, VerticalArrowsIcon, CaretUpdownFilledIcon } from '@designbasekorea/icons';
|
|
4
4
|
import { flushSync, createPortal } from 'react-dom';
|
|
5
5
|
|
|
6
6
|
function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
|
|
@@ -2455,7 +2455,7 @@ const Button = forwardRef(({ variant = 'primary', size = 'm', radius, fullWidth
|
|
|
2455
2455
|
return (jsxs(Fragment, { children: [jsx(Spinner, { type: "circular", size: size === 'xs' ? 'xs' : size === 's' ? 's' : 'm', color: getIconColor(), speed: 1, showLabel: false }), !iconOnly && jsx("span", { children: "\uB85C\uB529 \uC911..." })] }));
|
|
2456
2456
|
}
|
|
2457
2457
|
// iconOnly 버튼일 때는 children을 아이콘으로 처리
|
|
2458
|
-
if (iconOnly && children) {
|
|
2458
|
+
if (iconOnly && children && React.isValidElement(children)) {
|
|
2459
2459
|
return React.cloneElement(children, {
|
|
2460
2460
|
size: iconSize,
|
|
2461
2461
|
color: getIconColor(),
|
|
@@ -2574,7 +2574,77 @@ const RandomGradient = ({ scheme = 'primary', tone = 'vivid', width = '100%', he
|
|
|
2574
2574
|
};
|
|
2575
2575
|
RandomGradient.displayName = 'RandomGradient';
|
|
2576
2576
|
|
|
2577
|
-
const
|
|
2577
|
+
const Countdown = ({ endDate, duration, size = 'm', variant = 'default', showDays = true, showHours = true, showMinutes = true, showSeconds = true, showLabels = true, labels = {
|
|
2578
|
+
days: '일',
|
|
2579
|
+
hours: '시간',
|
|
2580
|
+
minutes: '분',
|
|
2581
|
+
seconds: '초',
|
|
2582
|
+
}, onComplete, onTick, className, }) => {
|
|
2583
|
+
// 종료 시간 계산
|
|
2584
|
+
const targetTime = useMemo(() => {
|
|
2585
|
+
if (endDate) {
|
|
2586
|
+
return typeof endDate === 'string' ? new Date(endDate).getTime() : endDate.getTime();
|
|
2587
|
+
}
|
|
2588
|
+
if (duration) {
|
|
2589
|
+
return Date.now() + duration * 1000;
|
|
2590
|
+
}
|
|
2591
|
+
return Date.now();
|
|
2592
|
+
}, [endDate, duration]);
|
|
2593
|
+
// 남은 시간 계산 함수
|
|
2594
|
+
const calculateTimeRemaining = () => {
|
|
2595
|
+
const now = Date.now();
|
|
2596
|
+
const total = targetTime - now;
|
|
2597
|
+
if (total <= 0) {
|
|
2598
|
+
return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 };
|
|
2599
|
+
}
|
|
2600
|
+
const seconds = Math.floor((total / 1000) % 60);
|
|
2601
|
+
const minutes = Math.floor((total / 1000 / 60) % 60);
|
|
2602
|
+
const hours = Math.floor((total / (1000 * 60 * 60)) % 24);
|
|
2603
|
+
const days = Math.floor(total / (1000 * 60 * 60 * 24));
|
|
2604
|
+
return { days, hours, minutes, seconds, total };
|
|
2605
|
+
};
|
|
2606
|
+
const [timeRemaining, setTimeRemaining] = useState(calculateTimeRemaining());
|
|
2607
|
+
useEffect(() => {
|
|
2608
|
+
const timer = setInterval(() => {
|
|
2609
|
+
const newTimeRemaining = calculateTimeRemaining();
|
|
2610
|
+
setTimeRemaining(newTimeRemaining);
|
|
2611
|
+
onTick?.(newTimeRemaining);
|
|
2612
|
+
if (newTimeRemaining.total <= 0) {
|
|
2613
|
+
clearInterval(timer);
|
|
2614
|
+
onComplete?.();
|
|
2615
|
+
}
|
|
2616
|
+
}, 1000);
|
|
2617
|
+
return () => clearInterval(timer);
|
|
2618
|
+
}, [targetTime, onComplete, onTick]);
|
|
2619
|
+
const classes = clsx('designbase-countdown', `designbase-countdown--${size}`, `designbase-countdown--${variant}`, {
|
|
2620
|
+
'designbase-countdown--with-labels': showLabels,
|
|
2621
|
+
'designbase-countdown--expired': timeRemaining.total <= 0,
|
|
2622
|
+
}, className);
|
|
2623
|
+
// 숫자를 두 자리로 포맷
|
|
2624
|
+
const formatNumber = (num) => {
|
|
2625
|
+
return num.toString().padStart(2, '0');
|
|
2626
|
+
};
|
|
2627
|
+
const renderTimeUnit = (value, label, show) => {
|
|
2628
|
+
if (!show)
|
|
2629
|
+
return null;
|
|
2630
|
+
return (jsxs("div", { className: "designbase-countdown__unit", children: [jsx("div", { className: "designbase-countdown__value", children: formatNumber(value) }), showLabels && (jsx("div", { className: "designbase-countdown__label", children: label }))] }));
|
|
2631
|
+
};
|
|
2632
|
+
const renderSeparator = () => {
|
|
2633
|
+
if (variant === 'minimal')
|
|
2634
|
+
return null;
|
|
2635
|
+
return jsx("div", { className: "designbase-countdown__separator", children: ":" });
|
|
2636
|
+
};
|
|
2637
|
+
const units = [
|
|
2638
|
+
{ value: timeRemaining.days, label: labels.days || '일', show: showDays },
|
|
2639
|
+
{ value: timeRemaining.hours, label: labels.hours || '시간', show: showHours },
|
|
2640
|
+
{ value: timeRemaining.minutes, label: labels.minutes || '분', show: showMinutes },
|
|
2641
|
+
{ value: timeRemaining.seconds, label: labels.seconds || '초', show: showSeconds },
|
|
2642
|
+
].filter(unit => unit.show);
|
|
2643
|
+
return (jsx("div", { className: classes, children: units.map((unit, index) => (jsxs(React.Fragment, { children: [renderTimeUnit(unit.value, unit.label, unit.show), index < units.length - 1 && renderSeparator()] }, unit.label))) }));
|
|
2644
|
+
};
|
|
2645
|
+
Countdown.displayName = 'Countdown';
|
|
2646
|
+
|
|
2647
|
+
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, }) => {
|
|
2578
2648
|
const [isVisible, setIsVisible] = useState(true);
|
|
2579
2649
|
const [progress, setProgress] = useState(100);
|
|
2580
2650
|
useEffect(() => {
|
|
@@ -2605,6 +2675,7 @@ const AdBanner = ({ type = 'hero', variant = 'primary', title, subtitle, ctaText
|
|
|
2605
2675
|
const classes = clsx('designbase-ad-banner', `designbase-ad-banner--type-${type}`, `designbase-ad-banner--variant-${variant}`, {
|
|
2606
2676
|
'designbase-ad-banner--auto-close': autoClose,
|
|
2607
2677
|
'designbase-ad-banner--with-gradient': useRandomGradient,
|
|
2678
|
+
'designbase-ad-banner--gradient-light': useRandomGradient && gradientTone === 'light',
|
|
2608
2679
|
}, className);
|
|
2609
2680
|
// 기본 아이콘
|
|
2610
2681
|
const defaultIcons = {
|
|
@@ -2627,12 +2698,13 @@ const AdBanner = ({ type = 'hero', variant = 'primary', title, subtitle, ctaText
|
|
|
2627
2698
|
success: 'success',
|
|
2628
2699
|
warning: 'warning',
|
|
2629
2700
|
error: 'error',
|
|
2630
|
-
gradient: 'sunset',
|
|
2631
2701
|
};
|
|
2632
2702
|
return schemeMap[variant];
|
|
2633
2703
|
};
|
|
2634
2704
|
// 배너 컨텐츠
|
|
2635
|
-
const bannerContent = (jsxs("div", { className: "designbase-ad-banner__content", children: [jsx("button", { className: "designbase-ad-banner__close", onClick: handleClose, "aria-label": "\uB2EB\uAE30", children: jsx(CloseIcon, { size:
|
|
2705
|
+
const bannerContent = (jsxs("div", { className: "designbase-ad-banner__content", children: [jsx("button", { className: "designbase-ad-banner__close", onClick: handleClose, "aria-label": "\uB2EB\uAE30", children: jsx(CloseIcon, { size: 20 }) }), jsxs("div", { className: "designbase-ad-banner__main", children: [jsxs("div", { className: "designbase-ad-banner__icon", children: [displayIcon, type === 'topbar' && (jsx("span", { className: "designbase-ad-banner__label", children: "\uD2B9\uBCC4 \uC774\uBCA4\uD2B8" })), type === 'floating' && (jsx("span", { className: "designbase-ad-banner__badge", children: "HOT DEAL" }))] }), jsxs("div", { className: "designbase-ad-banner__text", children: [jsx("h2", { className: "designbase-ad-banner__title", children: displayTitle }), jsx("p", { className: "designbase-ad-banner__subtitle", children: displaySubtitle }), showCountdown && countdownEndDate && (jsx("div", { className: "designbase-ad-banner__countdown", children: jsx(Countdown, { endDate: countdownEndDate, size: "s", variant: "compact", showLabels: false, onComplete: () => {
|
|
2706
|
+
console.log('카운트다운 완료!');
|
|
2707
|
+
} }) }))] }), jsx("div", { className: "designbase-ad-banner__actions", children: jsxs(Button, { variant: "primary", size: type === 'hero' ? 'l' : type === 'card' ? 's' : 'm', onPress: handleCtaClick, className: "designbase-ad-banner__cta", fullWidth: type === 'floating', children: [displayCtaText, jsx(ArrowRightIcon, { size: type === 'hero' ? 20 : 16 })] }) })] }), autoClose && (jsx("div", { className: "designbase-ad-banner__progress", children: jsx("div", { className: "designbase-ad-banner__progress-bar", style: { width: `${progress}%` } }) }))] }));
|
|
2636
2708
|
return (jsx("div", { className: classes, children: useRandomGradient ? (jsx(RandomGradient, { scheme: getGradientScheme(), tone: gradientTone, animated: gradientAnimated, height: "100%", width: "100%", className: "designbase-ad-banner__gradient-bg", children: bannerContent })) : (bannerContent) }));
|
|
2637
2709
|
};
|
|
2638
2710
|
// 기본 텍스트 헬퍼
|
|
@@ -4772,6 +4844,58 @@ const Card = forwardRef(function Card({ title, subtitle, description, children,
|
|
|
4772
4844
|
});
|
|
4773
4845
|
Card.displayName = 'Card';
|
|
4774
4846
|
|
|
4847
|
+
const Indicator = ({ current, total, type = 'dots', direction = 'horizontal', size = 'm', clickable = false, onStepClick, timer = false, timerDuration = 3000, onTimerComplete, className, ...props }) => {
|
|
4848
|
+
const [progress, setProgress] = useState(0);
|
|
4849
|
+
const timerRef = useRef(null);
|
|
4850
|
+
const startRef = useRef(0);
|
|
4851
|
+
useEffect(() => {
|
|
4852
|
+
if (!timer)
|
|
4853
|
+
return;
|
|
4854
|
+
setProgress(0);
|
|
4855
|
+
startRef.current = Date.now();
|
|
4856
|
+
const animate = () => {
|
|
4857
|
+
const elapsed = Date.now() - startRef.current;
|
|
4858
|
+
const ratio = Math.min(elapsed / timerDuration, 1);
|
|
4859
|
+
setProgress(ratio);
|
|
4860
|
+
if (ratio < 1) {
|
|
4861
|
+
timerRef.current = requestAnimationFrame(animate);
|
|
4862
|
+
}
|
|
4863
|
+
else {
|
|
4864
|
+
onTimerComplete?.();
|
|
4865
|
+
}
|
|
4866
|
+
};
|
|
4867
|
+
timerRef.current = requestAnimationFrame(animate);
|
|
4868
|
+
return () => {
|
|
4869
|
+
if (timerRef.current)
|
|
4870
|
+
cancelAnimationFrame(timerRef.current);
|
|
4871
|
+
};
|
|
4872
|
+
}, [current, timer, timerDuration, onTimerComplete]);
|
|
4873
|
+
const handleStepClick = (i) => {
|
|
4874
|
+
if (clickable && onStepClick)
|
|
4875
|
+
onStepClick(i);
|
|
4876
|
+
};
|
|
4877
|
+
const classes = clsx('designbase-indicator', `designbase-indicator--${type}`, `designbase-indicator--${direction}`, `designbase-indicator--${size}`, { 'designbase-indicator--clickable': clickable }, className);
|
|
4878
|
+
// 숫자 타입
|
|
4879
|
+
if (type === 'numbers') {
|
|
4880
|
+
return (jsxs("div", { className: classes, role: "progressbar", "aria-valuenow": current + 1, "aria-valuemin": 1, "aria-valuemax": total, children: [jsx("span", { className: "designbase-indicator__current", children: current + 1 }), jsx("span", { className: "designbase-indicator__separator", children: "/" }), jsx("span", { className: "designbase-indicator__total", children: total })] }));
|
|
4881
|
+
}
|
|
4882
|
+
// 라인 타입 (가로 progress bar)
|
|
4883
|
+
if (type === 'line') {
|
|
4884
|
+
return (jsx("div", { className: classes, role: "progressbar", "aria-valuenow": current + 1, "aria-valuemin": 1, "aria-valuemax": total, children: Array.from({ length: total }, (_, i) => (jsx("div", { className: clsx('designbase-indicator__line-segment', {
|
|
4885
|
+
'designbase-indicator__line-segment--active': i < current,
|
|
4886
|
+
'designbase-indicator__line-segment--current': i === current
|
|
4887
|
+
}), children: timer && i === current && (jsx("div", { className: "designbase-indicator__line-fill", style: { transform: `scaleX(${progress})` } })) }, i))) }));
|
|
4888
|
+
}
|
|
4889
|
+
// 점(dot) 타입 + 원형 애니메이션
|
|
4890
|
+
return (jsx("div", { className: classes, role: "progressbar", "aria-valuenow": current + 1, "aria-valuemin": 1, "aria-valuemax": total, children: Array.from({ length: total }, (_, i) => {
|
|
4891
|
+
const active = i === current;
|
|
4892
|
+
const circumference = 2 * Math.PI * 15.9155;
|
|
4893
|
+
const offset = circumference * (1 - progress);
|
|
4894
|
+
return (jsx("button", { className: clsx('designbase-indicator__dot', { 'designbase-indicator__dot--active': active }), onClick: () => handleStepClick(i), disabled: !clickable, children: timer && active && (jsxs("svg", { className: "designbase-indicator__circle", viewBox: "0 0 36 36", children: [jsx("circle", { className: "designbase-indicator__circle-bg", cx: "18", cy: "18", r: "15.9155", fill: "none", stroke: "var(--db-border-base)", strokeWidth: "2" }), 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));
|
|
4895
|
+
}) }));
|
|
4896
|
+
};
|
|
4897
|
+
Indicator.displayName = 'Indicator';
|
|
4898
|
+
|
|
4775
4899
|
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, }) => {
|
|
4776
4900
|
// 아이콘 크기 계산 (m이 기본값)
|
|
4777
4901
|
const iconSize = size === 's' ? 16 : size === 'l' ? 24 : size === 'xl' ? 28 : 20;
|
|
@@ -5040,10 +5164,6 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
|
|
|
5040
5164
|
willChange: isDragging ? 'transform' : 'auto',
|
|
5041
5165
|
};
|
|
5042
5166
|
};
|
|
5043
|
-
// 인디케이터 활성 상태 확인
|
|
5044
|
-
const isIndicatorActive = (index) => {
|
|
5045
|
-
return index === currentIndex;
|
|
5046
|
-
};
|
|
5047
5167
|
// 네비게이션 버튼 비활성화 상태
|
|
5048
5168
|
const isPrevDisabled = !infinite && currentIndex === 0;
|
|
5049
5169
|
const isNextDisabled = !infinite && currentIndex === items.length - 1;
|
|
@@ -5082,9 +5202,7 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
|
|
|
5082
5202
|
}, title: "\uACF5\uC720", children: jsx(ShareAltIcon, { size: iconSize, color: "currentColor" }) })), enableDownload && item.image && (jsx("button", { className: "designbase-carousel__action-button designbase-carousel__action-button--download", onClick: (e) => {
|
|
5083
5203
|
e.stopPropagation();
|
|
5084
5204
|
handleDownload(item, index);
|
|
5085
|
-
}, title: "\uB2E4\uC6B4\uB85C\uB4DC", children: jsx(DownloadIcon, { size: iconSize, color: "currentColor" }) }))] })] }, item.id))) }), showNavigation && items.length > 1 && (jsxs(Fragment, { children: [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: jsx(ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), 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: jsx(ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] })), enablePaging && items.length > 1 && (jsxs(Fragment, { children: [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: jsx(ArrowLeftIcon, { size: iconSize, color: "currentColor" }) }), 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: jsx(ArrowRightIcon, { size: iconSize, color: "currentColor" }) })] })), enableFullscreen && (jsx("button", { className: "designbase-carousel__fullscreen-button", onClick: handleFullscreenToggle, disabled: disabled || readonly, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsx(ShrinkIcon, { size: iconSize, color: "currentColor" }) : jsx(ExpandIcon, { size: iconSize, color: "currentColor" }) }))] }), jsxs("div", { className: "designbase-carousel__controls", children: [showIndicators && items.length > 1 && (jsx("div", { className: "designbase-carousel__indicators", children: items.
|
|
5086
|
-
'designbase-carousel__indicator--active': isIndicatorActive(index),
|
|
5087
|
-
}), onClick: () => goToSlide(index), disabled: disabled || readonly, title: `슬라이드 ${index + 1}로 이동`, type: "button", children: indicatorStyle === 'thumbnails' && item.thumbnail ? (jsx("img", { src: item.thumbnail, alt: item.title || `썸네일 ${index + 1}`, className: "designbase-carousel__indicator-thumbnail" })) : indicatorStyle === 'numbers' ? (jsx("span", { className: "designbase-carousel__indicator-number", children: index + 1 })) : indicatorStyle === 'lines' ? (jsx("span", { className: "designbase-carousel__indicator-line" })) : (jsx("span", { className: "designbase-carousel__indicator-dot" })) }, index))) })), showAutoPlayControl && items.length > 1 && (jsx("button", { className: "designbase-carousel__autoplay-button", onClick: handleAutoPlayToggle, disabled: disabled || readonly, title: isAutoPlaying ? "자동 재생 정지" : "자동 재생 시작", type: "button", children: isAutoPlaying ? jsx(PauseIcon, { size: iconSize, color: "currentColor" }) : jsx(PlayIcon, { size: iconSize, color: "currentColor" }) }))] })] }));
|
|
5205
|
+
}, title: "\uB2E4\uC6B4\uB85C\uB4DC", children: jsx(DownloadIcon, { size: iconSize, color: "currentColor" }) }))] })] }, item.id))) }), showNavigation && items.length > 1 && (jsxs(Fragment, { children: [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: jsx(ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), 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: jsx(ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] })), enablePaging && items.length > 1 && (jsxs(Fragment, { children: [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: jsx(ArrowLeftIcon, { size: iconSize, color: "currentColor" }) }), 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: jsx(ArrowRightIcon, { size: iconSize, color: "currentColor" }) })] })), enableFullscreen && (jsx("button", { className: "designbase-carousel__fullscreen-button", onClick: handleFullscreenToggle, disabled: disabled || readonly, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsx(ShrinkIcon, { size: iconSize, color: "currentColor" }) : jsx(ExpandIcon, { size: iconSize, color: "currentColor" }) }))] }), jsxs("div", { className: "designbase-carousel__controls", children: [showIndicators && items.length > 1 && (jsx("div", { className: "designbase-carousel__indicators", children: 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 && (jsx("button", { className: "designbase-carousel__autoplay-button", onClick: handleAutoPlayToggle, disabled: disabled || readonly, title: isAutoPlaying ? "자동 재생 정지" : "자동 재생 시작", type: "button", children: isAutoPlaying ? jsx(PauseIcon, { size: iconSize, color: "currentColor" }) : jsx(PlayIcon, { size: iconSize, color: "currentColor" }) }))] })] }));
|
|
5088
5206
|
};
|
|
5089
5207
|
|
|
5090
5208
|
const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', deletable = false, onDelete, onClick, startIcon, endIcon, disabled = false, selected = false, fullWidth = false, className, ...props }) => {
|
|
@@ -5502,76 +5620,6 @@ const Confirm = ({ open, title, children, confirmText = '확인', cancelText = '
|
|
|
5502
5620
|
};
|
|
5503
5621
|
Confirm.displayName = 'Confirm';
|
|
5504
5622
|
|
|
5505
|
-
const Countdown = ({ endDate, duration, size = 'm', variant = 'default', showDays = true, showHours = true, showMinutes = true, showSeconds = true, showLabels = true, labels = {
|
|
5506
|
-
days: '일',
|
|
5507
|
-
hours: '시간',
|
|
5508
|
-
minutes: '분',
|
|
5509
|
-
seconds: '초',
|
|
5510
|
-
}, onComplete, onTick, className, }) => {
|
|
5511
|
-
// 종료 시간 계산
|
|
5512
|
-
const targetTime = useMemo(() => {
|
|
5513
|
-
if (endDate) {
|
|
5514
|
-
return typeof endDate === 'string' ? new Date(endDate).getTime() : endDate.getTime();
|
|
5515
|
-
}
|
|
5516
|
-
if (duration) {
|
|
5517
|
-
return Date.now() + duration * 1000;
|
|
5518
|
-
}
|
|
5519
|
-
return Date.now();
|
|
5520
|
-
}, [endDate, duration]);
|
|
5521
|
-
// 남은 시간 계산 함수
|
|
5522
|
-
const calculateTimeRemaining = () => {
|
|
5523
|
-
const now = Date.now();
|
|
5524
|
-
const total = targetTime - now;
|
|
5525
|
-
if (total <= 0) {
|
|
5526
|
-
return { days: 0, hours: 0, minutes: 0, seconds: 0, total: 0 };
|
|
5527
|
-
}
|
|
5528
|
-
const seconds = Math.floor((total / 1000) % 60);
|
|
5529
|
-
const minutes = Math.floor((total / 1000 / 60) % 60);
|
|
5530
|
-
const hours = Math.floor((total / (1000 * 60 * 60)) % 24);
|
|
5531
|
-
const days = Math.floor(total / (1000 * 60 * 60 * 24));
|
|
5532
|
-
return { days, hours, minutes, seconds, total };
|
|
5533
|
-
};
|
|
5534
|
-
const [timeRemaining, setTimeRemaining] = useState(calculateTimeRemaining());
|
|
5535
|
-
useEffect(() => {
|
|
5536
|
-
const timer = setInterval(() => {
|
|
5537
|
-
const newTimeRemaining = calculateTimeRemaining();
|
|
5538
|
-
setTimeRemaining(newTimeRemaining);
|
|
5539
|
-
onTick?.(newTimeRemaining);
|
|
5540
|
-
if (newTimeRemaining.total <= 0) {
|
|
5541
|
-
clearInterval(timer);
|
|
5542
|
-
onComplete?.();
|
|
5543
|
-
}
|
|
5544
|
-
}, 1000);
|
|
5545
|
-
return () => clearInterval(timer);
|
|
5546
|
-
}, [targetTime, onComplete, onTick]);
|
|
5547
|
-
const classes = clsx('designbase-countdown', `designbase-countdown--${size}`, `designbase-countdown--${variant}`, {
|
|
5548
|
-
'designbase-countdown--with-labels': showLabels,
|
|
5549
|
-
'designbase-countdown--expired': timeRemaining.total <= 0,
|
|
5550
|
-
}, className);
|
|
5551
|
-
// 숫자를 두 자리로 포맷
|
|
5552
|
-
const formatNumber = (num) => {
|
|
5553
|
-
return num.toString().padStart(2, '0');
|
|
5554
|
-
};
|
|
5555
|
-
const renderTimeUnit = (value, label, show) => {
|
|
5556
|
-
if (!show)
|
|
5557
|
-
return null;
|
|
5558
|
-
return (jsxs("div", { className: "designbase-countdown__unit", children: [jsx("div", { className: "designbase-countdown__value", children: formatNumber(value) }), showLabels && (jsx("div", { className: "designbase-countdown__label", children: label }))] }));
|
|
5559
|
-
};
|
|
5560
|
-
const renderSeparator = () => {
|
|
5561
|
-
if (variant === 'minimal')
|
|
5562
|
-
return null;
|
|
5563
|
-
return jsx("div", { className: "designbase-countdown__separator", children: ":" });
|
|
5564
|
-
};
|
|
5565
|
-
const units = [
|
|
5566
|
-
{ value: timeRemaining.days, label: labels.days || '일', show: showDays },
|
|
5567
|
-
{ value: timeRemaining.hours, label: labels.hours || '시간', show: showHours },
|
|
5568
|
-
{ value: timeRemaining.minutes, label: labels.minutes || '분', show: showMinutes },
|
|
5569
|
-
{ value: timeRemaining.seconds, label: labels.seconds || '초', show: showSeconds },
|
|
5570
|
-
].filter(unit => unit.show);
|
|
5571
|
-
return (jsx("div", { className: classes, children: units.map((unit, index) => (jsxs(React.Fragment, { children: [renderTimeUnit(unit.value, unit.label, unit.show), index < units.length - 1 && renderSeparator()] }, unit.label))) }));
|
|
5572
|
-
};
|
|
5573
|
-
Countdown.displayName = 'Countdown';
|
|
5574
|
-
|
|
5575
5623
|
const Container = ({ size = 'l', maxWidth, variant = 'plain', padding = 'm', margin = 'none', backgroundColor, border = false, rounded = false, shadow = false, className, children, }) => {
|
|
5576
5624
|
// 크기별 최대 폭 설정 (토큰 기반)
|
|
5577
5625
|
const getMaxWidth = () => {
|
|
@@ -6590,67 +6638,70 @@ Progressbar.displayName = 'Progressbar';
|
|
|
6590
6638
|
const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multiple = false, showFileList = true, showProgress = true, disabled = false, readonly = false, onUpload, onRemove, onRetry, className, }) => {
|
|
6591
6639
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
|
6592
6640
|
const [isUploading, setIsUploading] = useState(false);
|
|
6593
|
-
//
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
return;
|
|
6597
|
-
const newUploadFiles = files.map(file => ({
|
|
6598
|
-
file,
|
|
6599
|
-
id: `${Date.now()}-${Math.random()}`,
|
|
6600
|
-
status: 'pending',
|
|
6601
|
-
progress: 0,
|
|
6602
|
-
}));
|
|
6603
|
-
if (multiple) {
|
|
6604
|
-
setUploadedFiles(prev => [...prev, ...newUploadFiles]);
|
|
6605
|
-
}
|
|
6606
|
-
else {
|
|
6607
|
-
setUploadedFiles(newUploadFiles);
|
|
6608
|
-
}
|
|
6609
|
-
// 시뮬레이션된 업로드 진행
|
|
6610
|
-
simulateUpload(newUploadFiles);
|
|
6611
|
-
}, [disabled, readonly, multiple]);
|
|
6612
|
-
// 업로드 시뮬레이션
|
|
6641
|
+
// ────────────────────────────────────────────────
|
|
6642
|
+
// 업로드 시뮬레이션 (먼저 정의해야 함!)
|
|
6643
|
+
// ────────────────────────────────────────────────
|
|
6613
6644
|
const simulateUpload = useCallback((files) => {
|
|
6614
6645
|
setIsUploading(true);
|
|
6615
6646
|
files.forEach((uploadFile, index) => {
|
|
6616
6647
|
// 업로드 시작
|
|
6617
|
-
setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id
|
|
6618
|
-
? { ...f, status: 'uploading' }
|
|
6619
|
-
: f));
|
|
6620
|
-
// 진행률 시뮬레이션
|
|
6648
|
+
setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id ? { ...f, status: 'uploading' } : f));
|
|
6621
6649
|
let progress = 0;
|
|
6622
6650
|
const interval = setInterval(() => {
|
|
6623
6651
|
progress += Math.random() * 20;
|
|
6624
6652
|
if (progress >= 100) {
|
|
6625
6653
|
progress = 100;
|
|
6626
6654
|
clearInterval(interval);
|
|
6627
|
-
//
|
|
6655
|
+
// 완료 처리
|
|
6628
6656
|
setTimeout(() => {
|
|
6629
6657
|
setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id
|
|
6630
6658
|
? {
|
|
6631
6659
|
...f,
|
|
6632
6660
|
status: 'success',
|
|
6633
6661
|
progress: 100,
|
|
6634
|
-
uploadedAt: new Date()
|
|
6662
|
+
uploadedAt: new Date(),
|
|
6635
6663
|
}
|
|
6636
6664
|
: f));
|
|
6637
|
-
// 모든
|
|
6665
|
+
// 모든 업로드 완료 확인
|
|
6638
6666
|
setTimeout(() => {
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6667
|
+
setUploadedFiles(prev => {
|
|
6668
|
+
const allDone = prev.every(f => f.status === 'success');
|
|
6669
|
+
if (allDone)
|
|
6670
|
+
setIsUploading(false);
|
|
6671
|
+
return prev;
|
|
6672
|
+
});
|
|
6673
|
+
}, 300);
|
|
6644
6674
|
}, 200);
|
|
6645
6675
|
}
|
|
6646
6676
|
else {
|
|
6647
|
-
setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id
|
|
6648
|
-
? { ...f, progress }
|
|
6649
|
-
: f));
|
|
6677
|
+
setUploadedFiles(prev => prev.map(f => f.id === uploadFile.id ? { ...f, progress } : f));
|
|
6650
6678
|
}
|
|
6651
|
-
}, 100 + index * 50);
|
|
6679
|
+
}, 100 + index * 50);
|
|
6652
6680
|
});
|
|
6653
|
-
}, [
|
|
6681
|
+
}, []);
|
|
6682
|
+
// ────────────────────────────────────────────────
|
|
6683
|
+
// 파일 선택 시 처리
|
|
6684
|
+
// ────────────────────────────────────────────────
|
|
6685
|
+
const handleFileSelect = useCallback((files) => {
|
|
6686
|
+
if (disabled || readonly)
|
|
6687
|
+
return;
|
|
6688
|
+
const newUploadFiles = files.map(file => ({
|
|
6689
|
+
file,
|
|
6690
|
+
id: `${Date.now()}-${Math.random()}`,
|
|
6691
|
+
status: 'pending',
|
|
6692
|
+
progress: 0,
|
|
6693
|
+
}));
|
|
6694
|
+
if (multiple) {
|
|
6695
|
+
setUploadedFiles(prev => [...prev, ...newUploadFiles]);
|
|
6696
|
+
}
|
|
6697
|
+
else {
|
|
6698
|
+
setUploadedFiles(newUploadFiles);
|
|
6699
|
+
}
|
|
6700
|
+
// ✅ 부모 콜백 즉시 실행 (파일 업로드 감지용)
|
|
6701
|
+
onUpload?.(newUploadFiles);
|
|
6702
|
+
// 진행률 애니메이션
|
|
6703
|
+
simulateUpload(newUploadFiles);
|
|
6704
|
+
}, [disabled, readonly, multiple, onUpload, simulateUpload]);
|
|
6654
6705
|
// 파일 제거
|
|
6655
6706
|
const handleRemove = useCallback((fileId) => {
|
|
6656
6707
|
if (disabled || readonly)
|
|
@@ -6658,17 +6709,16 @@ const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multip
|
|
|
6658
6709
|
setUploadedFiles(prev => prev.filter(f => f.id !== fileId));
|
|
6659
6710
|
onRemove?.(fileId);
|
|
6660
6711
|
}, [disabled, readonly, onRemove]);
|
|
6661
|
-
//
|
|
6712
|
+
// 재시도
|
|
6662
6713
|
const handleRetry = useCallback((fileId) => {
|
|
6663
6714
|
if (disabled || readonly)
|
|
6664
6715
|
return;
|
|
6665
6716
|
const file = uploadedFiles.find(f => f.id === fileId);
|
|
6666
|
-
if (file)
|
|
6717
|
+
if (file)
|
|
6667
6718
|
simulateUpload([file]);
|
|
6668
|
-
}
|
|
6669
6719
|
onRetry?.(fileId);
|
|
6670
6720
|
}, [disabled, readonly, uploadedFiles, simulateUpload, onRetry]);
|
|
6671
|
-
// 파일 크기
|
|
6721
|
+
// 파일 크기 표시
|
|
6672
6722
|
const formatFileSize = useCallback((bytes) => {
|
|
6673
6723
|
if (bytes === 0)
|
|
6674
6724
|
return '0 Bytes';
|
|
@@ -6681,7 +6731,7 @@ const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multip
|
|
|
6681
6731
|
const getStatusIcon = useCallback((status) => {
|
|
6682
6732
|
switch (status) {
|
|
6683
6733
|
case 'pending':
|
|
6684
|
-
return jsx(
|
|
6734
|
+
return jsx(FileBlankIcon, { size: 20 });
|
|
6685
6735
|
case 'uploading':
|
|
6686
6736
|
return jsx(Spinner, { type: "circular", size: "s" });
|
|
6687
6737
|
case 'success':
|
|
@@ -6692,10 +6742,13 @@ const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multip
|
|
|
6692
6742
|
return null;
|
|
6693
6743
|
}
|
|
6694
6744
|
}, []);
|
|
6745
|
+
// ────────────────────────────────────────────────
|
|
6746
|
+
// 렌더링
|
|
6747
|
+
// ────────────────────────────────────────────────
|
|
6695
6748
|
return (jsxs("div", { className: clsx('designbase-file-uploader', `designbase-file-uploader--size-${size}`, `designbase-file-uploader--variant-${variant}`, {
|
|
6696
6749
|
'designbase-file-uploader--disabled': disabled,
|
|
6697
6750
|
'designbase-file-uploader--readonly': readonly,
|
|
6698
|
-
}, className), children: [jsx(Dropzone, { size: size, variant: variant, accept: accept, maxSize: maxSize, multiple: multiple, disabled: disabled, readonly: readonly, onFileSelect: handleFileSelect }), showFileList && uploadedFiles.length > 0 && (jsxs("div", { className: "designbase-file-uploader__file-list", children: [jsxs("div", { className: "designbase-file-uploader__file-list-title", children: ["\uC5C5\uB85C\uB4DC\uB41C \uD30C\uC77C\uB4E4 (", uploadedFiles.length, "\uAC1C)"] }), jsx("div", { className: "designbase-file-uploader__file-items", children: uploadedFiles.map(
|
|
6751
|
+
}, className), children: [jsx(Dropzone, { size: size, variant: variant, accept: accept, maxSize: maxSize, multiple: multiple, disabled: disabled, readonly: readonly, onFileSelect: handleFileSelect }), showFileList && uploadedFiles.length > 0 && (jsxs("div", { className: "designbase-file-uploader__file-list", children: [jsxs("div", { className: "designbase-file-uploader__file-list-title", children: ["\uC5C5\uB85C\uB4DC\uB41C \uD30C\uC77C\uB4E4 (", uploadedFiles.length, "\uAC1C)"] }), jsx("div", { className: "designbase-file-uploader__file-items", children: uploadedFiles.map(uploadFile => (jsxs("div", { className: clsx('designbase-file-uploader__file-item', `designbase-file-uploader__file-item--${uploadFile.status}`), children: [jsxs("div", { className: "designbase-file-uploader__file-info", children: [jsx("div", { className: "designbase-file-uploader__file-icon", children: getStatusIcon(uploadFile.status) }), jsxs("div", { className: "designbase-file-uploader__file-details", children: [jsx("div", { className: "designbase-file-uploader__file-name", children: uploadFile.file.name }), jsx("div", { className: "designbase-file-uploader__file-size", children: formatFileSize(uploadFile.file.size) }), uploadFile.error && (jsx("div", { className: "designbase-file-uploader__file-error", children: uploadFile.error }))] })] }), showProgress && uploadFile.status === 'uploading' && (jsx("div", { className: "designbase-file-uploader__progress", children: jsx(Progressbar, { value: uploadFile.progress || 0, size: "s", variant: "primary", style: "solid", showLabel: true, labelPosition: "inside", fullWidth: true }) })), jsxs("div", { className: "designbase-file-uploader__file-actions", children: [uploadFile.status === 'error' && (jsx("button", { className: "designbase-file-uploader__retry-button", onClick: () => handleRetry(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC7AC\uC2DC\uB3C4" })), jsx("button", { className: "designbase-file-uploader__remove-button", onClick: () => handleRemove(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC0AD\uC81C" })] })] }, uploadFile.id))) })] }))] }));
|
|
6699
6752
|
};
|
|
6700
6753
|
|
|
6701
6754
|
const FloatingActionButton = forwardRef(({ size = 'm', variant = 'primary', icon, loading = false, disabled = false, onClick, className, style, ...props }, forwardedRef) => {
|
|
@@ -8357,6 +8410,229 @@ const Masonry = ({ images, columns = 3, spacing = 'm', ratio = 'auto', fit = 'co
|
|
|
8357
8410
|
})), currentIndex: selectedImageIndex, isOpen: true, onOpenChange: handleLightboxClose, onImageChange: handleLightboxNavigate }))] }));
|
|
8358
8411
|
};
|
|
8359
8412
|
|
|
8413
|
+
const OnboardingModal = ({ steps, currentStep = 0, isOpen, onClose, onStepChange, onComplete, showCloseButton = true, showIndicator = true, closeIcon: CloseIconComponent = CloseIcon, prevIcon: PrevIconComponent = ChevronLeftIcon, nextIcon: NextIconComponent = ChevronRightIcon, indicatorType = 'dots', indicatorSize = 'm', className, ...props }) => {
|
|
8414
|
+
const [internalStep, setInternalStep] = useState(currentStep);
|
|
8415
|
+
useEffect(() => {
|
|
8416
|
+
setInternalStep(currentStep);
|
|
8417
|
+
}, [currentStep]);
|
|
8418
|
+
const currentStepData = steps[internalStep];
|
|
8419
|
+
const isFirstStep = internalStep === 0;
|
|
8420
|
+
const isLastStep = internalStep === steps.length - 1;
|
|
8421
|
+
const handlePrev = () => {
|
|
8422
|
+
if (!isFirstStep) {
|
|
8423
|
+
const newStep = internalStep - 1;
|
|
8424
|
+
setInternalStep(newStep);
|
|
8425
|
+
onStepChange?.(newStep);
|
|
8426
|
+
}
|
|
8427
|
+
};
|
|
8428
|
+
const handleNext = () => {
|
|
8429
|
+
if (isLastStep) {
|
|
8430
|
+
onComplete?.();
|
|
8431
|
+
}
|
|
8432
|
+
else {
|
|
8433
|
+
const newStep = internalStep + 1;
|
|
8434
|
+
setInternalStep(newStep);
|
|
8435
|
+
onStepChange?.(newStep);
|
|
8436
|
+
}
|
|
8437
|
+
};
|
|
8438
|
+
const handleKeyDown = (e) => {
|
|
8439
|
+
if (e.key === 'Escape') {
|
|
8440
|
+
onClose();
|
|
8441
|
+
}
|
|
8442
|
+
else if (e.key === 'ArrowLeft' && !isFirstStep) {
|
|
8443
|
+
handlePrev();
|
|
8444
|
+
}
|
|
8445
|
+
else if (e.key === 'ArrowRight' && !isLastStep) {
|
|
8446
|
+
handleNext();
|
|
8447
|
+
}
|
|
8448
|
+
};
|
|
8449
|
+
if (!isOpen || !currentStepData) {
|
|
8450
|
+
return null;
|
|
8451
|
+
}
|
|
8452
|
+
const classes = clsx('designbase-onboarding-modal', className);
|
|
8453
|
+
return (jsxs("div", { className: classes, onKeyDown: handleKeyDown, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-labelledby": "onboarding-title", "aria-describedby": "onboarding-description", ...props, children: [jsx("div", { className: "designbase-onboarding-modal__overlay", onClick: onClose }), jsxs("div", { className: "designbase-onboarding-modal__content", children: [showCloseButton && (jsx("button", { type: "button", className: "designbase-onboarding-modal__close-button", onClick: onClose, "aria-label": "\uB2EB\uAE30", children: jsx(CloseIconComponent, { size: 20 }) })), jsxs("div", { className: "designbase-onboarding-modal__main", children: [currentStepData.image && (jsx("div", { className: "designbase-onboarding-modal__image", children: typeof currentStepData.image === 'string' ? (jsx("img", { src: currentStepData.image, alt: currentStepData.title || '온보딩 이미지' })) : (currentStepData.image) })), jsxs("div", { className: "designbase-onboarding-modal__text", children: [currentStepData.title && (jsx("h2", { id: "onboarding-title", className: "designbase-onboarding-modal__title", children: currentStepData.title })), currentStepData.description && (jsx("p", { id: "onboarding-description", className: "designbase-onboarding-modal__description", children: currentStepData.description })), currentStepData.content && (jsx("div", { className: "designbase-onboarding-modal__content-area", children: currentStepData.content }))] })] }), jsxs("div", { className: "designbase-onboarding-modal__navigation", children: [jsx(Button, { variant: "secondary", size: "m", startIcon: PrevIconComponent, onClick: handlePrev, disabled: isFirstStep, "aria-label": "\uC774\uC804", children: "\uC774\uC804" }), showIndicator && (jsx(Indicator, { current: internalStep, total: steps.length, type: indicatorType, size: indicatorSize, clickable: true, onStepClick: (step) => {
|
|
8454
|
+
setInternalStep(step);
|
|
8455
|
+
onStepChange?.(step);
|
|
8456
|
+
} })), jsx(Button, { variant: "primary", size: "m", endIcon: !isLastStep ? NextIconComponent : undefined, onClick: handleNext, "aria-label": isLastStep ? '완료' : '다음', children: isLastStep ? '완료' : '다음' })] })] })] }));
|
|
8457
|
+
};
|
|
8458
|
+
OnboardingModal.displayName = 'OnboardingModal';
|
|
8459
|
+
|
|
8460
|
+
const Tutorial = ({ steps, currentStep = 0, isActive, onStart, onEnd, onStepChange, onNext, onPrev, indicatorType = 'numbers', indicatorSize = 'm', closeIcon: CloseIconComponent = CloseIcon, prevIcon: PrevIconComponent = ChevronLeftIcon, nextIcon: NextIconComponent = ChevronRightIcon, className, ...props }) => {
|
|
8461
|
+
const [internalStep, setInternalStep] = useState(currentStep);
|
|
8462
|
+
const [targetElement, setTargetElement] = useState(null);
|
|
8463
|
+
const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0 });
|
|
8464
|
+
const [popoverPlacement, setPopoverPlacement] = useState('bottom');
|
|
8465
|
+
const overlayRef = useRef(null);
|
|
8466
|
+
const popoverRef = useRef(null);
|
|
8467
|
+
const currentStepData = steps[internalStep];
|
|
8468
|
+
const isFirstStep = internalStep === 0;
|
|
8469
|
+
const isLastStep = internalStep === steps.length - 1;
|
|
8470
|
+
// 타겟 요소 찾기 및 위치 계산
|
|
8471
|
+
const updateTargetElement = useCallback(() => {
|
|
8472
|
+
if (!currentStepData?.target)
|
|
8473
|
+
return;
|
|
8474
|
+
// 이전 타겟 요소의 강조 스타일 제거
|
|
8475
|
+
if (targetElement) {
|
|
8476
|
+
targetElement.classList.remove('designbase-tutorial__target');
|
|
8477
|
+
targetElement.style.position = '';
|
|
8478
|
+
targetElement.style.zIndex = '';
|
|
8479
|
+
}
|
|
8480
|
+
const element = document.querySelector(currentStepData.target);
|
|
8481
|
+
if (!element)
|
|
8482
|
+
return;
|
|
8483
|
+
setTargetElement(element);
|
|
8484
|
+
// 타겟 요소에 강조 스타일 적용
|
|
8485
|
+
element.style.position = 'relative';
|
|
8486
|
+
element.style.zIndex = '1000';
|
|
8487
|
+
element.classList.add('designbase-tutorial__target');
|
|
8488
|
+
// 팝오버 위치 계산
|
|
8489
|
+
const rect = element.getBoundingClientRect();
|
|
8490
|
+
const viewportWidth = window.innerWidth;
|
|
8491
|
+
const viewportHeight = window.innerHeight;
|
|
8492
|
+
const popoverWidth = 300;
|
|
8493
|
+
const popoverHeight = 200;
|
|
8494
|
+
const margin = 16;
|
|
8495
|
+
let placement = 'bottom';
|
|
8496
|
+
let top = rect.bottom + margin;
|
|
8497
|
+
let left = rect.left + (rect.width / 2) - (popoverWidth / 2);
|
|
8498
|
+
// 자동 배치 로직
|
|
8499
|
+
if (currentStepData.placement === 'auto') {
|
|
8500
|
+
// 위쪽 공간 확인
|
|
8501
|
+
if (rect.top - popoverHeight - margin > 0) {
|
|
8502
|
+
placement = 'top';
|
|
8503
|
+
top = rect.top - popoverHeight - margin;
|
|
8504
|
+
}
|
|
8505
|
+
// 아래쪽 공간 확인
|
|
8506
|
+
else if (rect.bottom + popoverHeight + margin < viewportHeight) {
|
|
8507
|
+
placement = 'bottom';
|
|
8508
|
+
top = rect.bottom + margin;
|
|
8509
|
+
}
|
|
8510
|
+
// 왼쪽 공간 확인
|
|
8511
|
+
else if (rect.left - popoverWidth - margin > 0) {
|
|
8512
|
+
placement = 'left';
|
|
8513
|
+
top = rect.top + (rect.height / 2) - (popoverHeight / 2);
|
|
8514
|
+
left = rect.left - popoverWidth - margin;
|
|
8515
|
+
}
|
|
8516
|
+
// 오른쪽 공간 확인
|
|
8517
|
+
else if (rect.right + popoverWidth + margin < viewportWidth) {
|
|
8518
|
+
placement = 'right';
|
|
8519
|
+
top = rect.top + (rect.height / 2) - (popoverHeight / 2);
|
|
8520
|
+
left = rect.right + margin;
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8523
|
+
else {
|
|
8524
|
+
placement = currentStepData.placement || 'bottom';
|
|
8525
|
+
switch (placement) {
|
|
8526
|
+
case 'top':
|
|
8527
|
+
top = rect.top - popoverHeight - margin;
|
|
8528
|
+
break;
|
|
8529
|
+
case 'bottom':
|
|
8530
|
+
top = rect.bottom + margin;
|
|
8531
|
+
break;
|
|
8532
|
+
case 'left':
|
|
8533
|
+
top = rect.top + (rect.height / 2) - (popoverHeight / 2);
|
|
8534
|
+
left = rect.left - popoverWidth - margin;
|
|
8535
|
+
break;
|
|
8536
|
+
case 'right':
|
|
8537
|
+
top = rect.top + (rect.height / 2) - (popoverHeight / 2);
|
|
8538
|
+
left = rect.right + margin;
|
|
8539
|
+
break;
|
|
8540
|
+
}
|
|
8541
|
+
}
|
|
8542
|
+
// 뷰포트 경계 확인 및 조정
|
|
8543
|
+
left = Math.max(margin, Math.min(left, viewportWidth - popoverWidth - margin));
|
|
8544
|
+
top = Math.max(margin, Math.min(top, viewportHeight - popoverHeight - margin));
|
|
8545
|
+
setPopoverPosition({ top, left });
|
|
8546
|
+
setPopoverPlacement(placement);
|
|
8547
|
+
}, [currentStepData]);
|
|
8548
|
+
// 단계 변경 시 타겟 업데이트
|
|
8549
|
+
useEffect(() => {
|
|
8550
|
+
if (isActive && currentStepData) {
|
|
8551
|
+
updateTargetElement();
|
|
8552
|
+
}
|
|
8553
|
+
}, [internalStep, isActive, updateTargetElement]);
|
|
8554
|
+
// 튜토리얼 비활성화 시 모든 강조 스타일 제거
|
|
8555
|
+
useEffect(() => {
|
|
8556
|
+
if (!isActive && targetElement) {
|
|
8557
|
+
targetElement.classList.remove('designbase-tutorial__target');
|
|
8558
|
+
targetElement.style.position = '';
|
|
8559
|
+
targetElement.style.zIndex = '';
|
|
8560
|
+
setTargetElement(null);
|
|
8561
|
+
}
|
|
8562
|
+
}, [isActive, targetElement]);
|
|
8563
|
+
// 윈도우 리사이즈 시 위치 재계산
|
|
8564
|
+
useEffect(() => {
|
|
8565
|
+
if (!isActive)
|
|
8566
|
+
return;
|
|
8567
|
+
const handleResize = () => {
|
|
8568
|
+
updateTargetElement();
|
|
8569
|
+
};
|
|
8570
|
+
window.addEventListener('resize', handleResize);
|
|
8571
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
8572
|
+
}, [isActive, updateTargetElement]);
|
|
8573
|
+
const handleNext = () => {
|
|
8574
|
+
if (isLastStep) {
|
|
8575
|
+
handleEnd();
|
|
8576
|
+
}
|
|
8577
|
+
else {
|
|
8578
|
+
// 이전 타겟 요소의 강조 스타일 제거
|
|
8579
|
+
if (targetElement) {
|
|
8580
|
+
targetElement.classList.remove('designbase-tutorial__target');
|
|
8581
|
+
targetElement.style.position = '';
|
|
8582
|
+
targetElement.style.zIndex = '';
|
|
8583
|
+
}
|
|
8584
|
+
const newStep = internalStep + 1;
|
|
8585
|
+
setInternalStep(newStep);
|
|
8586
|
+
onStepChange?.(newStep);
|
|
8587
|
+
onNext?.();
|
|
8588
|
+
}
|
|
8589
|
+
};
|
|
8590
|
+
const handlePrev = () => {
|
|
8591
|
+
if (!isFirstStep) {
|
|
8592
|
+
// 이전 타겟 요소의 강조 스타일 제거
|
|
8593
|
+
if (targetElement) {
|
|
8594
|
+
targetElement.classList.remove('designbase-tutorial__target');
|
|
8595
|
+
targetElement.style.position = '';
|
|
8596
|
+
targetElement.style.zIndex = '';
|
|
8597
|
+
}
|
|
8598
|
+
const newStep = internalStep - 1;
|
|
8599
|
+
setInternalStep(newStep);
|
|
8600
|
+
onStepChange?.(newStep);
|
|
8601
|
+
onPrev?.();
|
|
8602
|
+
}
|
|
8603
|
+
};
|
|
8604
|
+
const handleEnd = () => {
|
|
8605
|
+
// 타겟 요소에서 강조 스타일 제거
|
|
8606
|
+
if (targetElement) {
|
|
8607
|
+
targetElement.classList.remove('designbase-tutorial__target');
|
|
8608
|
+
targetElement.style.position = '';
|
|
8609
|
+
targetElement.style.zIndex = '';
|
|
8610
|
+
}
|
|
8611
|
+
setTargetElement(null);
|
|
8612
|
+
onEnd?.();
|
|
8613
|
+
};
|
|
8614
|
+
const handleKeyDown = (e) => {
|
|
8615
|
+
if (e.key === 'Escape') {
|
|
8616
|
+
handleEnd();
|
|
8617
|
+
}
|
|
8618
|
+
else if (e.key === 'ArrowLeft' && !isFirstStep) {
|
|
8619
|
+
handlePrev();
|
|
8620
|
+
}
|
|
8621
|
+
else if (e.key === 'ArrowRight' && !isLastStep) {
|
|
8622
|
+
handleNext();
|
|
8623
|
+
}
|
|
8624
|
+
};
|
|
8625
|
+
if (!isActive || !currentStepData) {
|
|
8626
|
+
return null;
|
|
8627
|
+
}
|
|
8628
|
+
const classes = clsx('designbase-tutorial', className);
|
|
8629
|
+
return (jsxs("div", { className: classes, onKeyDown: handleKeyDown, tabIndex: -1, role: "dialog", "aria-modal": "true", "aria-labelledby": "tutorial-title", "aria-describedby": "tutorial-content", ...props, children: [jsx("div", { ref: overlayRef, className: "designbase-tutorial__overlay", onClick: handleEnd }), jsxs("div", { ref: popoverRef, className: clsx('designbase-tutorial__popover', `designbase-tutorial__popover--${popoverPlacement}`), style: {
|
|
8630
|
+
top: popoverPosition.top,
|
|
8631
|
+
left: popoverPosition.left
|
|
8632
|
+
}, children: [jsx("button", { type: "button", className: "designbase-tutorial__close-button", onClick: handleEnd, "aria-label": "\uD29C\uD1A0\uB9AC\uC5BC \uC885\uB8CC", children: jsx(CloseIconComponent, { size: 16 }) }), jsxs("div", { className: "designbase-tutorial__content", children: [(currentStepData.title || currentStepData.content || currentStepData.children) && (jsxs("div", { className: "designbase-tutorial__text", children: [currentStepData.title && (jsx("h3", { id: "tutorial-title", className: "designbase-tutorial__title", children: currentStepData.title })), currentStepData.content && (jsx("p", { id: "tutorial-content", className: "designbase-tutorial__description", children: currentStepData.content })), currentStepData.children && (jsx("div", { className: "designbase-tutorial__children", children: currentStepData.children }))] })), jsxs("div", { className: "designbase-tutorial__navigation", children: [!isFirstStep && (jsx(Button, { variant: "secondary", size: "s", startIcon: jsx(PrevIconComponent, { size: 16 }), onPress: handlePrev, "aria-label": "\uC774\uC804 \uB2E8\uACC4", children: "\uC774\uC804" })), jsx(Indicator, { current: internalStep, total: steps.length, type: indicatorType, size: indicatorSize }), jsx(Button, { variant: "primary", size: "s", endIcon: !isLastStep ? jsx(NextIconComponent, { size: 16 }) : undefined, onPress: handleNext, "aria-label": isLastStep ? '완료' : '다음 단계', children: isLastStep ? '완료' : '다음' })] })] })] })] }));
|
|
8633
|
+
};
|
|
8634
|
+
Tutorial.displayName = 'Tutorial';
|
|
8635
|
+
|
|
8360
8636
|
const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size = 'm', variant = 'default', disabled = false, readOnly = false, fullWidth = false, searchIcon: SearchIconComponent = SearchIcon, clearIcon: ClearIconComponent = CloseIcon, enableRecentSearches = false, recentSearchesKey = 'searchbar-recent-searches', suggestedSearches = [], suggestionRollingInterval = 5000, onChange, onSearch, onFocus, onBlur, onKeyDown, className, ...props }) => {
|
|
8361
8637
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
8362
8638
|
const [recentSearches, setRecentSearches] = useState([]);
|
|
@@ -8380,9 +8656,9 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
|
|
|
8380
8656
|
}
|
|
8381
8657
|
}
|
|
8382
8658
|
}, [enableRecentSearches, recentSearchesKey]);
|
|
8383
|
-
// 추천 검색어 롤링 (
|
|
8659
|
+
// 추천 검색어 롤링 (value가 없을 때만)
|
|
8384
8660
|
useEffect(() => {
|
|
8385
|
-
if (suggestedSearches.length > 0 && !currentValue) {
|
|
8661
|
+
if (suggestedSearches.length > 0 && !currentValue && currentValue === '') {
|
|
8386
8662
|
suggestionIntervalRef.current = setInterval(() => {
|
|
8387
8663
|
setCurrentSuggestion(prev => (prev + 1) % suggestedSearches.length);
|
|
8388
8664
|
}, suggestionRollingInterval);
|
|
@@ -8481,6 +8757,11 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
|
|
|
8481
8757
|
const handleSuggestionClick = (suggestion) => {
|
|
8482
8758
|
setInternalValue(suggestion);
|
|
8483
8759
|
onChange?.(suggestion);
|
|
8760
|
+
// 롤링 중단
|
|
8761
|
+
if (suggestionIntervalRef.current) {
|
|
8762
|
+
clearInterval(suggestionIntervalRef.current);
|
|
8763
|
+
suggestionIntervalRef.current = null;
|
|
8764
|
+
}
|
|
8484
8765
|
handleSearch(suggestion);
|
|
8485
8766
|
};
|
|
8486
8767
|
const classes = clsx('designbase-search-bar', `designbase-search-bar--${size}`, `designbase-search-bar--${variant}`, {
|
|
@@ -8493,8 +8774,8 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
|
|
|
8493
8774
|
'designbase-search-bar__input--disabled': disabled,
|
|
8494
8775
|
'designbase-search-bar__input--readonly': readOnly,
|
|
8495
8776
|
});
|
|
8496
|
-
// 현재 플레이스홀더 (추천
|
|
8497
|
-
const currentPlaceholder = suggestedSearches.length > 0 && !currentValue
|
|
8777
|
+
// 현재 플레이스홀더 (value가 없을 때만 추천 검색어 롤링)
|
|
8778
|
+
const currentPlaceholder = suggestedSearches.length > 0 && !currentValue && currentValue === ''
|
|
8498
8779
|
? suggestedSearches[currentSuggestion]
|
|
8499
8780
|
: placeholder;
|
|
8500
8781
|
return (jsxs("div", { className: classes, role: "search", children: [jsxs("div", { className: "designbase-search-bar__container", children: [jsx("div", { className: "designbase-search-bar__search-icon", children: jsx(SearchIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }), jsx("input", { ref: inputRef, type: "text", className: inputClasses, value: currentValue, placeholder: currentPlaceholder, disabled: disabled, readOnly: readOnly, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, "aria-label": "\uAC80\uC0C9\uC5B4 \uC785\uB825", ...props }), currentValue && currentValue.length > 0 && !disabled && !readOnly && (jsx("button", { type: "button", className: "designbase-search-bar__clear-button", onClick: handleClear, "aria-label": "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30", children: jsx(ClearIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }))] }), showRecentSearches && recentSearches.length > 0 && (jsxs("div", { className: "designbase-search-bar__recent-searches", children: [jsxs("div", { className: "designbase-search-bar__recent-header", children: [jsx("span", { className: "designbase-search-bar__recent-title", children: "\uCD5C\uADFC \uAC80\uC0C9\uC5B4" }), jsx("button", { type: "button", className: "designbase-search-bar__clear-all-button", onClick: handleClearAllRecentSearches, "aria-label": "\uBAA8\uB4E0 \uCD5C\uADFC \uAC80\uC0C9\uC5B4 \uC0AD\uC81C", children: "\uC804\uCCB4 \uC0AD\uC81C" })] }), jsx("div", { className: "designbase-search-bar__recent-list", children: recentSearches.map((searchTerm, index) => (jsxs("div", { className: "designbase-search-bar__recent-item", children: [jsx("button", { type: "button", className: "designbase-search-bar__recent-search-button", onClick: () => handleRecentSearchClick(searchTerm), children: searchTerm }), jsx("button", { type: "button", className: "designbase-search-bar__recent-remove-button", onClick: () => handleRemoveRecentSearch(searchTerm), "aria-label": `${searchTerm} 삭제`, children: jsx(CloseIcon, { size: 16 }) })] }, index))) })] })), suggestedSearches.length > 0 && isFocused && !currentValue && (jsxs("div", { className: "designbase-search-bar__suggestions", children: [jsx("div", { className: "designbase-search-bar__suggestions-header", children: jsx("span", { className: "designbase-search-bar__suggestions-title", children: "\uCD94\uCC9C \uAC80\uC0C9\uC5B4" }) }), jsx("div", { className: "designbase-search-bar__suggestions-list", children: suggestedSearches.map((suggestion, index) => (jsx("button", { type: "button", className: clsx('designbase-search-bar__suggestion-item', {
|
|
@@ -11370,5 +11651,5 @@ const toggleTheme = () => {
|
|
|
11370
11651
|
console.log('toggleTheme called');
|
|
11371
11652
|
};
|
|
11372
11653
|
|
|
11373
|
-
export { Accordion, AdBanner, Alert, AnimationBackground, AnimationText, AudioPlayer, Avatar, Backdrop, Badge, Banner, BottomSheet, Breadcrumbs, Button, Calendar, Card, Carousel, Checkbox, Chip, ColorPicker, Confirm, Container, ContextMenu, Countdown, DatePicker, Divider, Drawer, Dropdown, Dropzone, EmptyState, FileUploader, FloatingActionButton, Form, Gradient, Grid, HeroFeature, Image$1 as Image, ImageList, Input, Label, Lightbox, List, Logo, MarkdownEditor, Masonry, MenuItem, Modal, ModalBody, ModalFooter, ModalHeader, Navbar, Pagination, Popover, Progress, ProgressStep, Progressbar, Radio, RandomGradient, RangeSlider, Rating, Reorder, ResizablePanels, ScrollArea, SearchBar, Section, SegmentControl, Select, Share, Sidebar, Skeleton, Spinner, SplitView, Stack, Stat, Stepper, Table, Tabs, Textarea, TimePicker, Timeline, Toast, Toggle, Toolbar, Tooltip, VideoPlayer, YouTubePlayer, getTheme, setTheme, toggleTheme };
|
|
11654
|
+
export { Accordion, AdBanner, Alert, AnimationBackground, AnimationText, AudioPlayer, Avatar, Backdrop, Badge, Banner, BottomSheet, Breadcrumbs, Button, Calendar, Card, Carousel, Checkbox, Chip, ColorPicker, Confirm, Container, ContextMenu, Countdown, DatePicker, Divider, Drawer, Dropdown, Dropzone, EmptyState, FileUploader, FloatingActionButton, Form, Gradient, Grid, HeroFeature, Image$1 as Image, ImageList, Indicator, Input, Label, Lightbox, List, Logo, MarkdownEditor, Masonry, MenuItem, Modal, ModalBody, ModalFooter, ModalHeader, Navbar, OnboardingModal, Pagination, Popover, Progress, ProgressStep, Progressbar, Radio, RandomGradient, RangeSlider, Rating, Reorder, ResizablePanels, ScrollArea, SearchBar, Section, SegmentControl, Select, Share, Sidebar, Skeleton, Spinner, SplitView, Stack, Stat, Stepper, Table, Tabs, Textarea, TimePicker, Timeline, Toast, Toggle, Toolbar, Tooltip, Tutorial, VideoPlayer, YouTubePlayer, getTheme, setTheme, toggleTheme };
|
|
11374
11655
|
//# sourceMappingURL=index.esm.js.map
|