@designbasekorea/ui 0.1.21 → 0.1.22

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.esm.js CHANGED
@@ -1,5 +1,5 @@
1
- import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, forwardRef, useId } from 'react';
2
- import { ChevronDownIcon, CloseIcon, InfoFilledIcon, ErrorFilledIcon, WarningFilledIcon, CircleCheckFilledIcon, RefreshIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, RepeatIcon, MuteFilledIcon, VolumeUpIcon, SettingsIcon, UserIcon, ChevronUpIcon, GalleryIcon, HeartIcon, BookmarkIcon, ShareAltIcon, DownloadIcon, ArrowLeftIcon, ArrowRightIcon, ShrinkIcon, ExpandIcon, BulbIcon, CloudCloseIcon, BellActiveIcon, AwardIcon, PlusIcon, SearchIcon, MinusIcon as MinusIcon$1, VideoIcon, CodeIcon, WriteIcon, ShowIcon, HideIcon, UploadIcon, ArrowBarLeftIcon, ArrowBarRightIcon, StarFilledIcon, StarHalfIcon, StarIcon, MoveIcon, MoreVerticalIcon, ArrowDownIcon, ArrowUpLeftIcon, ArrowUpRightIcon, ArrowDownLeftIcon, ArrowDownRightIcon, MailIcon, LinkIcon, CopyIcon, ChevronsUpIcon } from '@designbasekorea/icons';
1
+ import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, forwardRef, useId, useLayoutEffect } from 'react';
2
+ import { ChevronDownIcon, CloseIcon, InfoFilledIcon, ErrorFilledIcon, WarningFilledIcon, CircleCheckFilledIcon, RefreshIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, RepeatIcon, MuteFilledIcon, VolumeUpIcon, SettingsIcon, UserIcon, HideIcon, ShowIcon, SearchIcon, ChevronUpIcon, GalleryIcon, HeartIcon, BookmarkIcon, ShareAltIcon, DownloadIcon, ArrowLeftIcon, ArrowRightIcon, 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, StarFilledIcon, StarHalfIcon, StarIcon, MoveIcon, MoreVerticalIcon, ArrowDownIcon, ArrowUpLeftIcon, ArrowUpRightIcon, ArrowDownLeftIcon, ArrowDownRightIcon, FacebookIcon, XIcon, InstagramIcon, LinkedinIcon, PinterestIcon, WhatsappIcon, TelegramIcon, MailIcon, LinkIcon, ScanQrcodeIcon, VerticalArrowsIcon, CaretUpdownFilledIcon } from '@designbasekorea/icons';
3
3
  import { flushSync, createPortal } from 'react-dom';
4
4
 
5
5
  function getDefaultExportFromCjs (x) {
@@ -1453,7 +1453,7 @@ const Accordion = ({ items, style = 'default', size = 'm', allowMultiple = false
1453
1453
  const itemType = item.itemType || defaultItemType;
1454
1454
  switch (itemType) {
1455
1455
  case 'icon':
1456
- return item.icon ? (jsxRuntimeExports.jsx("div", { className: "designbase-accordion__icon", children: jsxRuntimeExports.jsx(item.icon, { size: iconSize, color: "var(--db-icon-primary)" }) })) : null;
1456
+ return item.icon ? (jsxRuntimeExports.jsx("div", { className: "designbase-accordion__icon", children: jsxRuntimeExports.jsx(item.icon, { size: iconSize }) })) : null;
1457
1457
  case 'number':
1458
1458
  return (jsxRuntimeExports.jsxs("div", { className: "designbase-accordion__number", children: [index + 1, "."] }));
1459
1459
  case 'question':
@@ -1469,7 +1469,7 @@ const Accordion = ({ items, style = 'default', size = 'm', allowMultiple = false
1469
1469
  'designbase-accordion__item--expanded': expanded,
1470
1470
  'designbase-accordion__item--disabled': item.disabled,
1471
1471
  });
1472
- return (jsxRuntimeExports.jsxs("div", { className: itemClasses, children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "designbase-accordion__trigger", onClick: () => !item.disabled && handleItemToggle(item.id), disabled: item.disabled, "aria-expanded": expanded, "aria-controls": `accordion-content-${item.id}`, children: [renderItemPrefix(item, index), jsxRuntimeExports.jsx("span", { className: "designbase-accordion__title", children: item.title }), jsxRuntimeExports.jsx("div", { className: "designbase-accordion__chevron", children: jsxRuntimeExports.jsx(ChevronDownIcon, { size: iconSize, color: "var(--db-icon-primary)" }) })] }), jsxRuntimeExports.jsx("div", { id: `accordion-content-${item.id}`, className: "designbase-accordion__content", "aria-hidden": !expanded, children: jsxRuntimeExports.jsx("div", { className: "designbase-accordion__content-inner", children: item.content }) })] }, item.id));
1472
+ return (jsxRuntimeExports.jsxs("div", { className: itemClasses, children: [jsxRuntimeExports.jsxs("button", { type: "button", className: "designbase-accordion__trigger", onClick: () => !item.disabled && handleItemToggle(item.id), disabled: item.disabled, "aria-expanded": expanded, "aria-controls": `accordion-content-${item.id}`, children: [renderItemPrefix(item, index), jsxRuntimeExports.jsx("span", { className: "designbase-accordion__title", children: item.title }), jsxRuntimeExports.jsx("div", { className: "designbase-accordion__chevron", children: jsxRuntimeExports.jsx(ChevronDownIcon, { size: iconSize }) })] }), jsxRuntimeExports.jsx("div", { id: `accordion-content-${item.id}`, className: "designbase-accordion__content", "aria-hidden": !expanded, children: jsxRuntimeExports.jsx("div", { className: "designbase-accordion__content-inner", children: item.content }) })] }, item.id));
1473
1473
  }) }));
1474
1474
  };
1475
1475
  Accordion.displayName = 'Accordion';
@@ -3636,7 +3636,7 @@ const Button = forwardRef(({ variant = 'primary', size = 'm', radius, fullWidth
3636
3636
  });
3637
3637
  Button.displayName = 'Button';
3638
3638
 
3639
- const Alert = ({ title, children, variant = 'info', size = 'm', showIcon = true, closable = false, actions, actionButtons, onClose, className, ...props }) => {
3639
+ const Alert = ({ title, children, variant = 'info', size = 'm', showIcon = true, closable = true, actions, actionButtons, onClose, className, ...props }) => {
3640
3640
  const [isVisible, setIsVisible] = useState(true);
3641
3641
  const handleClose = () => {
3642
3642
  setIsVisible(false);
@@ -3671,7 +3671,7 @@ const Alert = ({ title, children, variant = 'info', size = 'm', showIcon = true,
3671
3671
  'designbase-alert--with-icon': showIcon,
3672
3672
  'designbase-alert--closable': closable,
3673
3673
  }, className);
3674
- return (jsxRuntimeExports.jsxs("div", { className: classes, role: "alert", "aria-live": "polite", ...props, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-alert__content", children: [showIcon && (jsxRuntimeExports.jsx("div", { className: "designbase-alert__icon", children: getIcon() })), jsxRuntimeExports.jsxs("div", { className: "designbase-alert__body", children: [title && (jsxRuntimeExports.jsx("div", { className: "designbase-alert__title", children: title })), jsxRuntimeExports.jsx("div", { className: "designbase-alert__message", children: children })] }), closable && (jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-alert__close", onClick: handleClose, "aria-label": "\uC54C\uB9BC \uB2EB\uAE30", children: jsxRuntimeExports.jsx(CloseIcon, { size: iconSize }) }))] }), (actions || actionButtons) && (jsxRuntimeExports.jsx("div", { className: "designbase-alert__actions", children: actionButtons ? (jsxRuntimeExports.jsx("div", { className: "designbase-alert__action-buttons", children: actionButtons.map((action, index) => (jsxRuntimeExports.jsx(Button, { size: action.size || size, variant: action.variant || 'secondary', onClick: action.onClick, disabled: action.disabled, children: action.label }, index))) })) : (actions) }))] }));
3674
+ return (jsxRuntimeExports.jsxs("div", { className: classes, role: "alert", "aria-live": "polite", ...props, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-alert__content", children: [showIcon && (jsxRuntimeExports.jsx("div", { className: "designbase-alert__icon", children: getIcon() })), jsxRuntimeExports.jsxs("div", { className: "designbase-alert__body", children: [title && (jsxRuntimeExports.jsx("div", { className: "designbase-alert__title", children: title })), jsxRuntimeExports.jsx("div", { className: "designbase-alert__message", children: children })] }), closable && (jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-alert__close", onClick: handleClose, "aria-label": "\uC54C\uB9BC \uB2EB\uAE30", children: jsxRuntimeExports.jsx(CloseIcon, { size: iconSize }) }))] }), (actions || actionButtons) && (jsxRuntimeExports.jsx("div", { className: "designbase-alert__actions", children: actionButtons ? (jsxRuntimeExports.jsx("div", { className: "designbase-alert__action-buttons", children: actionButtons.map((action, index) => (jsxRuntimeExports.jsx(Button, { size: action.size || size, variant: action.variant || 'tertiary', onClick: action.onClick, disabled: action.disabled, children: action.label }, index))) })) : (actions) }))] }));
3675
3675
  };
3676
3676
  Alert.displayName = 'Alert';
3677
3677
 
@@ -4490,64 +4490,32 @@ const Backdrop = forwardRef(({ open = false, onClick, disableBackdropClick = fal
4490
4490
  });
4491
4491
  Backdrop.displayName = 'Backdrop';
4492
4492
 
4493
- const Banner = ({ title, description, icon, image, imageAlt, actions = [], dismissible = false, autoDismiss, size = 'm', variant = 'default', style = 'solid', position = 'top', alignment = 'left', animated = false, fullWidth = false, shadow = false, rounded = true, backgroundImage, overlayColor, overlayOpacity = 0.1, onDismiss, className, }) => {
4494
- const [isVisible, setIsVisible] = React.useState(true);
4493
+ const Banner = ({ title, description, icon, image, imageAlt, actions = [], size = 'm', variant = 'primary', backgroundImage, className, }) => {
4495
4494
  // 아이콘 크기 계산 (m이 기본값)
4496
4495
  const iconSize = size === 's' ? 16 : size === 'l' ? 24 : 20;
4497
- // 자동 닫기
4498
- React.useEffect(() => {
4499
- if (autoDismiss && autoDismiss > 0) {
4500
- const timer = setTimeout(() => {
4501
- handleDismiss();
4502
- }, autoDismiss);
4503
- return () => clearTimeout(timer);
4504
- }
4505
- }, [autoDismiss]);
4506
- const handleDismiss = () => {
4507
- setIsVisible(false);
4508
- onDismiss?.();
4509
- };
4510
4496
  const renderActions = () => {
4511
4497
  if (actions.length === 0)
4512
4498
  return null;
4513
4499
  return (jsxRuntimeExports.jsx("div", { className: "designbase-banner__actions", children: actions.map((action, index) => (jsxRuntimeExports.jsx(Button, { href: action.href, variant: action.variant || 'primary', size: action.size || 'sm', onClick: action.onClick, target: action.external ? '_blank' : undefined, rel: action.external ? 'noopener noreferrer' : undefined, icon: action.icon, children: action.text }, index))) }));
4514
4500
  };
4515
4501
  const renderBackground = () => {
4516
- if (!backgroundImage)
4502
+ if (variant !== 'image' || !backgroundImage)
4517
4503
  return null;
4518
- return (jsxRuntimeExports.jsx("div", { className: "designbase-banner__background", style: {
4519
- backgroundImage: `url(${backgroundImage})`,
4520
- backgroundSize: 'cover',
4521
- backgroundPosition: 'center',
4522
- backgroundRepeat: 'no-repeat'
4523
- } }));
4524
- };
4525
- const renderOverlay = () => {
4526
- if (!overlayColor && !backgroundImage)
4527
- return null;
4528
- return (jsxRuntimeExports.jsx("div", { className: "designbase-banner__overlay", style: {
4529
- backgroundColor: overlayColor,
4530
- opacity: overlayOpacity
4531
- } }));
4532
- };
4533
- const classes = clsx('designbase-banner', `designbase-banner--size-${size}`, `designbase-banner--variant-${variant}`, `designbase-banner--style-${style}`, `designbase-banner--position-${position}`, `designbase-banner--alignment-${alignment}`, {
4534
- 'designbase-banner--animated': animated,
4535
- 'designbase-banner--full-width': fullWidth,
4536
- 'designbase-banner--shadow': shadow,
4537
- 'designbase-banner--rounded': rounded,
4538
- 'designbase-banner--dismissible': dismissible,
4539
- 'designbase-banner--visible': isVisible,
4540
- 'designbase-banner--hidden': !isVisible,
4541
- }, className);
4542
- if (!isVisible)
4543
- return null;
4544
- return (jsxRuntimeExports.jsxs("div", { className: classes, children: [renderBackground(), renderOverlay(), jsxRuntimeExports.jsxs("div", { className: "designbase-banner__container", children: [icon && (jsxRuntimeExports.jsx("div", { className: "designbase-banner__icon", children: React.isValidElement(icon) ? (React.cloneElement(icon, {
4504
+ return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { className: "designbase-banner__background", style: {
4505
+ backgroundImage: `url(${backgroundImage})`,
4506
+ backgroundSize: 'cover',
4507
+ backgroundPosition: 'center',
4508
+ backgroundRepeat: 'no-repeat'
4509
+ } }), jsxRuntimeExports.jsx("div", { className: "designbase-banner__overlay" })] }));
4510
+ };
4511
+ const classes = clsx('designbase-banner', `designbase-banner--${size}`, `designbase-banner--${variant}`, className);
4512
+ return (jsxRuntimeExports.jsxs("div", { className: classes, children: [renderBackground(), jsxRuntimeExports.jsxs("div", { className: "designbase-banner__container", children: [icon && (jsxRuntimeExports.jsx("div", { className: "designbase-banner__icon", children: React.isValidElement(icon) ? (React.cloneElement(icon, {
4545
4513
  size: iconSize,
4546
4514
  color: 'currentColor'
4547
- })) : icon })), image && (jsxRuntimeExports.jsx("div", { className: "designbase-banner__image", children: jsxRuntimeExports.jsx("img", { src: image, alt: imageAlt || title || 'Banner image' }) })), jsxRuntimeExports.jsxs("div", { className: "designbase-banner__content", children: [title && (jsxRuntimeExports.jsx("h3", { className: "designbase-banner__title", children: title })), description && (jsxRuntimeExports.jsx("p", { className: "designbase-banner__description", children: description }))] }), renderActions(), dismissible && (jsxRuntimeExports.jsx("button", { className: "designbase-banner__dismiss", onClick: handleDismiss, "aria-label": "\uB2EB\uAE30", children: jsxRuntimeExports.jsx(CloseIcon, { size: iconSize }) }))] })] }));
4515
+ })) : icon })), image && (jsxRuntimeExports.jsx("div", { className: "designbase-banner__image", children: jsxRuntimeExports.jsx("img", { src: image, alt: imageAlt || title || 'Banner image' }) })), jsxRuntimeExports.jsxs("div", { className: "designbase-banner__content", children: [title && (jsxRuntimeExports.jsx("h3", { className: "designbase-banner__title", children: title })), description && (jsxRuntimeExports.jsx("p", { className: "designbase-banner__description", children: description }))] }), renderActions()] })] }));
4548
4516
  };
4549
4517
 
4550
- const BottomSheet = forwardRef(({ open = false, onClose, title, subtitle, size = 'm', variant = 'default', disableBackdropClick = false, disableEscapeKeyDown = false, disableDrag = false, maxHeight = 80, animation = 'slide', animationDuration = 300, zIndex = 1000, backdropBlur = true, backdropOpacity = 0.5, stickyHeader = false, stickyFooter = false, className, style, header, footer, children, ...props }, forwardedRef) => {
4518
+ const BottomSheet = forwardRef(({ open = false, onClose, title, subtitle, size = 'm', variant = 'default', disableBackdropClick = false, disableEscapeKeyDown = false, disableDrag = false, maxHeight = 80, animation = 'slide', animationDuration = 300, zIndex = 1000, showBackdrop = true, backdropBlur = false, backdropOpacity = 0.5, stickyHeader = false, stickyFooter = false, className, style, header, footer, children, ...props }, forwardedRef) => {
4551
4519
  const [sheetHeight, setSheetHeight] = useState(maxHeight);
4552
4520
  const [isDragging, setIsDragging] = useState(false);
4553
4521
  const [dragStartY, setDragStartY] = useState(0);
@@ -4671,7 +4639,7 @@ const BottomSheet = forwardRef(({ open = false, onClose, title, subtitle, size =
4671
4639
  'designbase-backdrop--blur': backdropBlur,
4672
4640
  });
4673
4641
  // Portal을 사용하여 body에 렌더링
4674
- return createPortal(jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { ref: backdropRef, className: backdropClasses, style: backdropStyle, onClick: handleBackdropClick, role: "presentation", "aria-hidden": !open }), jsxRuntimeExports.jsxs("div", { ref: forwardedRef, className: classes, style: sheetStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? 'bottom-sheet-title' : undefined, "aria-describedby": subtitle ? 'bottom-sheet-subtitle' : undefined, ...props, children: [!disableDrag && (jsxRuntimeExports.jsx("div", { className: "designbase-bottom-sheet__handle", onMouseDown: handleDragStart, onTouchStart: handleDragStart })), (title || header) && (jsxRuntimeExports.jsx("div", { ref: headerRef, className: "designbase-bottom-sheet__header", children: header || (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [title && (jsxRuntimeExports.jsx("h2", { id: "bottom-sheet-title", className: "designbase-bottom-sheet__title", children: title })), subtitle && (jsxRuntimeExports.jsx("p", { id: "bottom-sheet-subtitle", className: "designbase-bottom-sheet__subtitle", children: subtitle }))] })) })), jsxRuntimeExports.jsx("div", { className: "designbase-bottom-sheet__content", children: children }), footer && (jsxRuntimeExports.jsx("div", { className: "designbase-bottom-sheet__footer", children: footer }))] })] }), document.body);
4642
+ return createPortal(jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [showBackdrop && (jsxRuntimeExports.jsx("div", { ref: backdropRef, className: backdropClasses, style: backdropStyle, onClick: handleBackdropClick, role: "presentation", "aria-hidden": !open })), jsxRuntimeExports.jsxs("div", { ref: forwardedRef, className: classes, style: sheetStyle, role: "dialog", "aria-modal": "true", "aria-labelledby": title ? 'bottom-sheet-title' : undefined, "aria-describedby": subtitle ? 'bottom-sheet-subtitle' : undefined, ...props, children: [!disableDrag && (jsxRuntimeExports.jsx("div", { className: "designbase-bottom-sheet__handle", onMouseDown: handleDragStart, onTouchStart: handleDragStart })), (title || header) && (jsxRuntimeExports.jsx("div", { ref: headerRef, className: "designbase-bottom-sheet__header", children: header || (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [title && (jsxRuntimeExports.jsx("h2", { id: "bottom-sheet-title", className: "designbase-bottom-sheet__title", children: title })), subtitle && (jsxRuntimeExports.jsx("p", { id: "bottom-sheet-subtitle", className: "designbase-bottom-sheet__subtitle", children: subtitle }))] })) })), jsxRuntimeExports.jsx("div", { className: "designbase-bottom-sheet__content", children: children }), footer && (jsxRuntimeExports.jsx("div", { className: "designbase-bottom-sheet__footer", children: footer }))] })] }), document.body);
4675
4643
  });
4676
4644
  BottomSheet.displayName = 'BottomSheet';
4677
4645
 
@@ -4892,16 +4860,27 @@ const Input = forwardRef(({ type = 'text', label, placeholder, defaultValue, val
4892
4860
  const inputId = useId();
4893
4861
  const helperTextId = useId();
4894
4862
  const errorMessageId = useId();
4863
+ const [showPassword, setShowPassword] = useState(false);
4895
4864
  const handleChange = (e) => {
4896
4865
  onChange?.(e.target.value);
4897
4866
  };
4867
+ const handlePasswordToggle = () => {
4868
+ setShowPassword(!showPassword);
4869
+ };
4870
+ // 비밀번호 타입인 경우 토글 아이콘 사용
4871
+ const isPassword = type === 'password';
4872
+ const actualType = isPassword ? (showPassword ? 'text' : 'password') : type;
4873
+ const PasswordIcon = showPassword ? HideIcon : ShowIcon;
4874
+ // 검색 타입인 경우 자동으로 검색 아이콘 사용
4875
+ const isSearch = type === 'search';
4876
+ const actualStartIcon = isSearch ? SearchIcon : StartIcon;
4898
4877
  const classes = clsx('designbase-input', `designbase-input--${size}`, {
4899
4878
  'designbase-input--error': error,
4900
4879
  'designbase-input--disabled': disabled,
4901
4880
  'designbase-input--readonly': readOnly,
4902
4881
  'designbase-input--full-width': fullWidth,
4903
- 'designbase-input--with-start-icon': StartIcon,
4904
- 'designbase-input--with-end-icon': EndIcon,
4882
+ 'designbase-input--with-start-icon': actualStartIcon,
4883
+ 'designbase-input--with-end-icon': EndIcon || isPassword,
4905
4884
  }, className);
4906
4885
  const inputClasses = clsx('designbase-input__field', inputClassName);
4907
4886
  const iconSizeMap = {
@@ -4910,7 +4889,7 @@ const Input = forwardRef(({ type = 'text', label, placeholder, defaultValue, val
4910
4889
  lg: 18,
4911
4890
  };
4912
4891
  const iconSize = iconSizeMap[size];
4913
- return (jsxRuntimeExports.jsxs("div", { className: classes, children: [label && (jsxRuntimeExports.jsx(Label, { htmlFor: inputId, required: required, error: error, disabled: disabled, size: size === 's' ? 'xs' : size === 'm' ? 's' : 'm', children: label })), jsxRuntimeExports.jsxs("div", { className: "designbase-input__wrapper", children: [StartIcon && (jsxRuntimeExports.jsx("div", { className: "designbase-input__start-icon", children: jsxRuntimeExports.jsx(StartIcon, { size: iconSize }) })), jsxRuntimeExports.jsx("input", { ...props, ref: forwardedRef, id: inputId, type: type, value: value, defaultValue: defaultValue, placeholder: placeholder, disabled: disabled, readOnly: readOnly, required: required, className: inputClasses, onChange: handleChange, onFocus: onFocus, onBlur: onBlur, "aria-describedby": clsx(helperText && helperTextId, error && errorMessage && errorMessageId), "aria-invalid": error }), EndIcon && (jsxRuntimeExports.jsx("div", { className: "designbase-input__end-icon", children: jsxRuntimeExports.jsx(EndIcon, { size: iconSize }) }))] }), helperText && !error && (jsxRuntimeExports.jsx("p", { id: helperTextId, className: "designbase-input__helper-text", children: helperText })), error && errorMessage && (jsxRuntimeExports.jsx("p", { id: errorMessageId, className: "designbase-input__error-message", children: errorMessage }))] }));
4892
+ return (jsxRuntimeExports.jsxs("div", { className: classes, children: [label && (jsxRuntimeExports.jsx(Label, { htmlFor: inputId, required: required, error: error, disabled: disabled, size: size === 's' ? 'xs' : size === 'm' ? 's' : 'm', children: label })), jsxRuntimeExports.jsxs("div", { className: "designbase-input__wrapper", children: [actualStartIcon && (jsxRuntimeExports.jsx("div", { className: "designbase-input__start-icon", children: React.createElement(actualStartIcon, { size: iconSize }) })), jsxRuntimeExports.jsx("input", { ...props, ref: forwardedRef, id: inputId, type: actualType, value: value, defaultValue: defaultValue, placeholder: placeholder, disabled: disabled, readOnly: readOnly, required: required, className: inputClasses, onChange: handleChange, onFocus: onFocus, onBlur: onBlur, "aria-describedby": clsx(helperText && helperTextId, error && errorMessage && errorMessageId), "aria-invalid": error }), isPassword && (jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-input__password-toggle", onClick: handlePasswordToggle, disabled: disabled, "aria-label": showPassword ? '비밀번호 숨기기' : '비밀번호 보기', children: React.createElement(PasswordIcon, { size: iconSize }) })), EndIcon && !isPassword && (jsxRuntimeExports.jsx("div", { className: "designbase-input__end-icon", children: React.createElement(EndIcon, { size: iconSize }) }))] }), helperText && !error && (jsxRuntimeExports.jsx("p", { id: helperTextId, className: "designbase-input__helper-text", children: helperText })), error && errorMessage && (jsxRuntimeExports.jsx("p", { id: errorMessageId, className: "designbase-input__error-message", children: errorMessage }))] }));
4914
4893
  });
4915
4894
  Input.displayName = 'Input';
4916
4895
 
@@ -5561,7 +5540,7 @@ const Calendar = ({ currentDate = new Date(), view = 'month', events = [], onEve
5561
5540
  }, title: event.title, children: event.title }, event.id));
5562
5541
  }) }, index));
5563
5542
  }) }, i));
5564
- }) })] })] }))] }), jsxRuntimeExports.jsxs(Modal, { isOpen: showEventModal, onClose: handleCloseModal, title: isEditing ? '일정 편집' : '새 일정 추가', size: "l", children: [jsxRuntimeExports.jsx(ModalBody, { children: jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__event-form", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uC81C\uBAA9 *" }), jsxRuntimeExports.jsx(Input, { value: eventForm.title, onChange: (value) => setEventForm(prev => ({ ...prev, title: value })), placeholder: "\uC77C\uC815 \uC81C\uBAA9\uC744 \uC785\uB825\uD558\uC138\uC694" })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uC124\uBA85" }), jsxRuntimeExports.jsx(Input, { value: eventForm.description, onChange: (value) => setEventForm(prev => ({ ...prev, description: value })), placeholder: "\uC77C\uC815 \uC124\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694" })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uB0A0\uC9DC" }), jsxRuntimeExports.jsx(Input, { type: "date", value: selectedDate.toISOString().split('T')[0], onChange: (value) => {
5543
+ }) })] })] }))] }), jsxRuntimeExports.jsxs(Modal, { isOpen: showEventModal, onClose: handleCloseModal, title: isEditing ? '일정 편집' : '새 일정 추가', size: "m", children: [jsxRuntimeExports.jsx(ModalBody, { children: jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__event-form", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uC81C\uBAA9 *" }), jsxRuntimeExports.jsx(Input, { value: eventForm.title, onChange: (value) => setEventForm(prev => ({ ...prev, title: value })), placeholder: "\uC77C\uC815 \uC81C\uBAA9\uC744 \uC785\uB825\uD558\uC138\uC694" })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uC124\uBA85" }), jsxRuntimeExports.jsx(Input, { value: eventForm.description, onChange: (value) => setEventForm(prev => ({ ...prev, description: value })), placeholder: "\uC77C\uC815 \uC124\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694" })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uB0A0\uC9DC" }), jsxRuntimeExports.jsx(Input, { type: "date", value: selectedDate.toISOString().split('T')[0], onChange: (value) => {
5565
5544
  const newDate = new Date(value);
5566
5545
  setSelectedDate(newDate);
5567
5546
  } })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-calendar__form-row", children: [jsxRuntimeExports.jsx("label", { children: "\uC77C\uC815 \uD0C0\uC785" }), jsxRuntimeExports.jsx(Select, { value: eventForm.type, onChange: (value) => {
@@ -5680,23 +5659,20 @@ const Image$1 = ({ src, alt = '', ratio = 'auto', fit = 'cover', loading = 'lazy
5680
5659
  return (jsxRuntimeExports.jsxs("div", { className: classes, style: containerStyle, onClick: onClick, role: onClick ? 'button' : undefined, tabIndex: onClick ? 0 : undefined, children: [showPlaceholder && (jsxRuntimeExports.jsxs("div", { className: placeholderClasses, children: [placeholder === 'skeleton' && (jsxRuntimeExports.jsx("div", { className: "designbase-image__skeleton" })), placeholder === 'blur' && (jsxRuntimeExports.jsx("div", { className: "designbase-image__blur" }))] })), imageState === 'error' && (jsxRuntimeExports.jsxs("div", { className: "designbase-image__error", children: [jsxRuntimeExports.jsx(GalleryIcon, { size: errorIconSize, color: "currentColor" }), jsxRuntimeExports.jsx("span", { className: "designbase-image__error-text", children: alt || '이미지를 불러올 수 없습니다' })] })), jsxRuntimeExports.jsx("img", { ref: imageRef, src: currentSrc, alt: alt, loading: loading, className: imageClasses, onLoad: handleLoad, onError: handleError, draggable: false })] }));
5681
5660
  };
5682
5661
 
5683
- const Card = forwardRef(({ title, subtitle, description, children, image, icon: Icon, actions = [], tags = [], meta, variant = 'default', size = 'm', layout = 'vertical', imagePosition = 'top', fullWidth = false, clickable = false, hoverable = false, selectable = false, selected = false, disabled = false, loading = false, className, style, onClick, onSelect, onAction, }, ref) => {
5684
- // 아이콘 크기 계산 (m이 기본값) - 카드에 맞게 조정
5685
- const iconSize = size === 's' ? 16 : size === 'l' ? 20 : size === 'xl' ? 24 : 18;
5662
+ const Card = forwardRef(function Card({ title, subtitle, description, children, image, icon: Icon, actions = [], tags = [], meta, variant = 'default', size = 'm', layout = 'vertical', imagePosition = 'top', fullWidth = false, clickable = false, hoverable = false, selectable = false, selected = false, disabled = false, loading = false, className, style, onClick, onSelect, onAction, }, ref) {
5663
+ // 아이콘 크기(과도하지 않게 정규화)
5664
+ const iconSize = size === 's' ? 16 : size === 'l' ? 22 : size === 'xl' ? 24 : 18;
5686
5665
  const handleClick = () => {
5687
- if (!disabled && !loading && onClick) {
5666
+ if (!disabled && !loading && onClick)
5688
5667
  onClick();
5689
- }
5690
5668
  };
5691
5669
  const handleSelect = () => {
5692
- if (!disabled && !loading && selectable && onSelect) {
5670
+ if (!disabled && !loading && selectable && onSelect)
5693
5671
  onSelect(!selected);
5694
- }
5695
5672
  };
5696
5673
  const handleActionClick = (action, index) => {
5697
- if (!disabled && !loading && action.onClick) {
5674
+ if (!disabled && !loading && action.onClick)
5698
5675
  action.onClick();
5699
- }
5700
5676
  onAction?.(action, index);
5701
5677
  };
5702
5678
  const classes = clsx('designbase-card', `designbase-card--${variant}`, `designbase-card--${size}`, `designbase-card--${layout}`, {
@@ -5707,51 +5683,63 @@ const Card = forwardRef(({ title, subtitle, description, children, image, icon:
5707
5683
  'designbase-card--selected': selected,
5708
5684
  'designbase-card--disabled': disabled,
5709
5685
  'designbase-card--loading': loading,
5710
- 'designbase-card--with-image': image,
5711
- [`designbase-card--image-${imagePosition}`]: image,
5686
+ 'designbase-card--with-image': !!image,
5687
+ [`designbase-card--image-${imagePosition}`]: !!image,
5712
5688
  }, className);
5713
- // 이미지 렌더링
5714
5689
  const renderImage = () => {
5715
5690
  if (!image)
5716
5691
  return null;
5717
5692
  const imageClasses = clsx('designbase-card__image', `designbase-card__image--${imagePosition}`);
5718
5693
  return (jsxRuntimeExports.jsx("div", { className: imageClasses, children: jsxRuntimeExports.jsx(Image$1, { src: image.src, alt: image.alt || title || 'Card image', ratio: image.ratio, fit: image.fit, loading: image.loading, placeholder: image.placeholder, onClick: image.onClick, className: "designbase-card__image-element" }) }));
5719
5694
  };
5720
- // 아이콘 렌더링
5721
5695
  const renderIcon = () => {
5722
5696
  if (!Icon)
5723
5697
  return null;
5724
- return (jsxRuntimeExports.jsx("div", { className: "designbase-card__icon", children: jsxRuntimeExports.jsx(Icon, { size: iconSize, color: "currentColor", className: "designbase-card__icon-element" }) }));
5698
+ return (jsxRuntimeExports.jsx("div", { className: "designbase-card__icon", "aria-hidden": "true", children: jsxRuntimeExports.jsx(Icon, { size: iconSize, color: "currentColor", className: "designbase-card__icon-element" }) }));
5725
5699
  };
5726
- // 태그 렌더링
5727
5700
  const renderTags = () => {
5728
- if (tags.length === 0)
5701
+ if (!tags?.length)
5729
5702
  return null;
5730
- return (jsxRuntimeExports.jsx("div", { className: "designbase-card__tags", children: tags.map((tag, index) => (jsxRuntimeExports.jsx("span", { className: "designbase-card__tag", children: tag }, index))) }));
5703
+ return (jsxRuntimeExports.jsx("div", { className: "designbase-card__tags", children: tags.map((tag, i) => (jsxRuntimeExports.jsx("span", { className: "designbase-card__tag", children: tag }, i))) }));
5731
5704
  };
5732
- // 메타 정보 렌더링
5733
5705
  const renderMeta = () => {
5734
5706
  if (!meta)
5735
5707
  return null;
5736
- return (jsxRuntimeExports.jsxs("div", { className: "designbase-card__meta", children: [meta.author && (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-label", children: "\uC791\uC131\uC790:" }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: meta.author })] })), meta.date && (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-label", children: "\uB0A0\uC9DC:" }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: meta.date })] })), meta.views !== undefined && (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-label", children: "\uC870\uD68C\uC218:" }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: meta.views })] })), meta.likes !== undefined && (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-label", children: "\uC88B\uC544\uC694:" }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: meta.likes })] })), meta.comments !== undefined && (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-label", children: "\uB313\uAE00:" }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: meta.comments })] })), meta.custom && Object.entries(meta.custom).map(([key, value]) => (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-label", children: [key, ":"] }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: value })] }, key)))] }));
5708
+ const items = [];
5709
+ if (meta.author)
5710
+ items.push(['작성자', meta.author]);
5711
+ if (meta.date)
5712
+ items.push(['날짜', meta.date]);
5713
+ if (typeof meta.views === 'number')
5714
+ items.push(['조회수', meta.views]);
5715
+ if (typeof meta.likes === 'number')
5716
+ items.push(['좋아요', meta.likes]);
5717
+ if (typeof meta.comments === 'number')
5718
+ items.push(['댓글', meta.comments]);
5719
+ if (meta.custom) {
5720
+ Object.entries(meta.custom).forEach(([key, value]) => {
5721
+ items.push([key, value]);
5722
+ });
5723
+ }
5724
+ if (!items.length)
5725
+ return null;
5726
+ return (jsxRuntimeExports.jsx("div", { className: "designbase-card__meta", children: items.map(([label, value], idx) => (jsxRuntimeExports.jsxs("span", { className: "designbase-card__meta-item", children: [jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-label", children: label }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-sep", children: "\u00B7" }), jsxRuntimeExports.jsx("span", { className: "designbase-card__meta-value", children: value })] }, `${label}-${idx}`))) }));
5737
5727
  };
5738
- // 액션 버튼들 렌더링
5739
5728
  const renderActions = () => {
5740
- if (actions.length === 0)
5729
+ if (!actions?.length)
5741
5730
  return null;
5742
5731
  return (jsxRuntimeExports.jsx("div", { className: "designbase-card__actions", children: actions.map((action, index) => {
5743
5732
  const ActionIcon = action.icon;
5744
- return (jsxRuntimeExports.jsx(Button, { variant: action.variant || 'primary', size: action.size || 's', onClick: () => handleActionClick(action, index), disabled: action.disabled || disabled, loading: action.loading, icon: ActionIcon, children: action.label }, index));
5733
+ return (jsxRuntimeExports.jsx(Button, { variant: action.variant || 'primary', size: action.size || 's', onClick: () => handleActionClick(action, index), disabled: action.disabled || disabled, loading: !!action.loading, icon: ActionIcon, children: action.label }, index));
5745
5734
  }) }));
5746
5735
  };
5747
- // 카드 내용 렌더링
5748
- const renderContent = () => (jsxRuntimeExports.jsxs("div", { className: "designbase-card__content", children: [renderIcon(), (title || subtitle) && (jsxRuntimeExports.jsxs("div", { className: "designbase-card__header", children: [title && jsxRuntimeExports.jsx("h3", { className: "designbase-card__title", children: title }), subtitle && jsxRuntimeExports.jsx("h4", { className: "designbase-card__subtitle", children: subtitle })] })), description && (jsxRuntimeExports.jsx("p", { className: "designbase-card__description", children: description })), children && (jsxRuntimeExports.jsx("div", { className: "designbase-card__body", children: children })), renderTags(), renderMeta(), renderActions()] }));
5736
+ const renderContent = () => (jsxRuntimeExports.jsxs("div", { className: "designbase-card__content", children: [renderIcon(), (title || subtitle) && (jsxRuntimeExports.jsxs("div", { className: "designbase-card__header", children: [title && jsxRuntimeExports.jsx("h3", { className: "designbase-card__title", children: title }), subtitle && jsxRuntimeExports.jsx("p", { className: "designbase-card__subtitle", children: subtitle })] })), description && jsxRuntimeExports.jsx("p", { className: "designbase-card__description", children: description }), children && jsxRuntimeExports.jsx("div", { className: "designbase-card__body", children: children }), renderTags(), renderMeta(), renderActions()] }));
5749
5737
  return (jsxRuntimeExports.jsxs("div", { ref: ref, className: classes, style: style, onClick: clickable ? handleClick : undefined, onKeyDown: (e) => {
5750
5738
  if (clickable && (e.key === 'Enter' || e.key === ' ')) {
5751
5739
  e.preventDefault();
5752
5740
  handleClick();
5753
5741
  }
5754
- }, tabIndex: clickable ? 0 : undefined, role: clickable ? 'button' : undefined, "aria-label": clickable ? title : undefined, children: [selectable && (jsxRuntimeExports.jsx("div", { className: "designbase-card__select-overlay", onClick: handleSelect, children: jsxRuntimeExports.jsx("input", { type: "checkbox", checked: selected, onChange: handleSelect, className: "designbase-card__select-input" }) })), loading && (jsxRuntimeExports.jsx("div", { className: "designbase-card__loading-overlay", children: jsxRuntimeExports.jsx("div", { className: "designbase-card__loading-spinner" }) })), imagePosition === 'background' ? (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [renderImage(), jsxRuntimeExports.jsx("div", { className: "designbase-card__overlay", children: renderContent() })] })) : (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [imagePosition === 'top' && renderImage(), imagePosition === 'left' && renderImage(), renderContent(), imagePosition === 'bottom' && renderImage(), imagePosition === 'right' && renderImage()] }))] }));
5742
+ }, tabIndex: clickable ? 0 : undefined, role: clickable ? 'button' : undefined, "aria-label": clickable ? title : undefined, "aria-disabled": disabled || loading ? true : undefined, children: [selectable && (jsxRuntimeExports.jsx("div", { className: "designbase-card__select-overlay", onClick: handleSelect, children: jsxRuntimeExports.jsx("input", { type: "checkbox", checked: selected, onChange: handleSelect, className: "designbase-card__select-input", "aria-label": "\uCE74\uB4DC \uC120\uD0DD" }) })), loading && (jsxRuntimeExports.jsx("div", { className: "designbase-card__loading-overlay", "aria-live": "polite", "aria-busy": "true", children: jsxRuntimeExports.jsx("div", { className: "designbase-card__loading-spinner" }) })), imagePosition === 'background' ? (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [renderImage(), jsxRuntimeExports.jsx("div", { className: "designbase-card__overlay", children: renderContent() })] })) : (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [(imagePosition === 'top' || imagePosition === 'left') && renderImage(), renderContent(), (imagePosition === 'bottom' || imagePosition === 'right') && renderImage()] }))] }));
5755
5743
  });
5756
5744
  Card.displayName = 'Card';
5757
5745
 
@@ -5913,28 +5901,26 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
5913
5901
  const handleTouchMove = useCallback((event) => {
5914
5902
  if (!isDragging || !enableTouch || disabled || readonly || !touchStartRef.current)
5915
5903
  return;
5916
- event.preventDefault();
5917
5904
  const clientX = 'touches' in event ? event.touches[0].clientX : event.clientX;
5918
5905
  const clientY = 'touches' in event ? event.touches[0].clientY : event.clientY;
5919
5906
  const deltaX = clientX - touchStartRef.current.x;
5920
5907
  const deltaY = clientY - touchStartRef.current.y;
5921
5908
  // 수평 스와이프만 처리 (수직 스크롤 방지)
5922
5909
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
5910
+ event.preventDefault();
5923
5911
  setDragOffset(deltaX);
5924
5912
  }
5925
5913
  }, [isDragging, enableTouch, disabled, readonly]);
5926
5914
  // 터치/드래그 종료
5927
5915
  const handleTouchEnd = useCallback(() => {
5928
- if (!isDragging || !enableTouch || disabled || readonly || !touchStartRef.current)
5916
+ if (!enableTouch || disabled || readonly || !touchStartRef.current)
5929
5917
  return;
5930
5918
  const endTime = Date.now();
5931
5919
  const duration = endTime - touchStartRef.current.time;
5932
- const velocity = Math.abs(dragOffset) / duration;
5933
- setIsDragging(false);
5934
- touchStartRef.current = null;
5935
- // 속도나 거리에 따라 슬라이드 변경
5920
+ const velocity = Math.abs(dragOffset) / Math.max(duration, 1);
5936
5921
  const threshold = swipeSensitivity;
5937
5922
  const velocityThreshold = 0.5;
5923
+ // 속도나 거리에 따라 슬라이드 변경
5938
5924
  if (Math.abs(dragOffset) > threshold || velocity > velocityThreshold) {
5939
5925
  if (dragOffset > 0) {
5940
5926
  goToPrevious();
@@ -5943,8 +5929,11 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
5943
5929
  goToNext();
5944
5930
  }
5945
5931
  }
5932
+ // 상태 초기화
5933
+ setIsDragging(false);
5946
5934
  setDragOffset(0);
5947
- }, [isDragging, enableTouch, disabled, readonly, dragOffset, swipeSensitivity, goToPrevious, goToNext]);
5935
+ touchStartRef.current = null;
5936
+ }, [enableTouch, disabled, readonly, dragOffset, swipeSensitivity, goToPrevious, goToNext]);
5948
5937
  // 마우스 휠 핸들러
5949
5938
  const handleWheel = useCallback((event) => {
5950
5939
  if (!enableWheel || disabled || readonly)
@@ -6013,11 +6002,13 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
6013
6002
  const currentItem = items[currentIndex];
6014
6003
  // 슬라이드 스타일 계산
6015
6004
  const getSlideStyle = () => {
6016
- const translateX = -(currentIndex * (100 / itemsPerView)) + (dragOffset / (containerRef.current?.offsetWidth || 1)) * (100 / itemsPerView);
6005
+ const containerWidth = containerRef.current?.offsetWidth || 1;
6006
+ const translateX = -(currentIndex * (100 / itemsPerView)) + (dragOffset / containerWidth) * (100 / itemsPerView);
6017
6007
  return {
6018
6008
  transform: `translateX(${translateX}%)`,
6019
6009
  gap: `${gap}px`,
6020
- transition: isDragging ? 'none' : `transform ${transitionDuration}ms ease`,
6010
+ transition: isDragging ? 'none' : `transform ${transitionDuration}ms cubic-bezier(0.4, 0.0, 0.2, 1)`,
6011
+ willChange: isDragging ? 'transform' : 'auto',
6021
6012
  };
6022
6013
  };
6023
6014
  // 인디케이터 활성 상태 확인
@@ -6038,7 +6029,7 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
6038
6029
  'designbase-carousel--dragging': isDragging,
6039
6030
  'designbase-carousel--fullscreen': isFullscreen,
6040
6031
  'designbase-carousel--transitioning': isTransitioning,
6041
- }, className), children: [jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "designbase-carousel__container", onWheel: handleWheel, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStartEvent, onTouchMove: handleTouchMoveEvent, onTouchEnd: handleTouchEndEvent, children: [jsxRuntimeExports.jsx("div", { ref: trackRef, className: "designbase-carousel__track", style: getSlideStyle(), children: items.map((item, index) => (jsxRuntimeExports.jsxs("div", { className: clsx('designbase-carousel__slide', {
6032
+ }, className), children: [jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "designbase-carousel__container", onWheel: handleWheel, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStartEvent, onTouchMove: handleTouchMoveEvent, onTouchEnd: handleTouchEndEvent, onTouchCancel: handleTouchEndEvent, children: [jsxRuntimeExports.jsx("div", { ref: trackRef, className: "designbase-carousel__track", style: getSlideStyle(), children: items.map((item, index) => (jsxRuntimeExports.jsxs("div", { className: clsx('designbase-carousel__slide', {
6042
6033
  'designbase-carousel__slide--active': index === currentIndex,
6043
6034
  }), style: {
6044
6035
  width: `${100 / itemsPerView}%`,
@@ -6113,7 +6104,278 @@ const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', delet
6113
6104
  };
6114
6105
  Chip.displayName = 'Chip';
6115
6106
 
6116
- const Confirm = ({ open, title, children, confirmText = '확인', cancelText = '취소', confirmVariant = 'primary', variant = 'info', size = 'm', showIcon = true, confirmDisabled = false, cancelDisabled = false, onConfirm, onCancel, onClose, closeOnEscape = true, closeOnOverlayClick = true, className, ...props }) => {
6107
+ const ColorPicker$1 = ({ size = 'm', type = 'dropdown', value, defaultValue = '#006FFF', showInput = true, showAlpha = false, showFormatSelector = true, showCopyButton = true, disabled = false, readonly = false, onChange, className, }) => {
6108
+ const [selectedColor, setSelectedColor] = useState(value || defaultValue);
6109
+ const [isOpen, setIsOpen] = useState(false);
6110
+ const [hue, setHue] = useState(211);
6111
+ const [saturation, setSaturation] = useState(100);
6112
+ const [lightness, setLightness] = useState(50);
6113
+ const [alpha, setAlpha] = useState(100);
6114
+ const [colorFormat, setColorFormat] = useState('hex');
6115
+ const [isCopied, setIsCopied] = useState(false);
6116
+ const [inputValue, setInputValue] = useState(defaultValue);
6117
+ const pickerRef = useRef(null);
6118
+ // value prop이 변경되면 업데이트
6119
+ useEffect(() => {
6120
+ if (value) {
6121
+ setSelectedColor(value);
6122
+ updateHSLFromHex(value);
6123
+ }
6124
+ }, [value]);
6125
+ // HEX를 HSL로 변환
6126
+ const updateHSLFromHex = (hex) => {
6127
+ const rgb = hexToRgb(hex);
6128
+ if (rgb) {
6129
+ const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
6130
+ setHue(hsl.h);
6131
+ setSaturation(hsl.s);
6132
+ setLightness(hsl.l);
6133
+ }
6134
+ };
6135
+ // HEX를 RGB로 변환
6136
+ const hexToRgb = (hex) => {
6137
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
6138
+ return result ? {
6139
+ r: parseInt(result[1], 16),
6140
+ g: parseInt(result[2], 16),
6141
+ b: parseInt(result[3], 16)
6142
+ } : null;
6143
+ };
6144
+ // RGB를 HSL로 변환
6145
+ const rgbToHsl = (r, g, b) => {
6146
+ r /= 255;
6147
+ g /= 255;
6148
+ b /= 255;
6149
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
6150
+ let h = 0, s = 0, l = (max + min) / 2;
6151
+ if (max !== min) {
6152
+ const d = max - min;
6153
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
6154
+ switch (max) {
6155
+ case r:
6156
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
6157
+ break;
6158
+ case g:
6159
+ h = ((b - r) / d + 2) / 6;
6160
+ break;
6161
+ case b:
6162
+ h = ((r - g) / d + 4) / 6;
6163
+ break;
6164
+ }
6165
+ }
6166
+ return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
6167
+ };
6168
+ // HSL을 RGB로 변환
6169
+ const hslToRgb = (h, s, l) => {
6170
+ l /= 100;
6171
+ const a = s * Math.min(l, 1 - l) / 100;
6172
+ const f = (n) => {
6173
+ const k = (n + h / 30) % 12;
6174
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
6175
+ return Math.round(255 * color);
6176
+ };
6177
+ return { r: f(0), g: f(8), b: f(4) };
6178
+ };
6179
+ // HSL을 HEX로 변환
6180
+ const hslToHex = (h, s, l) => {
6181
+ const rgb = hslToRgb(h, s, l);
6182
+ const toHex = (n) => n.toString(16).padStart(2, '0');
6183
+ return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`.toUpperCase();
6184
+ };
6185
+ // 현재 포맷에 따른 색상 문자열 반환
6186
+ const getFormattedColor = () => {
6187
+ const rgb = hslToRgb(hue, saturation, lightness);
6188
+ const alphaValue = alpha / 100;
6189
+ switch (colorFormat) {
6190
+ case 'hex':
6191
+ return hslToHex(hue, saturation, lightness);
6192
+ case 'rgb':
6193
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
6194
+ case 'rgba':
6195
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alphaValue.toFixed(2)})`;
6196
+ case 'hsl':
6197
+ return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
6198
+ case 'hsla':
6199
+ return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alphaValue.toFixed(2)})`;
6200
+ default:
6201
+ return hslToHex(hue, saturation, lightness);
6202
+ }
6203
+ };
6204
+ // 색상 변경 핸들러
6205
+ const handleColorChange = (newColor) => {
6206
+ setSelectedColor(newColor);
6207
+ onChange?.(newColor);
6208
+ };
6209
+ // Hue 슬라이더 변경
6210
+ const handleHueChange = (e) => {
6211
+ const newHue = parseInt(e.target.value);
6212
+ setHue(newHue);
6213
+ const newColor = hslToHex(newHue, saturation, lightness);
6214
+ handleColorChange(newColor);
6215
+ };
6216
+ // Saturation 슬라이더 변경
6217
+ const handleSaturationChange = (e) => {
6218
+ const newSaturation = parseInt(e.target.value);
6219
+ setSaturation(newSaturation);
6220
+ const newColor = hslToHex(hue, newSaturation, lightness);
6221
+ handleColorChange(newColor);
6222
+ };
6223
+ // Lightness 슬라이더 변경
6224
+ const handleLightnessChange = (e) => {
6225
+ const newLightness = parseInt(e.target.value);
6226
+ setLightness(newLightness);
6227
+ const newColor = hslToHex(hue, saturation, newLightness);
6228
+ handleColorChange(newColor);
6229
+ };
6230
+ // Alpha 슬라이더 변경
6231
+ const handleAlphaChange = (e) => {
6232
+ const newAlpha = parseInt(e.target.value);
6233
+ setAlpha(newAlpha);
6234
+ const newColor = getFormattedColorWithAlpha(newAlpha);
6235
+ handleColorChange(newColor);
6236
+ };
6237
+ // Alpha 값을 포함한 색상 문자열 반환
6238
+ const getFormattedColorWithAlpha = (alphaVal) => {
6239
+ const rgb = hslToRgb(hue, saturation, lightness);
6240
+ const alphaValue = alphaVal / 100;
6241
+ if (colorFormat === 'rgba') {
6242
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alphaValue.toFixed(2)})`;
6243
+ }
6244
+ else if (colorFormat === 'hsla') {
6245
+ return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alphaValue.toFixed(2)})`;
6246
+ }
6247
+ return hslToHex(hue, saturation, lightness);
6248
+ };
6249
+ // 입력 필드 변경
6250
+ const handleInputChange = (e) => {
6251
+ const newValue = e.target.value.trim();
6252
+ setInputValue(newValue);
6253
+ };
6254
+ // 입력 필드에서 포커스 아웃 시 검증 및 적용
6255
+ const handleInputBlur = () => {
6256
+ let isValid = false;
6257
+ // HEX 검증
6258
+ if (/^#[0-9A-F]{6}$/i.test(inputValue)) {
6259
+ handleColorChange(inputValue.toUpperCase());
6260
+ updateHSLFromHex(inputValue);
6261
+ isValid = true;
6262
+ }
6263
+ // RGB 검증
6264
+ else if (/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i.test(inputValue)) {
6265
+ const match = inputValue.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
6266
+ if (match) {
6267
+ const r = parseInt(match[1]);
6268
+ const g = parseInt(match[2]);
6269
+ const b = parseInt(match[3]);
6270
+ if (r <= 255 && g <= 255 && b <= 255) {
6271
+ const hsl = rgbToHsl(r, g, b);
6272
+ setHue(hsl.h);
6273
+ setSaturation(hsl.s);
6274
+ setLightness(hsl.l);
6275
+ const hex = hslToHex(hsl.h, hsl.s, hsl.l);
6276
+ handleColorChange(hex);
6277
+ isValid = true;
6278
+ }
6279
+ }
6280
+ }
6281
+ // RGBA 검증
6282
+ else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)$/i.test(inputValue)) {
6283
+ const match = inputValue.match(/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)$/i);
6284
+ if (match) {
6285
+ const r = parseInt(match[1]);
6286
+ const g = parseInt(match[2]);
6287
+ const b = parseInt(match[3]);
6288
+ const a = parseFloat(match[4]);
6289
+ if (r <= 255 && g <= 255 && b <= 255 && a >= 0 && a <= 1) {
6290
+ const hsl = rgbToHsl(r, g, b);
6291
+ setHue(hsl.h);
6292
+ setSaturation(hsl.s);
6293
+ setLightness(hsl.l);
6294
+ setAlpha(Math.round(a * 100));
6295
+ const hex = hslToHex(hsl.h, hsl.s, hsl.l);
6296
+ handleColorChange(hex);
6297
+ isValid = true;
6298
+ }
6299
+ }
6300
+ }
6301
+ // 유효하지 않으면 이전 값으로 복원
6302
+ if (!isValid) {
6303
+ setInputValue(getFormattedColor());
6304
+ }
6305
+ };
6306
+ // 복사 기능
6307
+ const handleCopy = async () => {
6308
+ try {
6309
+ await navigator.clipboard.writeText(getFormattedColor());
6310
+ setIsCopied(true);
6311
+ setTimeout(() => setIsCopied(false), 2000);
6312
+ }
6313
+ catch (err) {
6314
+ console.error('Failed to copy:', err);
6315
+ }
6316
+ };
6317
+ // 포맷 변경 시 inputValue 업데이트
6318
+ useEffect(() => {
6319
+ setInputValue(getFormattedColor());
6320
+ }, [colorFormat, hue, saturation, lightness, alpha]);
6321
+ // 외부 클릭 감지
6322
+ useEffect(() => {
6323
+ const handleClickOutside = (event) => {
6324
+ if (pickerRef.current && !pickerRef.current.contains(event.target)) {
6325
+ setIsOpen(false);
6326
+ }
6327
+ };
6328
+ if (isOpen && type === 'dropdown') {
6329
+ document.addEventListener('mousedown', handleClickOutside);
6330
+ }
6331
+ return () => {
6332
+ document.removeEventListener('mousedown', handleClickOutside);
6333
+ };
6334
+ }, [isOpen, type]);
6335
+ const togglePicker = () => {
6336
+ if (disabled || readonly)
6337
+ return;
6338
+ setIsOpen(!isOpen);
6339
+ };
6340
+ const classes = clsx('designbase-color-picker', `designbase-color-picker--${size}`, {
6341
+ 'designbase-color-picker--disabled': disabled,
6342
+ 'designbase-color-picker--readonly': readonly,
6343
+ 'designbase-color-picker--open': isOpen,
6344
+ 'designbase-color-picker--no-input': !showInput,
6345
+ }, className);
6346
+ // 컬러 선택 UI
6347
+ const renderColorSelector = () => (jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__selector", children: [showFormatSelector && (jsxRuntimeExports.jsx("div", { className: "designbase-color-picker__format-selector", children: ['hex', 'rgb', 'rgba', 'hsl', 'hsla'].map((format) => (jsxRuntimeExports.jsx("button", { type: "button", className: clsx('designbase-color-picker__format-button', { 'designbase-color-picker__format-button--active': colorFormat === format }), onClick: () => setColorFormat(format), children: format.toUpperCase() }, format))) })), jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__sliders", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__slider-group", children: [jsxRuntimeExports.jsxs("label", { className: "designbase-color-picker__slider-label", children: ["Hue ", jsxRuntimeExports.jsxs("span", { className: "designbase-color-picker__slider-value", children: [hue, "\u00B0"] })] }), jsxRuntimeExports.jsx("input", { type: "range", min: "0", max: "360", value: hue, onChange: handleHueChange, className: "designbase-color-picker__slider designbase-color-picker__slider--hue" })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__slider-group", children: [jsxRuntimeExports.jsxs("label", { className: "designbase-color-picker__slider-label", children: ["Saturation ", jsxRuntimeExports.jsxs("span", { className: "designbase-color-picker__slider-value", children: [saturation, "%"] })] }), jsxRuntimeExports.jsx("input", { type: "range", min: "0", max: "100", value: saturation, onChange: handleSaturationChange, className: "designbase-color-picker__slider designbase-color-picker__slider--saturation", style: {
6348
+ background: `linear-gradient(to right,
6349
+ hsl(${hue}, 0%, ${lightness}%),
6350
+ hsl(${hue}, 100%, ${lightness}%))`
6351
+ } })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__slider-group", children: [jsxRuntimeExports.jsxs("label", { className: "designbase-color-picker__slider-label", children: ["Lightness ", jsxRuntimeExports.jsxs("span", { className: "designbase-color-picker__slider-value", children: [lightness, "%"] })] }), jsxRuntimeExports.jsx("input", { type: "range", min: "0", max: "100", value: lightness, onChange: handleLightnessChange, className: "designbase-color-picker__slider designbase-color-picker__slider--lightness", style: {
6352
+ background: `linear-gradient(to right,
6353
+ hsl(${hue}, ${saturation}%, 0%),
6354
+ hsl(${hue}, ${saturation}%, 50%),
6355
+ hsl(${hue}, ${saturation}%, 100%))`
6356
+ } })] }), showAlpha && (jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__slider-group", children: [jsxRuntimeExports.jsxs("label", { className: "designbase-color-picker__slider-label", children: ["Alpha ", jsxRuntimeExports.jsxs("span", { className: "designbase-color-picker__slider-value", children: [alpha, "%"] })] }), jsxRuntimeExports.jsx("input", { type: "range", min: "0", max: "100", value: alpha, onChange: handleAlphaChange, className: "designbase-color-picker__slider designbase-color-picker__slider--alpha", style: {
6357
+ background: `linear-gradient(to right,
6358
+ transparent,
6359
+ hsl(${hue}, ${saturation}%, ${lightness}%))`
6360
+ } })] }))] }), jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__preview", children: [jsxRuntimeExports.jsx("div", { className: "designbase-color-picker__preview-box", style: {
6361
+ backgroundColor: showAlpha
6362
+ ? `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha / 100})`
6363
+ : `hsl(${hue}, ${saturation}%, ${lightness}%)`
6364
+ } }), jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__preview-info", children: [jsxRuntimeExports.jsx("div", { className: "designbase-color-picker__preview-value", children: getFormattedColor() }), showCopyButton && (jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-color-picker__copy-button", onClick: handleCopy, "aria-label": "Copy color value", children: isCopied ? jsxRuntimeExports.jsx(DoneIcon$1, { size: 14 }) : jsxRuntimeExports.jsx(CopyIcon, { size: 14 }) }))] })] })] }));
6365
+ return (jsxRuntimeExports.jsxs("div", { ref: pickerRef, className: classes, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-color-picker__trigger", children: [jsxRuntimeExports.jsx("div", { className: "designbase-color-picker__color-display", onClick: togglePicker, children: jsxRuntimeExports.jsx("div", { className: "designbase-color-picker__color-box", style: {
6366
+ backgroundColor: showAlpha
6367
+ ? `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha / 100})`
6368
+ : `hsl(${hue}, ${saturation}%, ${lightness}%)`
6369
+ } }) }), showInput && (jsxRuntimeExports.jsx("input", { type: "text", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onClick: (e) => e.stopPropagation(), disabled: disabled, readOnly: readonly, className: "designbase-color-picker__input", placeholder: "#000000" })), showInput && showCopyButton && (jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-color-picker__copy-button-inline", onClick: (e) => {
6370
+ e.stopPropagation();
6371
+ handleCopy();
6372
+ }, disabled: disabled, "aria-label": "Copy color value", children: isCopied ? jsxRuntimeExports.jsx(DoneIcon$1, { size: 14 }) : jsxRuntimeExports.jsx(CopyIcon, { size: 14 }) })), jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-color-picker__toggle", onClick: togglePicker, disabled: disabled, "aria-label": "Toggle color picker", children: jsxRuntimeExports.jsx(ChevronDownIcon, { size: 16 }) })] }), type === 'dropdown' && isOpen && (jsxRuntimeExports.jsx("div", { className: "designbase-color-picker__dropdown", children: renderColorSelector() })), type === 'modal' && (jsxRuntimeExports.jsx(Modal, { isOpen: isOpen, onClose: () => setIsOpen(false), title: "\uC0C9\uC0C1 \uC120\uD0DD", size: "s", children: renderColorSelector() }))] }));
6373
+ };
6374
+ ColorPicker$1.displayName = 'ColorPicker';
6375
+
6376
+ ColorPicker;
6377
+
6378
+ const Confirm = ({ open, title, children, confirmText = '확인', cancelText = '취소', confirmVariant = 'primary', variant = 'info', size = 's', showIcon = true, confirmDisabled = false, cancelDisabled = false, onConfirm, onCancel, onClose, closeOnEscape = true, closeOnOverlayClick = true, className, ...props }) => {
6117
6379
  const modalRef = useRef(null);
6118
6380
  // ESC 키 처리
6119
6381
  useEffect(() => {
@@ -6283,7 +6545,7 @@ const Container = ({ size = 'l', maxWidth, variant = 'plain', padding = 'm', mar
6283
6545
  return (jsxRuntimeExports.jsx("div", { className: classes, style: style, children: children }));
6284
6546
  };
6285
6547
 
6286
- const MenuItem = ({ id, label, href, icon: Icon, active = false, disabled = false, badge, badgeColor = 'primary', variant = 'default', type = 'inline', size = 'm', style = 'dropdown', subItems, expanded = false, expandable = false, depth = 0, onClick, onChildClick, className, }) => {
6548
+ const MenuItem = ({ id, label, href, icon: Icon, active = false, disabled = false, badge, badgeColor = 'primary', variant = 'default', type = 'block', size = 'm', style = 'accordion', subItems, expanded = false, expandable = false, depth = 0, onClick, onChildClick, className, }) => {
6287
6549
  const [internalExpanded, setInternalExpanded] = useState(expanded);
6288
6550
  // expanded prop이 변경될 때 internalExpanded 업데이트
6289
6551
  useEffect(() => {
@@ -6338,7 +6600,7 @@ const MenuItem = ({ id, label, href, icon: Icon, active = false, disabled = fals
6338
6600
  return (jsxRuntimeExports.jsxs("div", { className: classes, children: [jsxRuntimeExports.jsxs("div", { className: contentClasses, onClick: handleClick, children: [Icon && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-menu-item__icon', `designbase-menu-item__icon--${variant}`, {
6339
6601
  'designbase-menu-item__icon--active': active,
6340
6602
  'designbase-menu-item__icon--disabled': disabled,
6341
- }), children: jsxRuntimeExports.jsx(Icon, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) })), jsxRuntimeExports.jsx("span", { className: "designbase-menu-item__label", children: label }), badge && (jsxRuntimeExports.jsx(Badge, { count: typeof badge === 'string' ? parseInt(badge) : badge, variant: badgeColor === 'neutral' ? 'secondary' : badgeColor, size: "s", style: "number" })), hasChildren && (jsxRuntimeExports.jsx("div", { className: "designbase-menu-item__expand-icon", children: isExpanded ? (jsxRuntimeExports.jsx(ChevronUpIcon, { size: 16 })) : (jsxRuntimeExports.jsx(ChevronDownIcon, { size: 16 })) }))] }), hasChildren && isExpanded && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-menu-item__children', `designbase-menu-item__children--${style}`), children: subItems.map((child) => (jsxRuntimeExports.jsx(MenuItem, { id: child.id, label: child.label, href: child.href, icon: child.icon, active: child.active, disabled: child.disabled, badge: child.badge, badgeColor: child.badgeColor, variant: child.variant, type: child.type, size: size, style: child.style || style, subItems: child.subItems, depth: depth + 1, onClick: () => handleChildClick(child), onChildClick: onChildClick }, child.id))) }))] }));
6603
+ }), children: jsxRuntimeExports.jsx(Icon, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) })), jsxRuntimeExports.jsx("span", { className: "designbase-menu-item__label", children: label }), badge && (jsxRuntimeExports.jsx(Badge, { count: typeof badge === 'string' ? parseInt(badge) : badge, variant: badgeColor === 'neutral' ? 'secondary' : badgeColor, size: "s", style: "number" })), hasChildren && (jsxRuntimeExports.jsx("div", { className: "designbase-menu-item__expand-icon", children: isExpanded ? (jsxRuntimeExports.jsx(ChevronUpIcon, { size: 16 })) : (jsxRuntimeExports.jsx(ChevronDownIcon, { size: 16 })) }))] }), hasChildren && isExpanded && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-menu-item__children', `designbase-menu-item__children--${style}`), children: subItems.map((child) => (jsxRuntimeExports.jsx(MenuItem, { id: child.id, label: child.label, href: child.href, icon: child.icon, active: child.active, disabled: child.disabled, badge: child.badge, badgeColor: child.badgeColor, variant: child.variant, type: child.type || (style === 'accordion' ? 'block' : 'inline'), size: size, style: child.style || style, subItems: child.subItems, depth: depth + 1, onClick: () => handleChildClick(child), onChildClick: onChildClick }, child.id))) }))] }));
6342
6604
  };
6343
6605
  MenuItem.displayName = 'MenuItem';
6344
6606
 
@@ -6474,50 +6736,72 @@ const ContextMenu = ({ items, open, x, y, onClose, className, }) => {
6474
6736
  };
6475
6737
  ContextMenu.displayName = 'ContextMenu';
6476
6738
 
6477
- const DatePicker = ({ mode = 'single', value, onChange, minDate, maxDate, events = [], showOutsideDays = true, startOfWeek = 'sunday', size = 'm', variant = 'default', highlightWeekends = true, highlightHolidays = false, today = new Date(), locale = 'ko-KR', format = 'yyyy-MM-dd', className, disabled = false, readonly = false,
6478
- // 드롭다운 관련 props
6479
- isOpen: controlledIsOpen, onOpenChange, trigger, placement = 'bottom',
6739
+ const DatePicker = ({ mode = 'single', type = 'dropdown', value, defaultValue, onChange, minDate, maxDate, events = [], showOutsideDays = true, startOfWeek = 'sunday', size = 'm', variant = 'default', highlightWeekends = true, highlightHolidays = false, today = new Date(), locale = 'ko-KR', format = 'yyyy-MM-dd', className, disabled = false, readonly = false,
6480
6740
  // 인라인 모드
6481
6741
  inline = false, }) => {
6482
6742
  // 아이콘 크기 계산 (m이 기본값)
6483
6743
  const iconSize = size === 's' ? 14 : size === 'l' ? 18 : 16;
6484
6744
  const [currentDate, setCurrentDate] = useState(new Date());
6485
6745
  const [hoveredDate, setHoveredDate] = useState(null);
6486
- const [internalIsOpen, setInternalIsOpen] = useState(false);
6746
+ const [isOpen, setIsOpen] = useState(false);
6487
6747
  const [rangeStart, setRangeStart] = useState(null);
6488
- const containerRef = useRef(null);
6489
- // 드롭다운 상태 관리
6490
- const isOpen = controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;
6491
- const setIsOpen = (open) => {
6492
- if (controlledIsOpen === undefined) {
6493
- setInternalIsOpen(open);
6748
+ const [inputValue, setInputValue] = useState('');
6749
+ const [selectedValue, setSelectedValue] = useState(value || defaultValue);
6750
+ const pickerRef = useRef(null);
6751
+ // 모달용 임시 상태 (적용 전까지는 변경되지 않음)
6752
+ const [tempValue, setTempValue] = useState(value || defaultValue);
6753
+ // value prop이 변경되면 업데이트
6754
+ useEffect(() => {
6755
+ if (value !== undefined) {
6756
+ setSelectedValue(value);
6757
+ // formatDateValue 함수가 정의되기 전이므로 직접 포맷팅
6758
+ if (value instanceof Date) {
6759
+ setInputValue(value.toLocaleDateString(locale));
6760
+ }
6761
+ else if (Array.isArray(value)) {
6762
+ setInputValue(value.map(date => date.toLocaleDateString(locale)).join(', '));
6763
+ }
6764
+ else if (value && 'start' in value && 'end' in value) {
6765
+ setInputValue(`${value.start.toLocaleDateString(locale)} ~ ${value.end.toLocaleDateString(locale)}`);
6766
+ }
6767
+ else {
6768
+ setInputValue('');
6769
+ }
6494
6770
  }
6495
- onOpenChange?.(open);
6496
- };
6497
- // 외부 클릭 감지
6771
+ }, [value, locale]);
6772
+ // 모달이 열릴 때 현재 값을 임시 상태로 복사
6498
6773
  useEffect(() => {
6499
- if (!isOpen || inline)
6774
+ if (isOpen && type === 'modal') {
6775
+ setTempValue(selectedValue);
6776
+ }
6777
+ }, [isOpen, type, selectedValue]);
6778
+ // 외부 클릭 감지 (드롭다운만)
6779
+ useEffect(() => {
6780
+ if (!isOpen || inline || type !== 'dropdown')
6500
6781
  return;
6501
6782
  const handleClickOutside = (event) => {
6502
- if (containerRef.current && !containerRef.current.contains(event.target)) {
6783
+ if (pickerRef.current && !pickerRef.current.contains(event.target)) {
6503
6784
  setIsOpen(false);
6504
6785
  }
6505
6786
  };
6506
6787
  document.addEventListener('mousedown', handleClickOutside);
6507
6788
  return () => document.removeEventListener('mousedown', handleClickOutside);
6508
- }, [isOpen, inline]);
6509
- // ESC 감지
6510
- useEffect(() => {
6511
- if (!isOpen || inline)
6512
- return;
6513
- const handleEscape = (event) => {
6514
- if (event.key === 'Escape') {
6515
- setIsOpen(false);
6516
- }
6517
- };
6518
- document.addEventListener('keydown', handleEscape);
6519
- return () => document.removeEventListener('keydown', handleEscape);
6520
- }, [isOpen, inline]);
6789
+ }, [isOpen, inline, type]);
6790
+ // 날짜 포맷팅 함수
6791
+ const formatDateValue = useCallback((dateValue) => {
6792
+ if (!dateValue)
6793
+ return '';
6794
+ if (dateValue instanceof Date) {
6795
+ return dateValue.toLocaleDateString(locale);
6796
+ }
6797
+ if (Array.isArray(dateValue)) {
6798
+ return dateValue.map(date => date.toLocaleDateString(locale)).join(', ');
6799
+ }
6800
+ if ('start' in dateValue && 'end' in dateValue) {
6801
+ return `${dateValue.start.toLocaleDateString(locale)} ~ ${dateValue.end.toLocaleDateString(locale)}`;
6802
+ }
6803
+ return '';
6804
+ }, [locale]);
6521
6805
  // 날짜 유효성 검사
6522
6806
  const isDateValid = useCallback((date) => {
6523
6807
  if (minDate && date < minDate)
@@ -6526,21 +6810,70 @@ inline = false, }) => {
6526
6810
  return false;
6527
6811
  return true;
6528
6812
  }, [minDate, maxDate]);
6813
+ // 토글 핸들러
6814
+ const togglePicker = () => {
6815
+ if (disabled || readonly)
6816
+ return;
6817
+ setIsOpen((o) => !o);
6818
+ };
6819
+ // 모달용 핸들러들
6820
+ const handleModalDateChange = (newValue) => {
6821
+ setTempValue(newValue);
6822
+ };
6823
+ const handleModalApply = () => {
6824
+ // 임시 값을 실제 값으로 적용
6825
+ setSelectedValue(tempValue);
6826
+ setInputValue(formatDateValue(tempValue));
6827
+ onChange?.(tempValue);
6828
+ // 모달 닫기
6829
+ setIsOpen(false);
6830
+ };
6831
+ const handleModalCancel = () => {
6832
+ // 임시 값을 원래 값으로 복원
6833
+ setTempValue(selectedValue);
6834
+ // 모달 닫기
6835
+ setIsOpen(false);
6836
+ };
6837
+ // 입력 필드 변경
6838
+ const handleInputChange = (e) => {
6839
+ setInputValue(e.target.value);
6840
+ };
6841
+ // 입력 필드 블러 시 검증
6842
+ const handleInputBlur = () => {
6843
+ // 간단한 날짜 파싱 (YYYY-MM-DD 형식)
6844
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
6845
+ if (dateRegex.test(inputValue)) {
6846
+ const parsedDate = new Date(inputValue);
6847
+ if (!isNaN(parsedDate.getTime()) && isDateValid(parsedDate)) {
6848
+ setSelectedValue(parsedDate);
6849
+ onChange?.(parsedDate);
6850
+ return;
6851
+ }
6852
+ }
6853
+ // 유효하지 않으면 이전 값으로 복원
6854
+ setInputValue(formatDateValue(selectedValue));
6855
+ };
6529
6856
  // 날짜 선택 처리
6530
6857
  const handleDateClick = useCallback((date) => {
6531
6858
  if (disabled || readonly || !isDateValid(date))
6532
6859
  return;
6860
+ const currentValue = type === 'modal' ? tempValue : selectedValue;
6861
+ const handleChange = type === 'modal' ? handleModalDateChange : (newValue) => {
6862
+ setSelectedValue(newValue);
6863
+ setInputValue(formatDateValue(newValue));
6864
+ onChange?.(newValue);
6865
+ };
6533
6866
  switch (mode) {
6534
6867
  case 'single':
6535
- onChange?.(date);
6536
- if (!inline)
6868
+ handleChange(date);
6869
+ if (!inline && type === 'dropdown')
6537
6870
  setIsOpen(false);
6538
6871
  break;
6539
6872
  case 'range':
6540
6873
  // 기존 범위가 있고, 새로운 날짜를 클릭한 경우
6541
- if (value && 'start' in value && !rangeStart) {
6874
+ if (currentValue && 'start' in currentValue && !rangeStart) {
6542
6875
  // 기존 범위를 해제하고 새로운 범위 시작
6543
- onChange?.(undefined);
6876
+ handleChange(undefined);
6544
6877
  setRangeStart(date);
6545
6878
  return;
6546
6879
  }
@@ -6554,29 +6887,29 @@ inline = false, }) => {
6554
6887
  const end = date;
6555
6888
  if (start > end) {
6556
6889
  // 시작일이 끝일보다 늦으면 순서 바꿈
6557
- onChange?.({ start: end, end: start });
6890
+ handleChange({ start: end, end: start });
6558
6891
  }
6559
6892
  else {
6560
- onChange?.({ start, end });
6893
+ handleChange({ start, end });
6561
6894
  }
6562
6895
  setRangeStart(null);
6563
- if (!inline)
6896
+ if (!inline && type === 'dropdown')
6564
6897
  setIsOpen(false);
6565
6898
  }
6566
6899
  break;
6567
6900
  case 'multiple':
6568
- const currentDates = Array.isArray(value) ? value : [];
6901
+ const currentDates = Array.isArray(currentValue) ? currentValue : [];
6569
6902
  const dateStr = date.toISOString().split('T')[0];
6570
6903
  const isSelected = currentDates.some(d => d.toISOString().split('T')[0] === dateStr);
6571
6904
  if (isSelected) {
6572
- onChange?.(currentDates.filter(d => d.toISOString().split('T')[0] !== dateStr));
6905
+ handleChange(currentDates.filter(d => d.toISOString().split('T')[0] !== dateStr));
6573
6906
  }
6574
6907
  else {
6575
- onChange?.([...currentDates, date]);
6908
+ handleChange([...currentDates, date]);
6576
6909
  }
6577
6910
  break;
6578
6911
  }
6579
- }, [mode, value, onChange, disabled, readonly, isDateValid, rangeStart, inline]);
6912
+ }, [mode, onChange, disabled, readonly, isDateValid, rangeStart, inline, type, tempValue, selectedValue, formatDateValue, handleModalDateChange]);
6580
6913
  // 날짜 호버 처리
6581
6914
  const handleDateHover = useCallback((date) => {
6582
6915
  if (mode === 'range') {
@@ -6594,6 +6927,7 @@ inline = false, }) => {
6594
6927
  const isInRange = useCallback((date) => {
6595
6928
  if (mode !== 'range')
6596
6929
  return false;
6930
+ const currentValue = type === 'modal' ? tempValue : selectedValue;
6597
6931
  // 현재 선택된 범위 확인
6598
6932
  let start = null;
6599
6933
  let end = null;
@@ -6602,10 +6936,10 @@ inline = false, }) => {
6602
6936
  start = rangeStart;
6603
6937
  end = hoveredDate;
6604
6938
  }
6605
- else if (value && 'start' in value) {
6939
+ else if (currentValue && 'start' in currentValue) {
6606
6940
  // 완성된 범위
6607
- start = value.start;
6608
- end = value.end;
6941
+ start = currentValue.start;
6942
+ end = currentValue.end;
6609
6943
  }
6610
6944
  if (!start)
6611
6945
  return false;
@@ -6613,28 +6947,29 @@ inline = false, }) => {
6613
6947
  const startStr = start.toISOString().split('T')[0];
6614
6948
  const endStr = end ? end.toISOString().split('T')[0] : startStr;
6615
6949
  return dateStr >= startStr && dateStr <= endStr;
6616
- }, [mode, value, rangeStart, hoveredDate]);
6950
+ }, [mode, rangeStart, hoveredDate, type, tempValue, selectedValue]);
6617
6951
  // 날짜가 선택되었는지 확인
6618
6952
  const isDateSelected = useCallback((date) => {
6619
6953
  const dateStr = date.toISOString().split('T')[0];
6954
+ const currentValue = type === 'modal' ? tempValue : selectedValue;
6620
6955
  switch (mode) {
6621
6956
  case 'single':
6622
- return value && value instanceof Date && value.toISOString().split('T')[0] === dateStr;
6957
+ return currentValue && currentValue instanceof Date && currentValue.toISOString().split('T')[0] === dateStr;
6623
6958
  case 'range':
6624
6959
  if (rangeStart && rangeStart.toISOString().split('T')[0] === dateStr) {
6625
6960
  return true;
6626
6961
  }
6627
- if (!value || !('start' in value))
6962
+ if (!currentValue || !('start' in currentValue))
6628
6963
  return false;
6629
- const range = value;
6964
+ const range = currentValue;
6630
6965
  return range.start.toISOString().split('T')[0] === dateStr ||
6631
6966
  range.end.toISOString().split('T')[0] === dateStr;
6632
6967
  case 'multiple':
6633
- return Array.isArray(value) && value.some(d => d.toISOString().split('T')[0] === dateStr);
6968
+ return Array.isArray(currentValue) && currentValue.some(d => d.toISOString().split('T')[0] === dateStr);
6634
6969
  default:
6635
6970
  return false;
6636
6971
  }
6637
- }, [mode, value, rangeStart]);
6972
+ }, [mode, rangeStart, type, tempValue, selectedValue]);
6638
6973
  // 월 이동
6639
6974
  const goToPreviousMonth = useCallback(() => {
6640
6975
  setCurrentDate(prev => {
@@ -6682,22 +7017,23 @@ inline = false, }) => {
6682
7017
  return weekdays;
6683
7018
  }, [startOfWeek]);
6684
7019
  // 날짜 셀 렌더링
6685
- const renderDateCell = useCallback((date) => {
7020
+ const renderDateCell = useCallback((date, isModal = false) => {
6686
7021
  const isCurrentMonth = date.getMonth() === currentDate.getMonth();
6687
7022
  const isToday = date.toDateString() === today.toDateString();
6688
7023
  const isSelected = isDateSelected(date);
6689
7024
  const isInRangeDate = isInRange(date);
6690
7025
  const isWeekend = date.getDay() === 0 || date.getDay() === 6;
6691
7026
  const dateEvents = getEventsForDate(date);
7027
+ const currentValue = isModal ? tempValue : selectedValue;
6692
7028
  const cellClass = clsx('designbase-date-picker__date', {
6693
7029
  'designbase-date-picker__date--outside': !isCurrentMonth && !showOutsideDays,
6694
7030
  'designbase-date-picker__date--today': isToday,
6695
7031
  'designbase-date-picker__date--selected': isSelected,
6696
7032
  'designbase-date-picker__date--in-range': isInRangeDate && !isSelected,
6697
7033
  'designbase-date-picker__date--range-start': mode === 'range' && ((rangeStart && rangeStart.toDateString() === date.toDateString()) ||
6698
- (value && 'start' in value && value.start.toDateString() === date.toDateString())),
6699
- 'designbase-date-picker__date--range-end': mode === 'range' && value && 'end' in value &&
6700
- value.end.toDateString() === date.toDateString(),
7034
+ (currentValue && 'start' in currentValue && currentValue.start.toDateString() === date.toDateString())),
7035
+ 'designbase-date-picker__date--range-end': mode === 'range' && currentValue && 'end' in currentValue &&
7036
+ currentValue.end.toDateString() === date.toDateString(),
6701
7037
  'designbase-date-picker__date--weekend': isWeekend && highlightWeekends,
6702
7038
  'designbase-date-picker__date--disabled': !isDateValid(date),
6703
7039
  'designbase-date-picker__date--has-events': dateEvents.length > 0,
@@ -6709,7 +7045,6 @@ inline = false, }) => {
6709
7045
  isDateSelected,
6710
7046
  isInRange,
6711
7047
  mode,
6712
- value,
6713
7048
  rangeStart,
6714
7049
  highlightWeekends,
6715
7050
  getEventsForDate,
@@ -6719,21 +7054,29 @@ inline = false, }) => {
6719
7054
  readonly,
6720
7055
  handleDateClick,
6721
7056
  handleDateHover,
7057
+ tempValue,
7058
+ selectedValue,
6722
7059
  ]);
6723
7060
  const calendarGrid = generateCalendarGrid();
6724
7061
  const weekdays = generateWeekdays();
6725
- // 드롭다운 형태일
6726
- if (!inline && trigger) {
6727
- return (jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "designbase-date-picker-container", children: [jsxRuntimeExports.jsx("div", { onClick: () => !disabled && !readonly && setIsOpen(!isOpen), children: trigger }), isOpen && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-date-picker-dropdown', `designbase-date-picker-dropdown--placement-${placement}`), children: jsxRuntimeExports.jsxs("div", { className: clsx('designbase-date-picker', `designbase-date-picker--size-${size}`, `designbase-date-picker--variant-${variant}`, {
6728
- 'designbase-date-picker--disabled': disabled,
6729
- 'designbase-date-picker--readonly': readonly,
6730
- }, className), children: [jsxRuntimeExports.jsxs("div", { className: "designbase-date-picker__header", children: [jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__nav-button", onClick: goToPreviousMonth, disabled: disabled || readonly, type: "button", "aria-label": "\uC774\uC804 \uB2EC", children: jsxRuntimeExports.jsx(ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__current-month", children: currentDate.toLocaleDateString(locale, { year: 'numeric', month: 'long' }) }), jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__nav-button", onClick: goToNextMonth, disabled: disabled || readonly, type: "button", "aria-label": "\uB2E4\uC74C \uB2EC", children: jsxRuntimeExports.jsx(ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__weekdays", children: weekdays.map(day => (jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__weekday", children: day }, day))) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__grid", children: calendarGrid.map(date => renderDateCell(date)) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__footer", children: jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__today-button", onClick: goToToday, disabled: disabled || readonly, type: "button", children: "\uC624\uB298" }) })] }) }))] }));
7062
+ // 캘린더 렌더링 함수
7063
+ const renderCalendar = (isModal = false) => {
7064
+ return (jsxRuntimeExports.jsxs("div", { className: "designbase-date-picker__selector", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-date-picker__header", children: [jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__nav-button", onClick: goToPreviousMonth, disabled: disabled || readonly, type: "button", "aria-label": "\uC774\uC804 \uB2EC", children: jsxRuntimeExports.jsx(ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__current-month", children: currentDate.toLocaleDateString(locale, { year: 'numeric', month: 'long' }) }), jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__nav-button", onClick: goToNextMonth, disabled: disabled || readonly, type: "button", "aria-label": "\uB2E4\uC74C \uB2EC", children: jsxRuntimeExports.jsx(ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__weekdays", children: weekdays.map(day => (jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__weekday", children: day }, day))) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__grid", children: calendarGrid.map(date => renderDateCell(date, isModal)) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__footer", children: jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__today-button", onClick: goToToday, disabled: disabled || readonly, type: "button", children: "\uC624\uB298" }) }), isModal && (jsxRuntimeExports.jsx(ModalFooter, { children: jsxRuntimeExports.jsxs("div", { className: "designbase-date-picker__modal-footer", children: [jsxRuntimeExports.jsx(Button, { variant: "secondary", size: "m", onPress: handleModalCancel, children: "\uCDE8\uC18C" }), jsxRuntimeExports.jsx(Button, { variant: "primary", size: "m", onPress: handleModalApply, children: "\uC801\uC6A9" })] }) }))] }));
7065
+ };
7066
+ // 인라인 모드일 때
7067
+ if (inline) {
7068
+ return (jsxRuntimeExports.jsx("div", { className: clsx('designbase-date-picker', `designbase-date-picker--size-${size}`, `designbase-date-picker--variant-${variant}`, {
7069
+ 'designbase-date-picker--disabled': disabled,
7070
+ 'designbase-date-picker--readonly': readonly,
7071
+ }, className), children: renderCalendar(false) }));
6731
7072
  }
6732
- // 인라인 형태 (기존 방식)
6733
- return (jsxRuntimeExports.jsxs("div", { className: clsx('designbase-date-picker', `designbase-date-picker--size-${size}`, `designbase-date-picker--variant-${variant}`, {
6734
- 'designbase-date-picker--disabled': disabled,
6735
- 'designbase-date-picker--readonly': readonly,
6736
- }, className), children: [jsxRuntimeExports.jsxs("div", { className: "designbase-date-picker__header", children: [jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__nav-button", onClick: goToPreviousMonth, disabled: disabled || readonly, type: "button", "aria-label": "\uC774\uC804 \uB2EC", children: jsxRuntimeExports.jsx(ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__current-month", children: currentDate.toLocaleDateString(locale, { year: 'numeric', month: 'long' }) }), jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__nav-button", onClick: goToNextMonth, disabled: disabled || readonly, type: "button", "aria-label": "\uB2E4\uC74C \uB2EC", children: jsxRuntimeExports.jsx(ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__weekdays", children: weekdays.map(day => (jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__weekday", children: day }, day))) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__grid", children: calendarGrid.map(date => renderDateCell(date)) }), jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__footer", children: jsxRuntimeExports.jsx("button", { className: "designbase-date-picker__today-button", onClick: goToToday, disabled: disabled || readonly, type: "button", children: "\uC624\uB298" }) })] }));
7073
+ // 드롭다운/모달 모드일
7074
+ const classes = clsx('designbase-date-picker', `designbase-date-picker--${size}`, {
7075
+ 'designbase-date-picker--disabled': disabled,
7076
+ 'designbase-date-picker--readonly': readonly,
7077
+ 'designbase-date-picker--open': isOpen,
7078
+ }, className);
7079
+ return (jsxRuntimeExports.jsxs("div", { ref: pickerRef, className: classes, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-date-picker__trigger", children: [jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-date-picker__icon-display", onClick: togglePicker, disabled: disabled, "aria-label": "\uB0A0\uC9DC \uC120\uD0DD \uC5F4\uAE30", children: jsxRuntimeExports.jsx(CalendarIcon, { size: 16 }) }), jsxRuntimeExports.jsx("input", { type: "text", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, onClick: (e) => e.stopPropagation(), disabled: disabled, readOnly: readonly, className: "designbase-date-picker__input", placeholder: "\uB0A0\uC9DC\uB97C \uC120\uD0DD\uD558\uC138\uC694", "aria-label": "\uB0A0\uC9DC \uC785\uB825" }), jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-date-picker__toggle", onClick: togglePicker, disabled: disabled, "aria-label": "\uB0A0\uC9DC \uC120\uD0DD \uC5F4\uAE30", children: jsxRuntimeExports.jsx(ChevronDownIcon, { size: 16 }) })] }), type === 'dropdown' && isOpen && (jsxRuntimeExports.jsx("div", { className: "designbase-date-picker__dropdown", role: "dialog", "aria-modal": "false", children: renderCalendar(false) })), type === 'modal' && (jsxRuntimeExports.jsx(Modal, { isOpen: isOpen, onClose: handleModalCancel, title: "\uB0A0\uC9DC \uC120\uD0DD", size: "s", children: renderCalendar(true) }))] }));
6737
7080
  };
6738
7081
 
6739
7082
  const Drawer = ({ isOpen, onClose, title, children, position = 'right', size = 'm', showCloseButton = true, closeOnBackdropClick = true, closeOnEscape = true, animationDuration = 300, showOverlay = true, overlayClosable = true, className, style, zIndex = 1000, trapFocus = true, id, ...props }) => {
@@ -7089,12 +7432,10 @@ const EmptyState = ({ variant = 'no-data', size = 'm', title, description, icon:
7089
7432
  const getDefaultIcon = () => {
7090
7433
  const defaultIconSize = iconSize || (() => {
7091
7434
  switch (size) {
7092
- case 'xs': return 32;
7093
- case 's': return 48;
7094
- case 'm': return 64;
7095
- case 'l': return 96;
7096
- case 'xl': return 128;
7097
- default: return 64;
7435
+ case 's': return 24;
7436
+ case 'm': return 32;
7437
+ case 'l': return 48;
7438
+ default: return 32;
7098
7439
  }
7099
7440
  })();
7100
7441
  if (CustomIcon) {
@@ -7127,6 +7468,7 @@ const Progressbar = ({ value, max = 100, min = 0, size = 'm', variant = 'default
7127
7468
  const classes = clsx('designbase-progressbar', `designbase-progressbar--${size}`, `designbase-progressbar--${variant}`, `designbase-progressbar--${style}`, {
7128
7469
  'designbase-progressbar--full-width': fullWidth,
7129
7470
  'designbase-progressbar--disabled': disabled,
7471
+ 'designbase-progressbar--completed': percentage === 100,
7130
7472
  }, className);
7131
7473
  const containerClasses = clsx('designbase-progressbar__container', `designbase-progressbar__container--${labelPosition}`);
7132
7474
  const trackClasses = clsx('designbase-progressbar__track', {
@@ -7242,13 +7584,13 @@ const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multip
7242
7584
  const getStatusIcon = useCallback((status) => {
7243
7585
  switch (status) {
7244
7586
  case 'pending':
7245
- return (jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: jsxRuntimeExports.jsx("circle", { cx: "8", cy: "8", r: "7", stroke: "currentColor", strokeWidth: "2", fill: "none" }) }));
7587
+ return jsxRuntimeExports.jsx(ClockIcon, { size: 20 });
7246
7588
  case 'uploading':
7247
7589
  return jsxRuntimeExports.jsx(Spinner, { type: "circular", size: "s" });
7248
7590
  case 'success':
7249
- return (jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: jsxRuntimeExports.jsx("path", { d: "M13 5L6 12L3 9", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
7591
+ return jsxRuntimeExports.jsx(DoneIcon$1, { size: 20 });
7250
7592
  case 'error':
7251
- return (jsxRuntimeExports.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: [jsxRuntimeExports.jsx("path", { d: "M8 1L15 8L8 15L1 8L8 1Z", stroke: "currentColor", strokeWidth: "2" }), jsxRuntimeExports.jsx("path", { d: "M8 5V9", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }), jsxRuntimeExports.jsx("circle", { cx: "8", cy: "12", r: "1", fill: "currentColor" })] }));
7593
+ return jsxRuntimeExports.jsx(ErrorIcon, { size: 20 });
7252
7594
  default:
7253
7595
  return null;
7254
7596
  }
@@ -7259,11 +7601,12 @@ const FileUploader = ({ size = 'm', variant = 'default', accept, maxSize, multip
7259
7601
  }, className), children: [jsxRuntimeExports.jsx(Dropzone, { size: size, variant: variant, accept: accept, maxSize: maxSize, multiple: multiple, disabled: disabled, readonly: readonly, onFileSelect: handleFileSelect }), showFileList && uploadedFiles.length > 0 && (jsxRuntimeExports.jsxs("div", { className: "designbase-file-uploader__file-list", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-file-uploader__file-list-title", children: ["\uC5C5\uB85C\uB4DC\uB41C \uD30C\uC77C\uB4E4 (", uploadedFiles.length, "\uAC1C)"] }), jsxRuntimeExports.jsx("div", { className: "designbase-file-uploader__file-items", children: uploadedFiles.map((uploadFile) => (jsxRuntimeExports.jsxs("div", { className: clsx('designbase-file-uploader__file-item', `designbase-file-uploader__file-item--${uploadFile.status}`), children: [jsxRuntimeExports.jsxs("div", { className: "designbase-file-uploader__file-info", children: [jsxRuntimeExports.jsx("div", { className: "designbase-file-uploader__file-icon", children: getStatusIcon(uploadFile.status) }), jsxRuntimeExports.jsxs("div", { className: "designbase-file-uploader__file-details", children: [jsxRuntimeExports.jsx("div", { className: "designbase-file-uploader__file-name", children: uploadFile.file.name }), jsxRuntimeExports.jsx("div", { className: "designbase-file-uploader__file-size", children: formatFileSize(uploadFile.file.size) }), uploadFile.error && (jsxRuntimeExports.jsx("div", { className: "designbase-file-uploader__file-error", children: uploadFile.error }))] })] }), showProgress && uploadFile.status === 'uploading' && (jsxRuntimeExports.jsx("div", { className: "designbase-file-uploader__progress", children: jsxRuntimeExports.jsx(Progressbar, { value: uploadFile.progress || 0, size: "s", variant: "primary", style: "solid", showLabel: true, labelPosition: "inside", fullWidth: true }) })), jsxRuntimeExports.jsxs("div", { className: "designbase-file-uploader__file-actions", children: [uploadFile.status === 'error' && (jsxRuntimeExports.jsx("button", { className: "designbase-file-uploader__retry-button", onClick: () => handleRetry(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC7AC\uC2DC\uB3C4" })), jsxRuntimeExports.jsx("button", { className: "designbase-file-uploader__remove-button", onClick: () => handleRemove(uploadFile.id), disabled: disabled || readonly, type: "button", children: "\uC0AD\uC81C" })] })] }, uploadFile.id))) })] }))] }));
7260
7602
  };
7261
7603
 
7262
- const FloatingActionButton = forwardRef(({ size = 'm', variant = 'primary', icon, disabled = false, onClick, className, style, ...props }, forwardedRef) => {
7604
+ const FloatingActionButton = forwardRef(({ size = 'm', variant = 'primary', icon, loading = false, disabled = false, onClick, className, style, ...props }, forwardedRef) => {
7263
7605
  const classes = clsx('designbase-fab', `designbase-fab--${size}`, `designbase-fab--${variant}`, {
7264
7606
  'designbase-fab--disabled': disabled,
7607
+ 'designbase-fab--loading': loading,
7265
7608
  }, className);
7266
- return (jsxRuntimeExports.jsx("button", { ref: forwardedRef, className: classes, style: style, onClick: onClick, disabled: disabled, "aria-label": "Floating action button", ...props, children: icon && (jsxRuntimeExports.jsx("div", { className: "designbase-fab__icon", children: icon })) }));
7609
+ return (jsxRuntimeExports.jsx("button", { ref: forwardedRef, className: classes, style: style, onClick: onClick, disabled: disabled || loading, "aria-label": "Floating action button", "aria-busy": loading, ...props, children: loading ? (jsxRuntimeExports.jsx("div", { className: "designbase-fab__icon", children: jsxRuntimeExports.jsx(Spinner, { type: "circular", size: "s" }) })) : icon && (jsxRuntimeExports.jsx("div", { className: "designbase-fab__icon", children: icon })) }));
7267
7610
  });
7268
7611
  FloatingActionButton.displayName = 'FloatingActionButton';
7269
7612
 
@@ -7335,7 +7678,251 @@ const Textarea = forwardRef(({ label, placeholder, defaultValue, value, size = '
7335
7678
  });
7336
7679
  Textarea.displayName = 'Textarea';
7337
7680
 
7338
- const Form = ({ fields, layout = 'vertical', size = 'm', variant = 'default', onSubmit, onChange, initialValues = {}, submitText = '제출', submitLoading = false, submitDisabled = false, showReset = false, resetText = '리셋', title, description, className, style, }) => {
7681
+ const ITEM_HEIGHT = 40;
7682
+ const TimePicker$1 = ({ size = 'm', type = 'dropdown', value, defaultValue = '12:00', format = '24h', minuteStep = 1, disabled = false, readonly = false, onChange, className, }) => {
7683
+ const [selectedTime, setSelectedTime] = useState(value || defaultValue);
7684
+ const [isOpen, setIsOpen] = useState(false);
7685
+ const [inputValue, setInputValue] = useState(value || defaultValue);
7686
+ const [hours, setHours] = useState(12);
7687
+ const [minutes, setMinutes] = useState(0);
7688
+ const [seconds, setSeconds] = useState(0);
7689
+ const [period, setPeriod] = useState('PM');
7690
+ // 모달용 임시 상태 (적용 전까지는 변경되지 않음)
7691
+ const [tempHours, setTempHours] = useState(12);
7692
+ const [tempMinutes, setTempMinutes] = useState(0);
7693
+ const [tempSeconds, setTempSeconds] = useState(0);
7694
+ const [tempPeriod, setTempPeriod] = useState('PM');
7695
+ const pickerRef = useRef(null);
7696
+ // 외부 클릭 감지 (드롭다운만)
7697
+ useEffect(() => {
7698
+ if (!isOpen || type !== 'dropdown')
7699
+ return;
7700
+ const handleClickOutside = (event) => {
7701
+ if (pickerRef.current && !pickerRef.current.contains(event.target)) {
7702
+ setIsOpen(false);
7703
+ }
7704
+ };
7705
+ document.addEventListener('mousedown', handleClickOutside);
7706
+ return () => document.removeEventListener('mousedown', handleClickOutside);
7707
+ }, [isOpen, type]);
7708
+ const is12h = format === '12h' || format === '12h-with-seconds';
7709
+ const showSeconds = format === '12h-with-seconds' || format === '24h-with-seconds';
7710
+ useEffect(() => {
7711
+ if (!value)
7712
+ return;
7713
+ setSelectedTime(value);
7714
+ setInputValue(value);
7715
+ parseTimeString(value);
7716
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7717
+ }, [value]);
7718
+ // 모달이 열릴 때 현재 값을 임시 상태로 복사
7719
+ useEffect(() => {
7720
+ if (isOpen && type === 'modal') {
7721
+ setTempHours(hours);
7722
+ setTempMinutes(minutes);
7723
+ setTempSeconds(seconds);
7724
+ setTempPeriod(period);
7725
+ }
7726
+ }, [isOpen, type, hours, minutes, seconds, period]);
7727
+ const parseTimeString = (timeStr) => {
7728
+ const s = timeStr.trim();
7729
+ let h = 0, m = 0, sec = 0, p = 'AM';
7730
+ if (is12h) {
7731
+ const re = showSeconds
7732
+ ? /^(0?[1-9]|1[0-2]):([0-5]\d):([0-5]\d)\s*(AM|PM)$/i
7733
+ : /^(0?[1-9]|1[0-2]):([0-5]\d)\s*(AM|PM)$/i;
7734
+ const match = s.match(re);
7735
+ if (match) {
7736
+ h = parseInt(match[1], 10);
7737
+ m = parseInt(match[2], 10);
7738
+ sec = showSeconds ? parseInt(match[3], 10) : 0;
7739
+ p = (showSeconds ? match[4] : match[3]).toUpperCase();
7740
+ setPeriod(p);
7741
+ setHours(h);
7742
+ setMinutes(m);
7743
+ setSeconds(sec);
7744
+ return;
7745
+ }
7746
+ }
7747
+ else {
7748
+ const re = showSeconds
7749
+ ? /^([01]?\d|2[0-3]):([0-5]\d):([0-5]\d)$/
7750
+ : /^([01]?\d|2[0-3]):([0-5]\d)$/;
7751
+ const match = s.match(re);
7752
+ if (match) {
7753
+ h = parseInt(match[1], 10);
7754
+ m = parseInt(match[2], 10);
7755
+ sec = showSeconds ? parseInt(match[3], 10) : 0;
7756
+ setHours(h);
7757
+ setMinutes(m);
7758
+ setSeconds(sec);
7759
+ return;
7760
+ }
7761
+ }
7762
+ };
7763
+ const to24Hour = (h12, p) => (p === 'AM' ? (h12 === 12 ? 0 : h12) : (h12 === 12 ? 12 : h12 + 12));
7764
+ const formatTimeString = (h, m, s, p) => {
7765
+ const HH = String(h).padStart(2, '0');
7766
+ const MM = String(m).padStart(2, '0');
7767
+ const SS = String(s).padStart(2, '0');
7768
+ if (is12h)
7769
+ return `${String(h).padStart(2, '0')}:${MM}${showSeconds ? `:${SS}` : ''} ${p || period}`;
7770
+ return `${HH}:${MM}${showSeconds ? `:${SS}` : ''}`;
7771
+ };
7772
+ const emitChange24 = (h, m, s, p) => {
7773
+ const h24 = is12h ? to24Hour(h, p || period) : h;
7774
+ onChange?.(`${String(h24).padStart(2, '0')}:${String(m).padStart(2, '0')}${showSeconds ? `:${String(s).padStart(2, '0')}` : ''}`);
7775
+ };
7776
+ const handleTimeChange = (h, m, s = seconds, p) => {
7777
+ setHours(h);
7778
+ setMinutes(m);
7779
+ setSeconds(s);
7780
+ if (p)
7781
+ setPeriod(p);
7782
+ const display = formatTimeString(h, m, s, p);
7783
+ setSelectedTime(display);
7784
+ setInputValue(display);
7785
+ emitChange24(h, m, s, p);
7786
+ };
7787
+ const handleInputBlur = () => {
7788
+ const s = inputValue.trim();
7789
+ let ok = false;
7790
+ if (is12h) {
7791
+ const re = showSeconds
7792
+ ? /^(0?[1-9]|1[0-2]):([0-5]\d):([0-5]\d)\s*(AM|PM)$/i
7793
+ : /^(0?[1-9]|1[0-2]):([0-5]\d)\s*(AM|PM)$/i;
7794
+ const m = s.match(re);
7795
+ if (m) {
7796
+ const h = parseInt(m[1], 10), mi = parseInt(m[2], 10);
7797
+ const sec = showSeconds ? parseInt(m[3], 10) : 0;
7798
+ const p = (showSeconds ? m[4] : m[3]).toUpperCase();
7799
+ handleTimeChange(h, mi, sec, p);
7800
+ ok = true;
7801
+ }
7802
+ }
7803
+ else {
7804
+ const re = showSeconds
7805
+ ? /^([01]?\d|2[0-3]):([0-5]\d):([0-5]\d)$/
7806
+ : /^([01]?\d|2[0-3]):([0-5]\d)$/;
7807
+ const m = s.match(re);
7808
+ if (m) {
7809
+ const h = parseInt(m[1], 10), mi = parseInt(m[2], 10);
7810
+ const sec = showSeconds ? parseInt(m[3], 10) : 0;
7811
+ handleTimeChange(h, mi, sec);
7812
+ ok = true;
7813
+ }
7814
+ }
7815
+ if (!ok)
7816
+ setInputValue(selectedTime);
7817
+ };
7818
+ const togglePicker = () => {
7819
+ if (disabled || readonly)
7820
+ return;
7821
+ setIsOpen((o) => !o);
7822
+ };
7823
+ // 모달용 핸들러들
7824
+ const handleModalTimeChange = (h, m, s = tempSeconds, p) => {
7825
+ setTempHours(h);
7826
+ setTempMinutes(m);
7827
+ setTempSeconds(s);
7828
+ if (p)
7829
+ setTempPeriod(p);
7830
+ };
7831
+ const handleModalApply = () => {
7832
+ // 임시 값을 실제 값으로 적용
7833
+ setHours(tempHours);
7834
+ setMinutes(tempMinutes);
7835
+ setSeconds(tempSeconds);
7836
+ setPeriod(tempPeriod);
7837
+ // 시간 변경 처리
7838
+ handleTimeChange(tempHours, tempMinutes, tempSeconds, tempPeriod);
7839
+ // 모달 닫기
7840
+ setIsOpen(false);
7841
+ };
7842
+ const handleModalCancel = () => {
7843
+ // 임시 값을 원래 값으로 복원
7844
+ setTempHours(hours);
7845
+ setTempMinutes(minutes);
7846
+ setTempSeconds(seconds);
7847
+ setTempPeriod(period);
7848
+ // 모달 닫기
7849
+ setIsOpen(false);
7850
+ };
7851
+ const pad2 = (n) => String(n).padStart(2, '0');
7852
+ /** ★ 변경 핵심: viewport(고정 레이어) ─ wheel(스크롤 영역) 분리 */
7853
+ const WheelPicker = ({ label, items, value, onChange, formatter }) => {
7854
+ const wheelRef = useRef(null);
7855
+ const scrollTimeout = useRef(null);
7856
+ const isProgrammatic = useRef(false);
7857
+ const indexOfValue = useCallback((v) => items.findIndex((x) => x === v), [items]);
7858
+ const scrollToIndex = useCallback((idx, smooth = true) => {
7859
+ if (!wheelRef.current)
7860
+ return;
7861
+ const top = idx * ITEM_HEIGHT; // 상/하 패딩은 CSS로 처리
7862
+ isProgrammatic.current = true;
7863
+ wheelRef.current.scrollTo({ top, behavior: smooth ? 'smooth' : 'auto' });
7864
+ window.setTimeout(() => { isProgrammatic.current = false; }, 150);
7865
+ }, []);
7866
+ useEffect(() => {
7867
+ const i = indexOfValue(value);
7868
+ if (i >= 0)
7869
+ scrollToIndex(i, false);
7870
+ }, [value, indexOfValue, scrollToIndex]);
7871
+ useEffect(() => {
7872
+ const i = indexOfValue(value);
7873
+ if (i >= 0)
7874
+ scrollToIndex(i, false);
7875
+ // eslint-disable-next-line react-hooks/exhaustive-deps
7876
+ }, []);
7877
+ const handleScroll = () => {
7878
+ if (isProgrammatic.current)
7879
+ return;
7880
+ if (!wheelRef.current)
7881
+ return;
7882
+ if (scrollTimeout.current)
7883
+ window.clearTimeout(scrollTimeout.current);
7884
+ scrollTimeout.current = window.setTimeout(() => {
7885
+ if (!wheelRef.current)
7886
+ return;
7887
+ const top = wheelRef.current.scrollTop;
7888
+ const idx = Math.round(top / ITEM_HEIGHT);
7889
+ const clamped = Math.max(0, Math.min(items.length - 1, idx));
7890
+ const v = items[clamped];
7891
+ scrollToIndex(clamped, true);
7892
+ if (v !== value)
7893
+ onChange(v);
7894
+ }, 120);
7895
+ };
7896
+ const fmt = (v) => (formatter ? formatter(v) : (typeof v === 'number' ? pad2(v) : String(v)));
7897
+ return (jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__wheel-container", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__wheel-label", children: label }), jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__wheel-viewport", children: [jsxRuntimeExports.jsx("div", { ref: wheelRef, className: "designbase-time-picker__wheel", onScroll: handleScroll, role: "listbox", "aria-label": label, children: items.map((item, i) => (jsxRuntimeExports.jsx("div", { className: clsx('designbase-time-picker__wheel-item', item === value && 'designbase-time-picker__wheel-item--selected'), role: "option", "aria-selected": item === value, onClick: () => {
7898
+ const idx = indexOfValue(item);
7899
+ scrollToIndex(idx, true);
7900
+ if (item !== value)
7901
+ onChange(item);
7902
+ }, children: fmt(item) }, `${label}-${item}-${i}`))) }), jsxRuntimeExports.jsx("div", { "aria-hidden": true, className: "designbase-time-picker__wheel-center-indicator" }), jsxRuntimeExports.jsx("div", { "aria-hidden": true, className: "designbase-time-picker__wheel-fade wheel-fade--top" }), jsxRuntimeExports.jsx("div", { "aria-hidden": true, className: "designbase-time-picker__wheel-fade wheel-fade--bottom" })] })] }));
7903
+ };
7904
+ const renderTimeSelector = (isModal = false) => {
7905
+ const hourItems = is12h ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 24 }, (_, i) => i);
7906
+ const minuteItems = Array.from({ length: Math.floor(60 / minuteStep) }, (_, i) => i * minuteStep);
7907
+ const secondItems = Array.from({ length: 60 }, (_, i) => i);
7908
+ const periodItems = ['AM', 'PM'];
7909
+ // 모달인 경우 임시 값 사용, 드롭다운인 경우 실제 값 사용
7910
+ const currentHours = isModal ? tempHours : hours;
7911
+ const currentMinutes = isModal ? tempMinutes : minutes;
7912
+ const currentSeconds = isModal ? tempSeconds : seconds;
7913
+ const currentPeriod = isModal ? tempPeriod : period;
7914
+ const handleChange = isModal ? handleModalTimeChange : handleTimeChange;
7915
+ return (jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__selector", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__picker-wheels", children: [jsxRuntimeExports.jsx(WheelPicker, { label: "\uC2DC\uAC04", items: hourItems, value: currentHours, onChange: (v) => handleChange(v, currentMinutes, currentSeconds, currentPeriod) }), jsxRuntimeExports.jsx(WheelPicker, { label: "\uBD84", items: minuteItems, value: currentMinutes, onChange: (v) => handleChange(currentHours, v, currentSeconds, currentPeriod) }), showSeconds && (jsxRuntimeExports.jsx(WheelPicker, { label: "\uCD08", items: secondItems, value: currentSeconds, onChange: (v) => handleChange(currentHours, currentMinutes, v, currentPeriod) })), is12h && (jsxRuntimeExports.jsx(WheelPicker, { label: "AM/PM", items: periodItems, value: currentPeriod, onChange: (v) => handleChange(currentHours, currentMinutes, currentSeconds, v), formatter: (x) => String(x) }))] }), jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__preview", "aria-live": "polite", children: [jsxRuntimeExports.jsx(ClockIcon, { size: 20 }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__preview-time", children: formatTimeString(currentHours, currentMinutes, currentSeconds, currentPeriod) })] }), !isModal && (jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__dropdown-actions", children: jsxRuntimeExports.jsx(Button, { variant: "primary", size: "m", onPress: () => {
7916
+ handleTimeChange(currentHours, currentMinutes, currentSeconds, currentPeriod);
7917
+ setIsOpen(false);
7918
+ }, children: "\uC120\uD0DD" }) }))] }));
7919
+ };
7920
+ const classes = clsx('designbase-time-picker', `designbase-time-picker--${size}`, { 'designbase-time-picker--disabled': disabled, 'designbase-time-picker--readonly': readonly, 'designbase-time-picker--open': isOpen }, className);
7921
+ return (jsxRuntimeExports.jsxs("div", { ref: pickerRef, className: classes, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__trigger", children: [jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-time-picker__icon-display", onClick: togglePicker, disabled: disabled, "aria-label": "\uC2DC\uAC04 \uC120\uD0DD \uC5F4\uAE30", children: jsxRuntimeExports.jsx(ClockIcon, { size: 16 }) }), jsxRuntimeExports.jsx("input", { type: "text", value: inputValue, onChange: (e) => setInputValue(e.target.value), onBlur: handleInputBlur, onClick: (e) => e.stopPropagation(), disabled: disabled, readOnly: readonly, className: "designbase-time-picker__input", placeholder: is12h ? (showSeconds ? '12:00:00 PM' : '12:00 PM') : (showSeconds ? '12:00:00' : '12:00'), "aria-label": "\uC2DC\uAC04 \uC785\uB825" }), jsxRuntimeExports.jsx("button", { type: "button", className: "designbase-time-picker__toggle", onClick: togglePicker, disabled: disabled, "aria-label": "\uC2DC\uAC04 \uC120\uD0DD \uC5F4\uAE30", children: jsxRuntimeExports.jsx(ChevronDownIcon, { size: 16 }) })] }), type === 'dropdown' && isOpen && (jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__dropdown", role: "dialog", "aria-modal": "false", children: renderTimeSelector(false) })), type === 'modal' && (jsxRuntimeExports.jsxs(Modal, { isOpen: isOpen, onClose: handleModalCancel, title: "\uC2DC\uAC04 \uC120\uD0DD", size: "s", children: [renderTimeSelector(true), jsxRuntimeExports.jsx(ModalFooter, { children: jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__modal-footer", children: [jsxRuntimeExports.jsx(Button, { variant: "secondary", size: "m", onPress: handleModalCancel, children: "\uCDE8\uC18C" }), jsxRuntimeExports.jsx(Button, { variant: "primary", size: "m", onPress: handleModalApply, children: "\uC801\uC6A9" })] }) })] }))] }));
7922
+ };
7923
+ TimePicker$1.displayName = 'TimePicker';
7924
+
7925
+ const Form = ({ fields, columns = 1, size = 'm', variant = 'default', onSubmit, onChange, initialValues = {}, submitText = '제출', submitLoading = false, submitDisabled = false, showReset = false, resetText = '리셋', title, description, className, style, }) => {
7339
7926
  const [values, setValues] = useState(initialValues);
7340
7927
  const [errors, setErrors] = useState({});
7341
7928
  const [touched, setTouched] = useState({});
@@ -7495,20 +8082,20 @@ const Form = ({ fields, layout = 'vertical', size = 'm', variant = 'default', on
7495
8082
  }
7496
8083
  return (jsxRuntimeExports.jsx(Checkbox, { ...commonProps, isSelected: fieldValue || false, isDisabled: field.disabled, children: field.label }));
7497
8084
  case 'file':
7498
- return (jsxRuntimeExports.jsx("input", { type: "file", id: field.name, name: field.name, accept: field.fileUpload?.accept, multiple: field.fileUpload?.multiple, disabled: field.disabled, required: field.required, onChange: (e) => {
7499
- const files = e.target.files;
7500
- if (files) {
7501
- const fileArray = Array.from(files);
7502
- handleFieldChange(field.name, field.fileUpload?.multiple ? fileArray : fileArray[0]);
7503
- }
7504
- }, onBlur: () => handleFieldBlur(field.name), className: clsx('designbase-form__file-input', {
7505
- 'designbase-form__input--error': touched[field.name] && errors[field.name],
7506
- }) }));
8085
+ return (jsxRuntimeExports.jsx(FileUploader, { size: size, accept: field.fileUpload?.accept, maxSize: field.fileUpload?.maxSize, multiple: field.fileUpload?.multiple, disabled: field.disabled, readonly: field.readOnly, onUpload: (files) => {
8086
+ handleFieldChange(field.name, files);
8087
+ } }));
8088
+ case 'date':
8089
+ return (jsxRuntimeExports.jsx(DatePicker, { size: size, value: fieldValue, disabled: field.disabled, readonly: field.readOnly, mode: "single", type: "dropdown", onChange: (date) => handleFieldChange(field.name, date) }));
8090
+ case 'color':
8091
+ return (jsxRuntimeExports.jsx(ColorPicker$1, { size: size, value: fieldValue, disabled: field.disabled, readonly: field.readOnly, showAlpha: true, showFormatSelector: true, showCopyButton: true, onChange: (color) => handleFieldChange(field.name, color) }));
8092
+ case 'timepicker':
8093
+ return (jsxRuntimeExports.jsx(TimePicker$1, { size: size, value: fieldValue, disabled: field.disabled, readonly: field.readOnly, format: "24h", type: "dropdown", minuteStep: 1, onChange: (time) => handleFieldChange(field.name, time) }));
7507
8094
  default:
7508
8095
  return (jsxRuntimeExports.jsx(Input, { ...commonProps, type: field.type, min: field.min, max: field.max, minLength: field.minLength, maxLength: field.maxLength, pattern: field.pattern }));
7509
8096
  }
7510
8097
  }, [values, errors, touched, handleFieldChange, handleFieldBlur, size]);
7511
- const classes = clsx('designbase-form', `designbase-form--${layout}`, `designbase-form--${size}`, `designbase-form--${variant}`, className);
8098
+ const classes = clsx('designbase-form', `designbase-form--columns-${columns}`, `designbase-form--${size}`, `designbase-form--${variant}`, className);
7512
8099
  return (jsxRuntimeExports.jsxs("form", { ref: formRef, className: classes, style: style, onSubmit: handleSubmit, children: [(title || description) && (jsxRuntimeExports.jsxs("div", { className: "designbase-form__header", children: [title && jsxRuntimeExports.jsx("h3", { className: "designbase-form__title", children: title }), description && jsxRuntimeExports.jsx("p", { className: "designbase-form__description", children: description })] })), jsxRuntimeExports.jsx("div", { className: "designbase-form__fields", children: fields.map(field => (jsxRuntimeExports.jsxs("div", { className: clsx('designbase-form__field-wrapper', `designbase-form__field-wrapper--${field.type}`), children: [field.label && (jsxRuntimeExports.jsxs("label", { className: "designbase-form__label", htmlFor: field.name, children: [field.label, field.required && jsxRuntimeExports.jsx("span", { className: "designbase-form__required", children: "*" })] })), renderField(field), touched[field.name] && errors[field.name] && (jsxRuntimeExports.jsx("div", { className: "designbase-form__error", children: errors[field.name] })), field.helpText && !errors[field.name] && (jsxRuntimeExports.jsx("div", { className: "designbase-form__help", children: field.helpText }))] }, field.name))) }), jsxRuntimeExports.jsxs("div", { className: "designbase-form__actions", children: [jsxRuntimeExports.jsx(Button, { type: "submit", variant: "primary", size: size, loading: submitLoading, disabled: submitDisabled, children: submitText }), showReset && (jsxRuntimeExports.jsx(Button, { type: "button", variant: "secondary", size: size, onClick: handleReset, children: resetText }))] })] }));
7513
8100
  };
7514
8101
 
@@ -7645,7 +8232,7 @@ const Stat = ({ value, label, icon, iconPosition = 'left', size = 'm', variant =
7645
8232
  return;
7646
8233
  onClick?.();
7647
8234
  };
7648
- return (jsxRuntimeExports.jsxs("div", { className: classes, style: customStyle, onClick: handleClick, role: clickable ? 'button' : undefined, tabIndex: clickable ? 0 : undefined, children: [icon && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-stat__icon', `designbase-stat__icon--${iconPosition}`), children: icon })), jsxRuntimeExports.jsxs("div", { className: "designbase-stat__content", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-stat__main", children: [jsxRuntimeExports.jsx("div", { className: "designbase-stat__value", children: value }), jsxRuntimeExports.jsx("div", { className: "designbase-stat__label", children: label })] }), showChange && change && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-stat__change', getChangeClass()), children: getChangeText() })), showDescription && description && (jsxRuntimeExports.jsx("div", { className: "designbase-stat__description", children: description }))] }), showProgress && progress !== undefined && (jsxRuntimeExports.jsx("div", { className: "designbase-stat__progress", children: jsxRuntimeExports.jsx("div", { className: "designbase-stat__progress-bar", style: { width: `${Math.min(Math.max(progress, 0), 100)}%` } }) }))] }));
8235
+ return (jsxRuntimeExports.jsxs("div", { className: classes, style: customStyle, onClick: handleClick, role: clickable ? 'button' : undefined, tabIndex: clickable ? 0 : undefined, children: [icon && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-stat__icon', `designbase-stat__icon--${iconPosition}`), children: icon })), jsxRuntimeExports.jsxs("div", { className: "designbase-stat__content", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-stat__main", children: [jsxRuntimeExports.jsx("div", { className: "designbase-stat__value", children: value }), jsxRuntimeExports.jsx("div", { className: "designbase-stat__label", children: label })] }), showChange && change && (jsxRuntimeExports.jsx("div", { className: clsx('designbase-stat__change', getChangeClass()), children: getChangeText() })), showDescription && description && (jsxRuntimeExports.jsx("div", { className: "designbase-stat__description", children: description }))] }), showProgress && typeof progress === 'number' && (jsxRuntimeExports.jsx("div", { className: "designbase-stat__progress", children: jsxRuntimeExports.jsx(Progressbar, { value: Math.min(Math.max(progress, 0), 100), size: "s", variant: color === 'primary' ? 'primary' : color === 'success' ? 'success' : color === 'warning' ? 'warning' : color === 'error' ? 'danger' : 'default' }) }))] }));
7649
8236
  };
7650
8237
 
7651
8238
  const HeroFeature = ({ title, subtitle, description, image, imageAlt, backgroundImage, backgroundVideo, overlayColor, overlayOpacity = 0.5, buttons = [], badge, stats = [], icon, size = 'l', variant = 'default', theme = 'light', alignment = 'left', animated = false, parallax = false, fullHeight = false, minHeight, maxHeight, className, }) => {
@@ -7726,7 +8313,7 @@ const HeroFeature = ({ title, subtitle, description, image, imageAlt, background
7726
8313
  return (jsxRuntimeExports.jsxs("section", { className: classes, style: containerStyle, children: [renderBackground(), renderOverlay(), jsxRuntimeExports.jsxs("div", { className: "designbase-hero-feature__container", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-hero-feature__content", children: [icon && (jsxRuntimeExports.jsx("div", { className: "designbase-hero-feature__icon", children: icon })), renderBadge(), jsxRuntimeExports.jsx("h1", { className: "designbase-hero-feature__title", children: title }), subtitle && (jsxRuntimeExports.jsx("h2", { className: "designbase-hero-feature__subtitle", children: subtitle })), description && (jsxRuntimeExports.jsx("p", { className: "designbase-hero-feature__description", children: description })), renderButtons(), renderStats()] }), variant === 'split' && renderImage()] }), variant !== 'split' && renderImage()] }));
7727
8314
  };
7728
8315
 
7729
- const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', theme = 'light', isOpen, onOpenChange, onImageChange, enableZoom = true, enableRotate = true, enableDownload = true, enableFullscreen = true, enableKeyboard = true, enableWheelZoom = true, showThumbnails = true, showCounter = true, showCloseButton = true, showNavigationButtons = true, showToolbar = true, closeOnBackdropClick = true, closeOnEscape = true, readonly = false, disabled = false, className, }) => {
8316
+ const Lightbox = ({ images, currentIndex = 0, size = 'l', isOpen, onOpenChange, onImageChange, enableZoom = true, enableRotate = true, enableDownload = true, enableFullscreen = true, enableKeyboard = true, enableWheelZoom = true, showThumbnails = true, showCounter = true, showCloseButton = true, showNavigationButtons = true, showToolbar = true, closeOnBackdropClick = true, closeOnEscape = true, className, }) => {
7730
8317
  const [internalCurrentIndex, setInternalCurrentIndex] = useState(currentIndex);
7731
8318
  const [zoomLevel, setZoomLevel] = useState(1);
7732
8319
  const [rotation, setRotation] = useState(0);
@@ -7787,32 +8374,32 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
7787
8374
  }, [onOpenChange]);
7788
8375
  // 줌 인
7789
8376
  const handleZoomIn = useCallback(() => {
7790
- if (!enableZoom || disabled || readonly)
8377
+ if (!enableZoom)
7791
8378
  return;
7792
8379
  setZoomLevel(prev => Math.min(prev * 1.5, 5));
7793
- }, [enableZoom, disabled, readonly]);
8380
+ }, [enableZoom]);
7794
8381
  // 줌 아웃
7795
8382
  const handleZoomOut = useCallback(() => {
7796
- if (!enableZoom || disabled || readonly)
8383
+ if (!enableZoom)
7797
8384
  return;
7798
8385
  setZoomLevel(prev => Math.max(prev / 1.5, 0.1));
7799
- }, [enableZoom, disabled, readonly]);
8386
+ }, [enableZoom]);
7800
8387
  // 줌 리셋
7801
8388
  const handleZoomReset = useCallback(() => {
7802
- if (!enableZoom || disabled || readonly)
8389
+ if (!enableZoom)
7803
8390
  return;
7804
8391
  setZoomLevel(1);
7805
8392
  setImagePosition({ x: 0, y: 0 });
7806
- }, [enableZoom, disabled, readonly]);
8393
+ }, [enableZoom]);
7807
8394
  // 회전
7808
8395
  const handleRotate = useCallback(() => {
7809
- if (!enableRotate || disabled || readonly)
8396
+ if (!enableRotate)
7810
8397
  return;
7811
8398
  setRotation(prev => (prev + 90) % 360);
7812
- }, [enableRotate, disabled, readonly]);
8399
+ }, [enableRotate]);
7813
8400
  // 다운로드
7814
8401
  const handleDownload = useCallback(() => {
7815
- if (!enableDownload || disabled || readonly)
8402
+ if (!enableDownload)
7816
8403
  return;
7817
8404
  const currentImage = images[internalCurrentIndex];
7818
8405
  if (currentImage) {
@@ -7849,13 +8436,13 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
7849
8436
  document.body.removeChild(link);
7850
8437
  });
7851
8438
  }
7852
- }, [enableDownload, disabled, readonly, images, internalCurrentIndex]);
8439
+ }, [enableDownload, images, internalCurrentIndex]);
7853
8440
  // 전체화면 토글
7854
8441
  const handleFullscreenToggle = useCallback(() => {
7855
- if (!enableFullscreen || disabled || readonly)
8442
+ if (!enableFullscreen)
7856
8443
  return;
7857
8444
  setIsFullscreen(prev => !prev);
7858
- }, [enableFullscreen, disabled, readonly]);
8445
+ }, [enableFullscreen]);
7859
8446
  // 이미지 로드 핸들러
7860
8447
  const handleImageLoad = useCallback(() => {
7861
8448
  setIsImageLoaded(true);
@@ -7879,7 +8466,7 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
7879
8466
  }, [isOpen, internalCurrentIndex, images]);
7880
8467
  // 마우스 휠 줌 핸들러
7881
8468
  const handleWheel = useCallback((event) => {
7882
- if (!enableWheelZoom || disabled || readonly)
8469
+ if (!enableWheelZoom)
7883
8470
  return;
7884
8471
  event.preventDefault();
7885
8472
  if (event.deltaY < 0) {
@@ -7888,44 +8475,40 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
7888
8475
  else {
7889
8476
  handleZoomOut();
7890
8477
  }
7891
- }, [enableWheelZoom, disabled, readonly, handleZoomIn, handleZoomOut]);
8478
+ }, [enableWheelZoom, handleZoomIn, handleZoomOut]);
7892
8479
  // 드래그 시작 핸들러
7893
8480
  const handleMouseDown = useCallback((event) => {
7894
- if (zoomLevel <= 1 || disabled || readonly)
8481
+ if (zoomLevel <= 1)
7895
8482
  return;
7896
8483
  event.preventDefault();
7897
8484
  setIsDragging(true);
7898
8485
  setDragStart({ x: event.clientX - imagePosition.x, y: event.clientY - imagePosition.y });
7899
- }, [zoomLevel, disabled, readonly, imagePosition]);
8486
+ }, [zoomLevel, imagePosition]);
7900
8487
  // 드래그 중 핸들러
7901
8488
  const handleMouseMove = useCallback((event) => {
7902
- if (!isDragging || zoomLevel <= 1 || disabled || readonly)
8489
+ if (!isDragging || zoomLevel <= 1)
7903
8490
  return;
7904
8491
  event.preventDefault();
7905
8492
  setImagePosition({
7906
8493
  x: event.clientX - dragStart.x,
7907
8494
  y: event.clientY - dragStart.y,
7908
8495
  });
7909
- }, [isDragging, zoomLevel, disabled, readonly, dragStart]);
8496
+ }, [isDragging, zoomLevel, dragStart]);
7910
8497
  // 드래그 종료 핸들러
7911
8498
  const handleMouseUp = useCallback(() => {
7912
8499
  setIsDragging(false);
7913
8500
  }, []);
7914
8501
  // 터치 시작 핸들러
7915
8502
  const handleTouchStart = useCallback((event) => {
7916
- if (disabled || readonly)
7917
- return;
7918
8503
  const touch = event.touches[0];
7919
8504
  setTouchStart({ x: touch.clientX, y: touch.clientY });
7920
- }, [disabled, readonly]);
8505
+ }, []);
7921
8506
  // 터치 종료 핸들러
7922
8507
  const handleTouchEnd = useCallback((event) => {
7923
- if (disabled || readonly)
7924
- return;
7925
8508
  const touch = event.changedTouches[0];
7926
8509
  setTouchEnd({ x: touch.clientX, y: touch.clientY });
7927
8510
  handleSwipe();
7928
- }, [disabled, readonly]);
8511
+ }, []);
7929
8512
  // 스와이프 처리
7930
8513
  const handleSwipe = useCallback(() => {
7931
8514
  const deltaX = touchStart.x - touchEnd.x;
@@ -7945,7 +8528,7 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
7945
8528
  }, [touchStart, touchEnd, goToNext, goToPrevious]);
7946
8529
  // 키보드 이벤트 핸들러
7947
8530
  useEffect(() => {
7948
- if (!enableKeyboard || !isOpen || disabled || readonly)
8531
+ if (!enableKeyboard || !isOpen)
7949
8532
  return;
7950
8533
  const handleKeyDown = (event) => {
7951
8534
  switch (event.key) {
@@ -7986,8 +8569,6 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
7986
8569
  }, [
7987
8570
  enableKeyboard,
7988
8571
  isOpen,
7989
- disabled,
7990
- readonly,
7991
8572
  closeOnEscape,
7992
8573
  handleClose,
7993
8574
  goToPrevious,
@@ -8021,11 +8602,9 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
8021
8602
  const currentImage = images[internalCurrentIndex];
8022
8603
  if (!isOpen || !currentImage)
8023
8604
  return null;
8024
- return (jsxRuntimeExports.jsx("div", { className: clsx('designbase-lightbox', `designbase-lightbox--size-${size}`, `designbase-lightbox--variant-${variant}`, `designbase-lightbox--theme-${theme}`, {
8605
+ return (jsxRuntimeExports.jsx("div", { className: clsx('designbase-lightbox', `designbase-lightbox--size-${size}`, {
8025
8606
  'designbase-lightbox--fullscreen': isFullscreen,
8026
- 'designbase-lightbox--disabled': disabled,
8027
- 'designbase-lightbox--readonly': readonly,
8028
- }, className), children: jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__overlay", children: jsxRuntimeExports.jsxs("div", { ref: modalRef, className: "designbase-lightbox__modal", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__header", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__title", children: [currentImage.title && (jsxRuntimeExports.jsx("h3", { className: "designbase-lightbox__title-text", children: currentImage.title })), showCounter && (jsxRuntimeExports.jsxs("span", { className: "designbase-lightbox__counter", children: [internalCurrentIndex + 1, " / ", images.length] }))] }), jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__header-actions", children: [showToolbar && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [enableZoom && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleZoomOut, disabled: disabled || readonly, title: "\uC90C \uC544\uC6C3", type: "button", children: jsxRuntimeExports.jsx(MinusIcon$1, { size: 16 }) }), jsxRuntimeExports.jsxs("button", { className: "designbase-lightbox__toolbar-button", onClick: handleZoomReset, disabled: disabled || readonly, title: "\uC90C \uB9AC\uC14B", type: "button", children: [Math.round(zoomLevel * 100), "%"] }), jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleZoomIn, disabled: disabled || readonly, title: "\uC90C \uC778", type: "button", children: jsxRuntimeExports.jsx(PlusIcon, { size: 16 }) })] })), enableRotate && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleRotate, disabled: disabled || readonly, title: "\uD68C\uC804", type: "button", children: jsxRuntimeExports.jsx(RefreshIcon, { size: 16 }) })), enableDownload && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleDownload, disabled: disabled || readonly, title: "\uB2E4\uC6B4\uB85C\uB4DC", type: "button", children: jsxRuntimeExports.jsx(DownloadIcon, { size: 16 }) })), enableFullscreen && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleFullscreenToggle, disabled: disabled || readonly, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsxRuntimeExports.jsx(ShrinkIcon, { size: 16 }) : jsxRuntimeExports.jsx(ExpandIcon, { size: 16 }) }))] })), showCloseButton && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__close-button", onClick: handleClose, disabled: disabled, title: "\uB2EB\uAE30", type: "button", children: jsxRuntimeExports.jsx(CloseIcon, { size: 20 }) }))] })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__content", children: [showNavigationButtons && images.length > 1 && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__nav-button designbase-lightbox__nav-button--prev", onClick: goToPrevious, disabled: disabled || readonly, title: "\uC774\uC804 \uC774\uBBF8\uC9C0", type: "button", children: jsxRuntimeExports.jsx(ChevronLeftIcon, { size: 24 }) }), jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__nav-button designbase-lightbox__nav-button--next", onClick: goToNext, disabled: disabled || readonly, title: "\uB2E4\uC74C \uC774\uBBF8\uC9C0", type: "button", children: jsxRuntimeExports.jsx(ChevronRightIcon, { size: 24 }) })] })), jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "designbase-lightbox__image-container", onWheel: handleWheel, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, children: [isImageError ? (jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__error", children: [jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__error-icon", children: "\u26A0\uFE0F" }), jsxRuntimeExports.jsx("p", { className: "designbase-lightbox__error-text", children: "\uC774\uBBF8\uC9C0\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." })] })) : (jsxRuntimeExports.jsx("img", { ref: imageRef, src: currentImage.src, alt: currentImage.alt || currentImage.title || `이미지 ${internalCurrentIndex + 1}`, className: clsx('designbase-lightbox__image', {
8607
+ }, className), children: jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__overlay", children: jsxRuntimeExports.jsxs("div", { ref: modalRef, className: "designbase-lightbox__modal", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__header", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__title", children: [currentImage.title && (jsxRuntimeExports.jsx("h3", { className: "designbase-lightbox__title-text", children: currentImage.title })), showCounter && (jsxRuntimeExports.jsxs("span", { className: "designbase-lightbox__counter", children: [internalCurrentIndex + 1, " / ", images.length] }))] }), jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__header-actions", children: [showToolbar && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [enableZoom && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleZoomOut, title: "\uC90C \uC544\uC6C3", type: "button", children: jsxRuntimeExports.jsx(MinusIcon$1, { size: 16 }) }), jsxRuntimeExports.jsxs("button", { className: "designbase-lightbox__toolbar-button", onClick: handleZoomReset, title: "\uC90C \uB9AC\uC14B", type: "button", children: [Math.round(zoomLevel * 100), "%"] }), jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleZoomIn, title: "\uC90C \uC778", type: "button", children: jsxRuntimeExports.jsx(PlusIcon, { size: 16 }) })] })), enableRotate && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleRotate, title: "\uD68C\uC804", type: "button", children: jsxRuntimeExports.jsx(RefreshIcon, { size: 16 }) })), enableDownload && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleDownload, title: "\uB2E4\uC6B4\uB85C\uB4DC", type: "button", children: jsxRuntimeExports.jsx(DownloadIcon, { size: 16 }) })), enableFullscreen && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__toolbar-button", onClick: handleFullscreenToggle, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsxRuntimeExports.jsx(ShrinkIcon, { size: 16 }) : jsxRuntimeExports.jsx(ExpandIcon, { size: 16 }) }))] })), showCloseButton && (jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__close-button", onClick: handleClose, title: "\uB2EB\uAE30", type: "button", children: jsxRuntimeExports.jsx(CloseIcon, { size: 20 }) }))] })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__content", children: [showNavigationButtons && images.length > 1 && (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__nav-button designbase-lightbox__nav-button--prev", onClick: goToPrevious, title: "\uC774\uC804 \uC774\uBBF8\uC9C0", type: "button", children: jsxRuntimeExports.jsx(ChevronLeftIcon, { size: 24 }) }), jsxRuntimeExports.jsx("button", { className: "designbase-lightbox__nav-button designbase-lightbox__nav-button--next", onClick: goToNext, title: "\uB2E4\uC74C \uC774\uBBF8\uC9C0", type: "button", children: jsxRuntimeExports.jsx(ChevronRightIcon, { size: 24 }) })] })), jsxRuntimeExports.jsxs("div", { ref: containerRef, className: "designbase-lightbox__image-container", onWheel: handleWheel, onMouseDown: handleMouseDown, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseUp, onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, children: [isImageError ? (jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__error", children: [jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__error-icon", children: "\u26A0\uFE0F" }), jsxRuntimeExports.jsx("p", { className: "designbase-lightbox__error-text", children: "\uC774\uBBF8\uC9C0\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." })] })) : (jsxRuntimeExports.jsx("img", { ref: imageRef, src: currentImage.src, alt: currentImage.alt || currentImage.title || `이미지 ${internalCurrentIndex + 1}`, className: clsx('designbase-lightbox__image', {
8029
8608
  'designbase-lightbox__image--loaded': isImageLoaded,
8030
8609
  'designbase-lightbox__image--dragging': isDragging,
8031
8610
  }), style: {
@@ -8033,10 +8612,10 @@ const Lightbox = ({ images, currentIndex = 0, size = 'l', variant = 'default', t
8033
8612
  cursor: zoomLevel > 1 ? (isDragging ? 'grabbing' : 'grab') : 'default',
8034
8613
  }, onLoad: handleImageLoad, onError: handleImageError })), !isImageLoaded && !isImageError && (jsxRuntimeExports.jsxs("div", { className: "designbase-lightbox__loading", children: [jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__spinner" }), jsxRuntimeExports.jsx("p", { className: "designbase-lightbox__loading-text", children: "\uC774\uBBF8\uC9C0 \uB85C\uB529 \uC911..." })] }))] }), currentImage.description && (jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__description", children: jsxRuntimeExports.jsx("p", { className: "designbase-lightbox__description-text", children: currentImage.description }) }))] }), showThumbnails && images.length > 1 && (jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__thumbnails", children: jsxRuntimeExports.jsx("div", { className: "designbase-lightbox__thumbnails-container", children: images.map((image, index) => (jsxRuntimeExports.jsx("button", { className: clsx('designbase-lightbox__thumbnail', {
8035
8614
  'designbase-lightbox__thumbnail--active': index === internalCurrentIndex,
8036
- }), onClick: () => handleImageChange(index), disabled: disabled || readonly, title: image.title || `이미지 ${index + 1}`, type: "button", children: jsxRuntimeExports.jsx("img", { src: image.thumbnail || image.src, alt: image.alt || image.title || `썸네일 ${index + 1}`, className: "designbase-lightbox__thumbnail-image" }) }, image.id))) }) }))] }) }) }));
8615
+ }), onClick: () => handleImageChange(index), title: image.title || `이미지 ${index + 1}`, type: "button", children: jsxRuntimeExports.jsx("img", { src: image.thumbnail || image.src, alt: image.alt || image.title || `썸네일 ${index + 1}`, className: "designbase-lightbox__thumbnail-image" }) }, image.id))) }) }))] }) }) }));
8037
8616
  };
8038
8617
 
8039
- const ImageList = ({ images, layout = 'grid', columns = 3, spacing = 'md', ratio = '16:9', fit = 'cover', rounded = false, shadow = false, hover = true, clickable = true, lightbox = true, onImageClick, onImageLoad, onImageError, loading = 'lazy', responsive = true, className, style, }) => {
8618
+ const ImageList = ({ images, layout = 'grid', columns = 3, spacing = 'm', ratio = '16:9', fit = 'cover', rounded = false, shadow = false, hover = true, clickable = true, lightbox = true, onImageClick, onImageLoad, onImageError, loading = 'lazy', responsive = true, className, style, }) => {
8040
8619
  const [selectedImageIndex, setSelectedImageIndex] = useState(null);
8041
8620
  // 이미지 클릭 핸들러
8042
8621
  const handleImageClick = useCallback((image, index) => {
@@ -8134,8 +8713,7 @@ const List = ({ items, size = 'm', variant = 'default', itemType = 'default', la
8134
8713
  const renderBadge = (badge) => {
8135
8714
  if (!badge)
8136
8715
  return null;
8137
- const badgeClasses = clsx('designbase-list__item-badge', `designbase-list__item-badge--color-${badge.color || 'primary'}`, `designbase-list__item-badge--variant-${badge.variant || 'solid'}`);
8138
- return (jsxRuntimeExports.jsx("span", { className: badgeClasses, children: badge.text }));
8716
+ return (jsxRuntimeExports.jsx(Badge, { variant: badge.variant || 'primary', style: badge.style || 'text', size: "s", children: badge.text }));
8139
8717
  };
8140
8718
  const renderMeta = (meta) => {
8141
8719
  if (!meta || meta.length === 0)
@@ -8548,7 +9126,7 @@ onFileUpload, onChange, onSave, onFocus, onBlur, className, }) => {
8548
9126
  }, className: "designbase-markdown-editor__textarea" }) })), (currentMode === 'preview' || currentMode === 'split') && (jsxRuntimeExports.jsx("div", { className: "designbase-markdown-editor__preview-panel", children: jsxRuntimeExports.jsx("div", { ref: previewRef, className: "designbase-markdown-editor__preview", dangerouslySetInnerHTML: { __html: markdownToHtml(internalValue) } }) })), currentMode === 'code' && (jsxRuntimeExports.jsx("div", { className: "designbase-markdown-editor__code-panel", children: jsxRuntimeExports.jsx("pre", { className: "designbase-markdown-editor__code-view", children: jsxRuntimeExports.jsx("code", { children: internalValue }) }) }))] }), renderStatus()] }));
8549
9127
  };
8550
9128
 
8551
- const Masonry = ({ images, columns = 3, spacing = 'md', ratio = 'auto', fit = 'cover', rounded = false, shadow = false, hover = true, clickable = true, lightbox = true, animation = 'fade', animationDelay = 100, onImageClick, onImageLoad, onImageError, loading = 'lazy', responsive = true, infiniteScroll = false, infiniteScrollThreshold = 100, onLoadMore, className, style, }) => {
9129
+ const Masonry = ({ images, columns = 3, spacing = 'm', ratio = 'auto', fit = 'cover', rounded = false, shadow = false, hover = true, clickable = true, lightbox = true, animation = 'fade', animationDelay = 100, onImageClick, onImageLoad, onImageError, loading = 'lazy', responsive = true, infiniteScroll = false, infiniteScrollThreshold = 100, onLoadMore, className, style, }) => {
8552
9130
  const [selectedImageIndex, setSelectedImageIndex] = useState(null);
8553
9131
  const [columnHeights, setColumnHeights] = useState([]);
8554
9132
  const [visibleImages, setVisibleImages] = useState([]);
@@ -8565,7 +9143,7 @@ const Masonry = ({ images, columns = 3, spacing = 'md', ratio = 'auto', fit = 'c
8565
9143
  const imageHeight = columnWidth * aspectRatio;
8566
9144
  // 가장 짧은 컬럼에 이미지 추가
8567
9145
  const shortestColumn = heights.indexOf(Math.min(...heights));
8568
- heights[shortestColumn] += imageHeight + (spacing === 'xs' ? 4 : spacing === 'sm' ? 8 : spacing === 'md' ? 16 : spacing === 'lg' ? 24 : 32);
9146
+ heights[shortestColumn] += imageHeight + (spacing === 'xs' ? 4 : spacing === 's' ? 8 : spacing === 'm' ? 16 : spacing === 'l' ? 24 : 32);
8569
9147
  }
8570
9148
  });
8571
9149
  setColumnHeights(heights);
@@ -8726,10 +9304,12 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
8726
9304
  };
8727
9305
  SearchBar.displayName = 'SearchBar';
8728
9306
 
8729
- const Navbar = ({ size = 'm', variant = 'default', position = 'static', logo, onLogoClick, items = [], onItemClick, userMenuItems = [], onUserMenuItemClick, userProfile, onAuthClick, isAuthenticated = false, showSearch = false, onSearch, searchPlaceholder = '검색...', fullWidth = false, shadow = false, className, ...props }) => {
9307
+ const Navbar = ({ size = 'm', variant = 'default', position = 'static', logo, onLogoClick, items = [], onItemClick, userMenuItems = [], onUserMenuItemClick, userProfile, onLoginClick, onLogoutClick, isAuthenticated = false, showSearch = false, onSearch, searchPlaceholder = '검색...', fullWidth = false, shadow = false, className, ...props }) => {
8730
9308
  const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
8731
9309
  const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
8732
9310
  const [searchQuery, setSearchQuery] = useState('');
9311
+ const [expandedItems, setExpandedItems] = useState({});
9312
+ const navbarRef = useRef(null);
8733
9313
  // 아이콘 크기 계산 (m이 기본값)
8734
9314
  const iconSize = size === 's' ? 16 : size === 'l' ? 24 : 20;
8735
9315
  const handleMobileMenuToggle = () => {
@@ -8738,14 +9318,26 @@ const Navbar = ({ size = 'm', variant = 'default', position = 'static', logo, on
8738
9318
  const handleUserMenuToggle = () => {
8739
9319
  setIsUserMenuOpen(!isUserMenuOpen);
8740
9320
  };
8741
- const handleItemClick = (item) => {
9321
+ const handleItemClick = (item, isMobile = false) => {
8742
9322
  if (item.disabled)
8743
9323
  return;
9324
+ // 모바일/태블릿에서만 하위 메뉴 확장/축소 토글
9325
+ if (isMobile && item.children && item.children.length > 0) {
9326
+ setExpandedItems(prev => ({
9327
+ ...prev,
9328
+ [item.id]: !prev[item.id]
9329
+ }));
9330
+ // 하위 메뉴가 있는 경우 메뉴를 닫지 않음
9331
+ return;
9332
+ }
9333
+ if (item.onClick) {
9334
+ item.onClick(item);
9335
+ }
8744
9336
  if (onItemClick) {
8745
9337
  onItemClick(item);
8746
9338
  }
8747
- // 모바일 메뉴 자동 닫기
8748
- if (isMobileMenuOpen) {
9339
+ // 모바일 메뉴 자동 닫기 (하위 메뉴가 없는 경우에만)
9340
+ if (isMobileMenuOpen && (!item.children || item.children.length === 0)) {
8749
9341
  setIsMobileMenuOpen(false);
8750
9342
  }
8751
9343
  };
@@ -8757,107 +9349,103 @@ const Navbar = ({ size = 'm', variant = 'default', position = 'static', logo, on
8757
9349
  }
8758
9350
  setIsUserMenuOpen(false);
8759
9351
  };
9352
+ const handleSubItemClick = (item) => {
9353
+ if (item.disabled)
9354
+ return;
9355
+ if (item.onClick) {
9356
+ item.onClick(item);
9357
+ }
9358
+ if (onItemClick) {
9359
+ onItemClick(item);
9360
+ }
9361
+ // 하위 메뉴 아이템 클릭 시 모바일 메뉴 닫기
9362
+ if (isMobileMenuOpen) {
9363
+ setIsMobileMenuOpen(false);
9364
+ }
9365
+ };
9366
+ // 외부 클릭 시 메뉴 닫기
9367
+ useEffect(() => {
9368
+ const handleClickOutside = (event) => {
9369
+ if (navbarRef.current && !navbarRef.current.contains(event.target)) {
9370
+ setIsUserMenuOpen(false);
9371
+ }
9372
+ };
9373
+ document.addEventListener('mousedown', handleClickOutside);
9374
+ return () => {
9375
+ document.removeEventListener('mousedown', handleClickOutside);
9376
+ };
9377
+ }, []);
8760
9378
  const classes = clsx('designbase-navbar', `designbase-navbar--${size}`, `designbase-navbar--${variant}`, `designbase-navbar--${position}`, {
8761
9379
  'designbase-navbar--full-width': fullWidth,
8762
9380
  'designbase-navbar--shadow': shadow,
8763
- 'designbase-navbar--mobile-open': isMobileMenuOpen,
8764
9381
  }, className);
8765
9382
  const containerClasses = clsx('designbase-navbar__container', {
8766
9383
  'designbase-navbar__container--full-width': fullWidth,
8767
9384
  });
8768
- // 재귀적으로 메뉴 아이템을 렌더링하는 함수
8769
- const renderMenuItem = (item, depth = 0) => {
9385
+ // 메뉴 아이템을 렌더링하는 함수
9386
+ const renderMenuItem = (item, isMobile = false) => {
8770
9387
  const hasChildren = item.children && item.children.length > 0;
8771
- const isFirstLevel = depth === 0;
8772
- const isSecondLevel = depth === 1;
8773
- if (isFirstLevel) {
8774
- // 1depth - 메인 네비게이션
8775
- return (jsxRuntimeExports.jsx("li", { className: "designbase-navbar__nav-item", children: hasChildren ? (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__dropdown", children: [jsxRuntimeExports.jsxs("button", { className: clsx('designbase-navbar__nav-link', 'designbase-navbar__nav-link--has-children', {
8776
- 'designbase-navbar__nav-link--active': item.active,
8777
- 'designbase-navbar__nav-link--disabled': item.disabled,
8778
- }), onClick: () => handleItemClick(item), disabled: item.disabled, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label, jsxRuntimeExports.jsx(ChevronDownIcon, { size: 12, className: "designbase-navbar__chevron" })] }), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__dropdown-menu", children: item.children.map((child) => renderMenuItem(child, 1)) })] })) : (jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__nav-link', {
8779
- 'designbase-navbar__nav-link--active': item.active,
8780
- 'designbase-navbar__nav-link--disabled': item.disabled,
8781
- }), onClick: (e) => {
8782
- if (item.disabled) {
8783
- e.preventDefault();
8784
- return;
8785
- }
8786
- handleItemClick(item);
8787
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] })) }, item.id));
8788
- }
8789
- else if (isSecondLevel) {
8790
- // 2depth - 드롭다운 메뉴
8791
- return (jsxRuntimeExports.jsx("li", { children: hasChildren ? (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__dropdown-item designbase-navbar__dropdown-item--has-children", children: [jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__dropdown-item', {
8792
- 'designbase-navbar__dropdown-item--active': item.active,
8793
- 'designbase-navbar__dropdown-item--disabled': item.disabled,
8794
- }), onClick: (e) => {
8795
- if (item.disabled) {
8796
- e.preventDefault();
8797
- return;
8798
- }
8799
- handleItemClick(item);
8800
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label, jsxRuntimeExports.jsx(ChevronRightIcon, { size: 12, className: "designbase-navbar__chevron" })] }), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__submenu", children: item.children.map((child) => renderMenuItem(child, 2)) })] })) : (jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__dropdown-item', {
8801
- 'designbase-navbar__dropdown-item--active': item.active,
8802
- 'designbase-navbar__dropdown-item--disabled': item.disabled,
8803
- }), onClick: (e) => {
8804
- if (item.disabled) {
8805
- e.preventDefault();
8806
- return;
8807
- }
8808
- handleItemClick(item);
8809
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] })) }, item.id));
8810
- }
8811
- else {
8812
- // 3depth 이상 - 서브메뉴
8813
- return (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__dropdown-item', {
8814
- 'designbase-navbar__dropdown-item--active': item.active,
8815
- 'designbase-navbar__dropdown-item--disabled': item.disabled,
8816
- }), onClick: (e) => {
8817
- if (item.disabled) {
8818
- e.preventDefault();
8819
- return;
8820
- }
8821
- handleItemClick(item);
8822
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] }) }, item.id));
8823
- }
8824
- };
8825
- const renderNavItem = (item) => renderMenuItem(item, 0);
8826
- return (jsxRuntimeExports.jsxs("nav", { className: classes, role: "navigation", "aria-label": "\uBA54\uC778 \uB124\uBE44\uAC8C\uC774\uC158", ...props, children: [jsxRuntimeExports.jsxs("div", { className: containerClasses, children: [jsxRuntimeExports.jsx("div", { className: "designbase-navbar__brand", children: jsxRuntimeExports.jsx("div", { className: "designbase-navbar__logo", onClick: onLogoClick, role: onLogoClick ? 'button' : undefined, tabIndex: onLogoClick ? 0 : undefined, children: logo || jsxRuntimeExports.jsx(Logo, { size: size === 's' ? 's' : size === 'l' ? 'l' : 'm' }) }) }), jsxRuntimeExports.jsx("div", { className: "designbase-navbar__nav", children: jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__nav-list", children: items.map(renderNavItem) }) }), showSearch && (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__search", children: jsxRuntimeExports.jsx(SearchBar, { placeholder: searchPlaceholder, value: searchQuery, onChange: setSearchQuery, onSearch: onSearch, size: size === 's' ? 's' : size === 'l' ? 'l' : 'm', variant: "outlined" }) })), jsxRuntimeExports.jsx("div", { className: "designbase-navbar__user-menu", children: isAuthenticated && userProfile ? (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__user-dropdown", children: [jsxRuntimeExports.jsxs("button", { className: "designbase-navbar__user-toggle", onClick: handleUserMenuToggle, "aria-expanded": isUserMenuOpen, children: [jsxRuntimeExports.jsx(Avatar, { src: userProfile.avatar, alt: userProfile.name, initials: userProfile.name, size: size === 's' ? 's' : size === 'l' ? 'l' : 'm', badge: userProfile.badge, badgeVariant: userProfile.badgeVariant, badgeStyle: userProfile.badgeStyle, badgeText: userProfile.badgeText }), jsxRuntimeExports.jsx("span", { className: "designbase-navbar__user-name", children: userProfile.name }), jsxRuntimeExports.jsx(ChevronDownIcon, { size: 12 })] }), isUserMenuOpen && (jsxRuntimeExports.jsxs("ul", { className: "designbase-navbar__user-dropdown-menu", children: [userProfile.email && (jsxRuntimeExports.jsx("li", { className: "designbase-navbar__user-info", children: jsxRuntimeExports.jsx("span", { className: "designbase-navbar__user-email", children: userProfile.email }) })), userMenuItems.map((item) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__user-dropdown-item', {
8827
- 'designbase-navbar__user-dropdown-item--disabled': item.disabled,
8828
- }), onClick: (e) => {
8829
- if (item.disabled) {
8830
- e.preventDefault();
8831
- return;
8832
- }
8833
- handleUserMenuItemClick(item);
8834
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] }) }, item.id)))] }))] })) : (jsxRuntimeExports.jsx("button", { className: "designbase-navbar__auth-button", onClick: onAuthClick, children: "\uB85C\uADF8\uC778" })) }), jsxRuntimeExports.jsxs("button", { className: "designbase-navbar__mobile-toggle", onClick: handleMobileMenuToggle, "aria-expanded": isMobileMenuOpen, "aria-label": "\uBA54\uB274 \uC5F4\uAE30", children: [jsxRuntimeExports.jsx("span", { className: "designbase-navbar__mobile-toggle-line" }), jsxRuntimeExports.jsx("span", { className: "designbase-navbar__mobile-toggle-line" }), jsxRuntimeExports.jsx("span", { className: "designbase-navbar__mobile-toggle-line" })] })] }), isMobileMenuOpen && (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__mobile-menu", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__mobile-menu-header", children: [jsxRuntimeExports.jsx("h3", { children: "\uBA54\uB274" }), jsxRuntimeExports.jsx("button", { className: "designbase-navbar__mobile-menu-close", onClick: handleMobileMenuToggle, "aria-label": "\uBA54\uB274 \uB2EB\uAE30", children: jsxRuntimeExports.jsx("i", { className: "designbase-icon-x" }) })] }), showSearch && (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-search", children: jsxRuntimeExports.jsx(SearchBar, { placeholder: searchPlaceholder, value: searchQuery, onChange: setSearchQuery, onSearch: onSearch, size: "s", variant: "outlined", fullWidth: true }) })), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__mobile-nav-list", children: items.map((item) => (jsxRuntimeExports.jsxs("li", { className: "designbase-navbar__mobile-nav-item", children: [jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__mobile-nav-link', {
8835
- 'designbase-navbar__mobile-nav-link--active': item.active,
8836
- 'designbase-navbar__mobile-nav-link--disabled': item.disabled,
8837
- }), onClick: (e) => {
8838
- if (item.disabled) {
8839
- e.preventDefault();
8840
- return;
8841
- }
8842
- handleItemClick(item);
8843
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] }), item.children && item.children.length > 0 && (jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__mobile-submenu", children: item.children.map((child) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: child.href, className: clsx('designbase-navbar__mobile-submenu-link', {
8844
- 'designbase-navbar__mobile-submenu-link--active': child.active,
8845
- 'designbase-navbar__mobile-submenu-link--disabled': child.disabled,
9388
+ const isExpanded = expandedItems[item.id];
9389
+ if (isMobile) {
9390
+ // 모바일에서는 MenuItem 컴포넌트 사용
9391
+ return (jsxRuntimeExports.jsx(MenuItem, { id: item.id, label: item.label, href: item.href, icon: item.icon, active: item.active, disabled: item.disabled, badge: item.badge, badgeColor: item.badgeColor, variant: item.variant, type: "block", size: size, style: "accordion", subItems: item.children?.map(child => ({
9392
+ id: child.id,
9393
+ label: child.label,
9394
+ href: child.href,
9395
+ icon: child.icon,
9396
+ active: child.active,
9397
+ disabled: child.disabled,
9398
+ badge: child.badge,
9399
+ badgeColor: child.badgeColor,
9400
+ variant: child.variant,
9401
+ onClick: () => handleSubItemClick(child),
9402
+ })), expandable: hasChildren, expanded: isExpanded, onClick: () => handleItemClick(item, true) }, item.id));
9403
+ }
9404
+ // 데스크톱에서는 기존 방식 유지
9405
+ return (jsxRuntimeExports.jsx("li", { className: "designbase-navbar__nav-item", children: hasChildren ? (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__dropdown", children: [jsxRuntimeExports.jsxs("button", { className: clsx('designbase-navbar__nav-link', 'designbase-navbar__nav-link--has-children', {
9406
+ 'designbase-navbar__nav-link--active': item.active,
9407
+ 'designbase-navbar__nav-link--disabled': item.disabled,
9408
+ }), onClick: () => handleItemClick(item, false), disabled: item.disabled, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label, jsxRuntimeExports.jsx(ChevronDownIcon, { size: 12, className: clsx('designbase-navbar__chevron', {
9409
+ 'designbase-navbar__chevron--expanded': isExpanded,
9410
+ }) })] }), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__dropdown-menu", children: item.children.map((child) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: child.href, className: clsx('designbase-navbar__dropdown-item', {
9411
+ 'designbase-navbar__dropdown-item--active': child.active,
9412
+ 'designbase-navbar__dropdown-item--disabled': child.disabled,
9413
+ }), onClick: (e) => {
9414
+ if (child.disabled) {
9415
+ e.preventDefault();
9416
+ return;
9417
+ }
9418
+ handleItemClick(child, false);
9419
+ }, children: [child.icon && React.createElement(child.icon, { size: iconSize, color: 'currentColor' }), child.label] }) }, child.id))) })] })) : (jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__nav-link', {
9420
+ 'designbase-navbar__nav-link--active': item.active,
9421
+ 'designbase-navbar__nav-link--disabled': item.disabled,
9422
+ }), onClick: (e) => {
9423
+ if (item.disabled) {
9424
+ e.preventDefault();
9425
+ return;
9426
+ }
9427
+ handleItemClick(item, false);
9428
+ }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] })) }, item.id));
9429
+ };
9430
+ return (jsxRuntimeExports.jsxs("nav", { ref: navbarRef, className: clsx(classes, {
9431
+ 'designbase-navbar--mobile-open': isMobileMenuOpen,
9432
+ }), role: "navigation", "aria-label": "\uBA54\uC778 \uB124\uBE44\uAC8C\uC774\uC158", ...props, children: [jsxRuntimeExports.jsxs("div", { className: containerClasses, children: [jsxRuntimeExports.jsx("div", { className: "designbase-navbar__left", children: jsxRuntimeExports.jsx("div", { className: "designbase-navbar__brand", children: jsxRuntimeExports.jsx("div", { className: "designbase-navbar__logo", onClick: onLogoClick, role: onLogoClick ? 'button' : undefined, tabIndex: onLogoClick ? 0 : undefined, children: logo || jsxRuntimeExports.jsx(Logo, { size: "s" }) }) }) }), jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__right", children: [jsxRuntimeExports.jsx("div", { className: "designbase-navbar__nav designbase-navbar__nav--desktop", children: jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__nav-list", children: items.map(item => renderMenuItem(item, false)) }) }), showSearch && (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__search", children: jsxRuntimeExports.jsx(SearchBar, { placeholder: searchPlaceholder, value: searchQuery, onChange: setSearchQuery, onSearch: onSearch, size: size === 's' ? 's' : size === 'l' ? 'l' : 'm', variant: "outlined" }) })), jsxRuntimeExports.jsx("div", { className: "designbase-navbar__user-menu", children: isAuthenticated && userProfile ? (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__user-dropdown", children: [jsxRuntimeExports.jsxs("button", { className: "designbase-navbar__user-toggle", onClick: handleUserMenuToggle, "aria-expanded": isUserMenuOpen, children: [jsxRuntimeExports.jsx(Avatar, { src: userProfile.avatar, alt: userProfile.name, initials: userProfile.name, size: size === 's' ? 's' : size === 'l' ? 'l' : 'm', badge: userProfile.badge, badgeVariant: userProfile.badgeVariant, badgeStyle: userProfile.badgeStyle, badgeText: userProfile.badgeText }), jsxRuntimeExports.jsx("span", { className: "designbase-navbar__user-name", children: userProfile.name }), jsxRuntimeExports.jsx(ChevronDownIcon, { size: 12 })] }), isUserMenuOpen && (jsxRuntimeExports.jsxs("ul", { className: "designbase-navbar__user-dropdown-menu", children: [userProfile.email && (jsxRuntimeExports.jsx("li", { className: "designbase-navbar__user-info", children: jsxRuntimeExports.jsx("span", { className: "designbase-navbar__user-email", children: userProfile.email }) })), userMenuItems.map((item) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__user-dropdown-item', {
9433
+ 'designbase-navbar__user-dropdown-item--disabled': item.disabled,
9434
+ }), onClick: (e) => {
9435
+ if (item.disabled) {
9436
+ e.preventDefault();
9437
+ return;
9438
+ }
9439
+ handleUserMenuItemClick(item);
9440
+ }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] }) }, item.id)))] }))] })) : (jsxRuntimeExports.jsx(Button, { variant: "primary", size: size === 's' ? 's' : size === 'l' ? 'l' : 'm', onClick: onLoginClick, children: "\uB85C\uADF8\uC778" })) })] }), jsxRuntimeExports.jsxs("button", { className: "designbase-navbar__mobile-toggle", onClick: handleMobileMenuToggle, "aria-expanded": isMobileMenuOpen, "aria-label": "\uBA54\uB274 \uC5F4\uAE30", children: [jsxRuntimeExports.jsx("span", { className: "designbase-navbar__mobile-toggle-line" }), jsxRuntimeExports.jsx("span", { className: "designbase-navbar__mobile-toggle-line" }), jsxRuntimeExports.jsx("span", { className: "designbase-navbar__mobile-toggle-line" })] })] }), isMobileMenuOpen && (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__mobile-menu", children: [showSearch && (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-search", children: jsxRuntimeExports.jsx(SearchBar, { placeholder: searchPlaceholder, value: searchQuery, onChange: setSearchQuery, onSearch: onSearch, size: "s", variant: "outlined", fullWidth: true }) })), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__mobile-nav-list", children: items.map((item) => renderMenuItem(item, true)) }), jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user", children: isAuthenticated && userProfile ? (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__mobile-user-info", children: [userProfile.avatar ? (jsxRuntimeExports.jsx("img", { src: userProfile.avatar, alt: userProfile.name, className: "designbase-navbar__mobile-user-avatar" })) : (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user-avatar-placeholder", children: userProfile.name.charAt(0).toUpperCase() })), jsxRuntimeExports.jsxs("div", { children: [jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user-name", children: userProfile.name }), userProfile.email && (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user-email", children: userProfile.email }))] })] }), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__mobile-user-menu", children: userMenuItems.map((item) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__mobile-user-menu-link', {
9441
+ 'designbase-navbar__mobile-user-menu-link--disabled': item.disabled,
8846
9442
  }), onClick: (e) => {
8847
- if (child.disabled) {
9443
+ if (item.disabled) {
8848
9444
  e.preventDefault();
8849
9445
  return;
8850
9446
  }
8851
- handleItemClick(child);
8852
- }, children: [child.icon && React.createElement(child.icon, { size: iconSize, color: 'currentColor' }), child.label] }) }, child.id))) }))] }, item.id))) }), isAuthenticated && userProfile && (jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__mobile-user", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-navbar__mobile-user-info", children: [userProfile.avatar ? (jsxRuntimeExports.jsx("img", { src: userProfile.avatar, alt: userProfile.name, className: "designbase-navbar__mobile-user-avatar" })) : (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user-avatar-placeholder", children: userProfile.name.charAt(0).toUpperCase() })), jsxRuntimeExports.jsxs("div", { children: [jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user-name", children: userProfile.name }), userProfile.email && (jsxRuntimeExports.jsx("div", { className: "designbase-navbar__mobile-user-email", children: userProfile.email }))] })] }), jsxRuntimeExports.jsx("ul", { className: "designbase-navbar__mobile-user-menu", children: userMenuItems.map((item) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-navbar__mobile-user-menu-link', {
8853
- 'designbase-navbar__mobile-user-menu-link--disabled': item.disabled,
8854
- }), onClick: (e) => {
8855
- if (item.disabled) {
8856
- e.preventDefault();
8857
- return;
8858
- }
8859
- handleUserMenuItemClick(item);
8860
- }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] }) }, item.id))) })] }))] }))] }));
9447
+ handleUserMenuItemClick(item);
9448
+ }, children: [item.icon && React.createElement(item.icon, { size: iconSize, color: 'currentColor' }), item.label] }) }, item.id))) })] })) : (jsxRuntimeExports.jsx(Button, { variant: "primary", size: "m", onClick: onLoginClick, fullWidth: true, children: "\uB85C\uADF8\uC778" })) })] }))] }));
8861
9449
  };
8862
9450
  Navbar.displayName = 'Navbar';
8863
9451
 
@@ -8939,302 +9527,269 @@ const Pagination = ({ currentPage, totalPages, totalItems, pageSize = 10, pageSi
8939
9527
  };
8940
9528
  Pagination.displayName = 'Pagination';
8941
9529
 
8942
- const Popover = ({ content, children, position = 'top', size = 'm', variant = 'default', trigger = 'click', delay = 200, hideDelay = 0, alwaysShow = false, disabled = false, maxWidth = 300, showArrow = true, closeOnOutsideClick = true, closeOnEscape = true, open: controlledOpen, onOpenChange, showCloseButton = true, className, ...props }) => {
9530
+ const GAP$1 = 8; // trigger popover 사이 간격
9531
+ const Popover = ({ content, children, title, position = 'top', size = 'm', variant = 'default', trigger = 'click', delay = 0, // 클릭/포커스는 즉시, 호버는 아래에서만 적용
9532
+ hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 300, showArrow = true, closeOnOutsideClick = true, closeOnEscape = true, open: controlledOpen, onOpenChange, className, ...props }) => {
8943
9533
  const [internalOpen, setInternalOpen] = useState(false);
8944
- const [popoverStyle, setPopoverStyle] = useState({});
8945
- const [arrowStyle, setArrowStyle] = useState({});
9534
+ const isControlled = controlledOpen !== undefined;
9535
+ const isOpen = isControlled ? !!controlledOpen : internalOpen;
8946
9536
  const triggerRef = useRef(null);
8947
9537
  const popoverRef = useRef(null);
8948
- const timeoutRef = useRef();
8949
- // 제어/비제어 컴포넌트 처리
8950
- const isControlled = controlledOpen !== undefined;
8951
- const isOpen = isControlled ? controlledOpen : internalOpen;
8952
- // 팝오버 위치 계산
9538
+ const [style, setStyle] = useState({});
9539
+ const [arrowStyle, setArrowStyle] = useState({});
9540
+ const [placementGroup, setPlacementGroup] = useState('top'); // 애니메이션 방향용
9541
+ const rafId = useRef(null);
9542
+ const timers = useRef({});
9543
+ // ----- 위치 계산 ----------------------------------------------------------
8953
9544
  const calculatePosition = useCallback(() => {
8954
9545
  if (!triggerRef.current || !popoverRef.current)
8955
9546
  return;
8956
- const triggerRect = triggerRef.current.getBoundingClientRect();
8957
- const popoverRect = popoverRef.current.getBoundingClientRect();
8958
- const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
8959
- const scrollY = window.pageYOffset || document.documentElement.scrollTop;
8960
- let top = 0;
8961
- let left = 0;
8962
- let arrowTop = 0;
8963
- let arrowLeft = 0;
8964
- const isVertical = position.startsWith('top') || position.startsWith('bottom');
8965
- const isHorizontal = position.startsWith('left') || position.startsWith('right');
9547
+ const tRect = triggerRef.current.getBoundingClientRect();
9548
+ const pRect = popoverRef.current.getBoundingClientRect();
9549
+ // fixed 기준 스크롤값 불필요
9550
+ let top = 0, left = 0;
9551
+ let aTop = 0, aLeft = 0;
9552
+ const setGroup = (p) => {
9553
+ if (p.startsWith('top'))
9554
+ return 'top';
9555
+ if (p.startsWith('bottom'))
9556
+ return 'bottom';
9557
+ if (p.startsWith('left'))
9558
+ return 'left';
9559
+ return 'right';
9560
+ };
8966
9561
  switch (position) {
8967
9562
  case 'top':
8968
- top = triggerRect.top + scrollY - popoverRect.height - 8;
8969
- left = triggerRect.left + scrollX + (triggerRect.width / 2) - (popoverRect.width / 2);
8970
- arrowTop = popoverRect.height;
8971
- arrowLeft = popoverRect.width / 2 - 4;
9563
+ top = tRect.top - pRect.height - GAP$1;
9564
+ left = tRect.left + tRect.width / 2 - pRect.width / 2;
9565
+ aTop = pRect.height;
9566
+ aLeft = pRect.width / 2 - 4;
8972
9567
  break;
8973
9568
  case 'top-start':
8974
- top = triggerRect.top + scrollY - popoverRect.height - 8;
8975
- left = triggerRect.left + scrollX;
8976
- arrowTop = popoverRect.height;
8977
- arrowLeft = 12;
9569
+ top = tRect.top - pRect.height - GAP$1;
9570
+ left = tRect.left;
9571
+ aTop = pRect.height;
9572
+ aLeft = 12;
8978
9573
  break;
8979
9574
  case 'top-end':
8980
- top = triggerRect.top + scrollY - popoverRect.height - 8;
8981
- left = triggerRect.right + scrollX - popoverRect.width;
8982
- arrowTop = popoverRect.height;
8983
- arrowLeft = popoverRect.width - 12;
9575
+ top = tRect.top - pRect.height - GAP$1;
9576
+ left = tRect.right - pRect.width;
9577
+ aTop = pRect.height;
9578
+ aLeft = pRect.width - 12;
8984
9579
  break;
8985
9580
  case 'bottom':
8986
- top = triggerRect.bottom + scrollY + 8;
8987
- left = triggerRect.left + scrollX + (triggerRect.width / 2) - (popoverRect.width / 2);
8988
- arrowTop = -4;
8989
- arrowLeft = popoverRect.width / 2 - 4;
9581
+ top = tRect.bottom + GAP$1;
9582
+ left = tRect.left + tRect.width / 2 - pRect.width / 2;
9583
+ aTop = -4;
9584
+ aLeft = pRect.width / 2 - 4;
8990
9585
  break;
8991
9586
  case 'bottom-start':
8992
- top = triggerRect.bottom + scrollY + 8;
8993
- left = triggerRect.left + scrollX;
8994
- arrowTop = -4;
8995
- arrowLeft = 12;
9587
+ top = tRect.bottom + GAP$1;
9588
+ left = tRect.left;
9589
+ aTop = -4;
9590
+ aLeft = 12;
8996
9591
  break;
8997
9592
  case 'bottom-end':
8998
- top = triggerRect.bottom + scrollY + 8;
8999
- left = triggerRect.right + scrollX - popoverRect.width;
9000
- arrowTop = -4;
9001
- arrowLeft = popoverRect.width - 12;
9593
+ top = tRect.bottom + GAP$1;
9594
+ left = tRect.right - pRect.width;
9595
+ aTop = -4;
9596
+ aLeft = pRect.width - 12;
9002
9597
  break;
9003
9598
  case 'left':
9004
- top = triggerRect.top + scrollY + (triggerRect.height / 2) - (popoverRect.height / 2);
9005
- left = triggerRect.left + scrollX - popoverRect.width - 8;
9006
- arrowTop = popoverRect.height / 2 - 4;
9007
- arrowLeft = popoverRect.width;
9599
+ top = tRect.top + tRect.height / 2 - pRect.height / 2;
9600
+ left = tRect.left - pRect.width - GAP$1;
9601
+ aTop = pRect.height / 2 - 4;
9602
+ aLeft = pRect.width;
9008
9603
  break;
9009
9604
  case 'left-start':
9010
- top = triggerRect.top + scrollY;
9011
- left = triggerRect.left + scrollX - popoverRect.width - 8;
9012
- arrowTop = 12;
9013
- arrowLeft = popoverRect.width;
9605
+ top = tRect.top;
9606
+ left = tRect.left - pRect.width - GAP$1;
9607
+ aTop = 12;
9608
+ aLeft = pRect.width;
9014
9609
  break;
9015
9610
  case 'left-end':
9016
- top = triggerRect.bottom + scrollY - popoverRect.height;
9017
- left = triggerRect.left + scrollX - popoverRect.width - 8;
9018
- arrowTop = popoverRect.height - 12;
9019
- arrowLeft = popoverRect.width;
9611
+ top = tRect.bottom - pRect.height;
9612
+ left = tRect.left - pRect.width - GAP$1;
9613
+ aTop = pRect.height - 12;
9614
+ aLeft = pRect.width;
9020
9615
  break;
9021
9616
  case 'right':
9022
- top = triggerRect.top + scrollY + (triggerRect.height / 2) - (popoverRect.height / 2);
9023
- left = triggerRect.right + scrollX + 8;
9024
- arrowTop = popoverRect.height / 2 - 4;
9025
- arrowLeft = -4;
9617
+ top = tRect.top + tRect.height / 2 - pRect.height / 2;
9618
+ left = tRect.right + GAP$1;
9619
+ aTop = pRect.height / 2 - 4;
9620
+ aLeft = -4;
9026
9621
  break;
9027
9622
  case 'right-start':
9028
- top = triggerRect.top + scrollY;
9029
- left = triggerRect.right + scrollX + 8;
9030
- arrowTop = 12;
9031
- arrowLeft = -4;
9623
+ top = tRect.top;
9624
+ left = tRect.right + GAP$1;
9625
+ aTop = 12;
9626
+ aLeft = -4;
9032
9627
  break;
9033
9628
  case 'right-end':
9034
- top = triggerRect.bottom + scrollY - popoverRect.height;
9035
- left = triggerRect.right + scrollX + 8;
9036
- arrowTop = popoverRect.height - 12;
9037
- arrowLeft = -4;
9629
+ top = tRect.bottom - pRect.height;
9630
+ left = tRect.right + GAP$1;
9631
+ aTop = pRect.height - 12;
9632
+ aLeft = -4;
9038
9633
  break;
9039
9634
  }
9040
- // 화면 경계 체크 및 조정
9041
- const viewportWidth = window.innerWidth;
9042
- const viewportHeight = window.innerHeight;
9043
- // 수평 경계 체크
9044
- if (left < 8) {
9635
+ // viewport 충돌 보정(살짝 shift)
9636
+ const vw = window.innerWidth;
9637
+ const vh = window.innerHeight;
9638
+ if (left < 8)
9045
9639
  left = 8;
9046
- if (isHorizontal) {
9047
- arrowLeft = Math.max(arrowLeft, 4);
9048
- }
9049
- }
9050
- else if (left + popoverRect.width > viewportWidth - 8) {
9051
- left = viewportWidth - popoverRect.width - 8;
9052
- if (isHorizontal) {
9053
- arrowLeft = Math.min(arrowLeft, popoverRect.width - 4);
9054
- }
9055
- }
9056
- // 수직 경계 체크
9057
- if (top < 8) {
9640
+ if (left + pRect.width > vw - 8)
9641
+ left = vw - pRect.width - 8;
9642
+ if (top < 8)
9058
9643
  top = 8;
9059
- if (isVertical) {
9060
- arrowTop = Math.max(arrowTop, 4);
9061
- }
9062
- }
9063
- else if (top + popoverRect.height > viewportHeight - 8) {
9064
- top = viewportHeight - popoverRect.height - 8;
9065
- if (isVertical) {
9066
- arrowTop = Math.min(arrowTop, popoverRect.height - 4);
9067
- }
9068
- }
9069
- setPopoverStyle({
9644
+ if (top + pRect.height > vh - 8)
9645
+ top = vh - pRect.height - 8;
9646
+ setStyle({
9070
9647
  position: 'fixed',
9071
- top: `${top}px`,
9072
- left: `${left}px`,
9073
- maxWidth: `${maxWidth}px`,
9648
+ top,
9649
+ left,
9650
+ maxWidth,
9074
9651
  zIndex: 9999,
9075
9652
  });
9076
- setArrowStyle({
9077
- position: 'absolute',
9078
- top: `${arrowTop}px`,
9079
- left: `${arrowLeft}px`,
9080
- });
9653
+ setArrowStyle({ position: 'absolute', top: aTop, left: aLeft });
9654
+ setPlacementGroup(setGroup(position));
9081
9655
  }, [position, maxWidth]);
9082
- // 팝오버 열기
9083
- const openPopover = useCallback(() => {
9084
- if (disabled || alwaysShow)
9085
- return;
9086
- if (timeoutRef.current) {
9087
- clearTimeout(timeoutRef.current);
9656
+ // 페인트 전에 위치를 확정 (깜빡임/점프 제거)
9657
+ useLayoutEffect(() => {
9658
+ if (isOpen) {
9659
+ calculatePosition();
9088
9660
  }
9089
- timeoutRef.current = setTimeout(() => {
9090
- const newOpen = true;
9091
- if (!isControlled) {
9092
- setInternalOpen(newOpen);
9093
- }
9094
- onOpenChange?.(newOpen);
9095
- // 위치 계산을 위해 다음 프레임에서 실행
9096
- requestAnimationFrame(() => {
9661
+ }, [isOpen, calculatePosition]);
9662
+ // 스크롤/리사이즈는 rAF에 태워 스로틀
9663
+ useEffect(() => {
9664
+ if (!isOpen)
9665
+ return;
9666
+ const onMove = () => {
9667
+ if (rafId.current != null)
9668
+ cancelAnimationFrame(rafId.current);
9669
+ rafId.current = requestAnimationFrame(() => {
9097
9670
  calculatePosition();
9671
+ rafId.current = null;
9098
9672
  });
9099
- }, trigger === 'hover' ? delay : 0);
9100
- }, [disabled, alwaysShow, isControlled, onOpenChange, calculatePosition, trigger, delay]);
9101
- // 팝오버 닫기
9673
+ };
9674
+ window.addEventListener('scroll', onMove, { passive: true, capture: true });
9675
+ window.addEventListener('resize', onMove, { passive: true });
9676
+ // 컨텐츠/트리거 크기 변화 추적
9677
+ const ResizeObserver = window.ResizeObserver;
9678
+ const ro = ResizeObserver ? new ResizeObserver(() => onMove()) : null;
9679
+ if (ro) {
9680
+ if (triggerRef.current)
9681
+ ro.observe(triggerRef.current);
9682
+ if (popoverRef.current)
9683
+ ro.observe(popoverRef.current);
9684
+ }
9685
+ return () => {
9686
+ window.removeEventListener('scroll', onMove, true);
9687
+ window.removeEventListener('resize', onMove);
9688
+ if (rafId.current != null)
9689
+ cancelAnimationFrame(rafId.current);
9690
+ ro?.disconnect?.();
9691
+ };
9692
+ }, [isOpen, calculatePosition]);
9693
+ // open/close helpers
9694
+ const setOpen = useCallback((next) => {
9695
+ if (!isControlled)
9696
+ setInternalOpen(next);
9697
+ onOpenChange?.(next);
9698
+ }, [isControlled, onOpenChange]);
9699
+ const openPopover = useCallback(() => {
9700
+ if (disabled || alwaysShow)
9701
+ return;
9702
+ if (timers.current.t)
9703
+ clearTimeout(timers.current.t);
9704
+ const d = trigger === 'hover' ? Math.max(0, delay) : 0;
9705
+ timers.current.t = setTimeout(() => {
9706
+ setOpen(true);
9707
+ }, d);
9708
+ }, [trigger, delay, disabled, alwaysShow, setOpen]);
9102
9709
  const closePopover = useCallback(() => {
9103
9710
  if (disabled || alwaysShow)
9104
9711
  return;
9105
- if (timeoutRef.current) {
9106
- clearTimeout(timeoutRef.current);
9107
- }
9108
- timeoutRef.current = setTimeout(() => {
9109
- const newOpen = false;
9110
- if (!isControlled) {
9111
- setInternalOpen(newOpen);
9112
- }
9113
- onOpenChange?.(newOpen);
9114
- }, trigger === 'hover' ? hideDelay : 0);
9115
- }, [disabled, alwaysShow, isControlled, onOpenChange, trigger, hideDelay]);
9116
- // 이벤트 핸들러
9712
+ if (timers.current.t)
9713
+ clearTimeout(timers.current.t);
9714
+ const d = trigger === 'hover' ? Math.max(0, hideDelay) : 0;
9715
+ timers.current.t = setTimeout(() => {
9716
+ setOpen(false);
9717
+ }, d);
9718
+ }, [trigger, hideDelay, disabled, alwaysShow, setOpen]);
9719
+ // 트리거 이벤트
9117
9720
  const handleClick = useCallback((e) => {
9118
- if (trigger === 'click') {
9119
- e.preventDefault();
9120
- e.stopPropagation();
9121
- if (isOpen) {
9122
- closePopover();
9123
- }
9124
- else {
9125
- openPopover();
9126
- }
9127
- }
9721
+ if (trigger !== 'click')
9722
+ return;
9723
+ e.preventDefault();
9724
+ e.stopPropagation();
9725
+ isOpen ? closePopover() : openPopover();
9128
9726
  }, [trigger, isOpen, openPopover, closePopover]);
9129
9727
  const handleMouseEnter = useCallback(() => {
9130
- if (trigger === 'hover') {
9728
+ if (trigger === 'hover')
9131
9729
  openPopover();
9132
- }
9133
9730
  }, [trigger, openPopover]);
9134
9731
  const handleMouseLeave = useCallback(() => {
9135
- if (trigger === 'hover') {
9732
+ if (trigger === 'hover')
9136
9733
  closePopover();
9137
- }
9138
9734
  }, [trigger, closePopover]);
9139
9735
  const handleFocus = useCallback(() => {
9140
- if (trigger === 'focus') {
9736
+ if (trigger === 'focus')
9141
9737
  openPopover();
9142
- }
9143
9738
  }, [trigger, openPopover]);
9144
9739
  const handleBlur = useCallback(() => {
9145
- if (trigger === 'focus') {
9740
+ if (trigger === 'focus')
9146
9741
  closePopover();
9147
- }
9148
9742
  }, [trigger, closePopover]);
9149
- // 키보드 이벤트
9150
9743
  const handleKeyDown = useCallback((e) => {
9151
- if (e.key === 'Escape' && closeOnEscape) {
9744
+ if (e.key === 'Escape' && closeOnEscape)
9152
9745
  closePopover();
9153
- }
9154
9746
  }, [closeOnEscape, closePopover]);
9155
- // 닫기 버튼 핸들러
9156
- const handleCloseClick = useCallback((e) => {
9157
- e.preventDefault();
9158
- e.stopPropagation();
9747
+ const handleCloseClick = useCallback(() => {
9159
9748
  closePopover();
9160
9749
  }, [closePopover]);
9161
- // 외부 클릭 처리
9162
- useEffect(() => {
9163
- if (isOpen && closeOnOutsideClick) {
9164
- const handleOutsideClick = (e) => {
9165
- if (triggerRef.current &&
9166
- popoverRef.current &&
9167
- !triggerRef.current.contains(e.target) &&
9168
- !popoverRef.current.contains(e.target)) {
9169
- closePopover();
9170
- }
9171
- };
9172
- document.addEventListener('mousedown', handleOutsideClick);
9173
- return () => {
9174
- document.removeEventListener('mousedown', handleOutsideClick);
9175
- };
9176
- }
9177
- }, [isOpen, closeOnOutsideClick, closePopover]);
9178
- // 윈도우 리사이즈 및 스크롤 이벤트
9179
- useEffect(() => {
9180
- if (isOpen) {
9181
- const handleResize = () => calculatePosition();
9182
- const handleScroll = () => calculatePosition();
9183
- window.addEventListener('resize', handleResize);
9184
- window.addEventListener('scroll', handleScroll, true);
9185
- return () => {
9186
- window.removeEventListener('resize', handleResize);
9187
- window.removeEventListener('scroll', handleScroll, true);
9188
- };
9189
- }
9190
- }, [isOpen, calculatePosition]);
9191
- // 컴포넌트 언마운트 시 타이머 정리
9750
+ // 외부 클릭 닫기
9192
9751
  useEffect(() => {
9193
- return () => {
9194
- if (timeoutRef.current) {
9195
- clearTimeout(timeoutRef.current);
9752
+ if (!isOpen || !closeOnOutsideClick)
9753
+ return;
9754
+ const onDown = (e) => {
9755
+ if (!triggerRef.current || !popoverRef.current)
9756
+ return;
9757
+ const t = e.target;
9758
+ if (!triggerRef.current.contains(t) && !popoverRef.current.contains(t)) {
9759
+ closePopover();
9196
9760
  }
9197
9761
  };
9198
- }, []);
9199
- // 항상 표시 모드
9762
+ document.addEventListener('mousedown', onDown);
9763
+ return () => document.removeEventListener('mousedown', onDown);
9764
+ }, [isOpen, closeOnOutsideClick, closePopover]);
9765
+ // alwaysShow 모드 동기화
9200
9766
  useEffect(() => {
9201
- if (alwaysShow && !disabled) {
9202
- const newOpen = true;
9203
- if (!isControlled) {
9204
- setInternalOpen(newOpen);
9205
- }
9206
- onOpenChange?.(newOpen);
9207
- requestAnimationFrame(() => {
9208
- calculatePosition();
9209
- });
9210
- }
9211
- else if (!alwaysShow) {
9212
- const newOpen = false;
9213
- if (!isControlled) {
9214
- setInternalOpen(newOpen);
9215
- }
9216
- onOpenChange?.(newOpen);
9217
- }
9218
- }, [alwaysShow, disabled, isControlled, onOpenChange, calculatePosition]);
9219
- const classes = clsx('designbase-popover', `designbase-popover--${size}`, `designbase-popover--${variant}`, `designbase-popover--${position}`, {
9220
- 'designbase-popover--visible': isOpen,
9221
- 'designbase-popover--disabled': disabled,
9222
- }, className);
9767
+ if (disabled)
9768
+ return;
9769
+ if (alwaysShow)
9770
+ setOpen(true);
9771
+ else if (!isControlled)
9772
+ setOpen(false);
9773
+ // eslint-disable-next-line react-hooks/exhaustive-deps
9774
+ }, [alwaysShow, disabled]);
9775
+ useEffect(() => () => { if (timers.current.t)
9776
+ clearTimeout(timers.current.t); }, []);
9777
+ const classes = clsx('designbase-popover', `designbase-popover--${size}`, `designbase-popover--${variant}`, `designbase-popover--${position}`, { 'designbase-popover--visible': isOpen, 'designbase-popover--disabled': disabled }, className);
9223
9778
  const arrowClasses = clsx('designbase-popover__arrow', `designbase-popover__arrow--${position}`);
9224
- // 자식 요소에 이벤트 핸들러 추가
9779
+ // 자식에 트리거 이벤트 주입
9225
9780
  const enhancedChildren = React.cloneElement(children, {
9226
9781
  ref: triggerRef,
9227
- onClick: handleClick,
9228
- onMouseEnter: handleMouseEnter,
9229
- onMouseLeave: handleMouseLeave,
9230
- onFocus: handleFocus,
9231
- onBlur: handleBlur,
9232
- onKeyDown: handleKeyDown,
9782
+ onClick: (e) => { children.props.onClick?.(e); handleClick(e); },
9783
+ onMouseEnter: (e) => { children.props.onMouseEnter?.(e); handleMouseEnter(); },
9784
+ onMouseLeave: (e) => { children.props.onMouseLeave?.(e); handleMouseLeave(); },
9785
+ onFocus: (e) => { children.props.onFocus?.(e); handleFocus(); },
9786
+ onBlur: (e) => { children.props.onBlur?.(e); handleBlur(); },
9787
+ onKeyDown: (e) => { children.props.onKeyDown?.(e); handleKeyDown(e); },
9233
9788
  tabIndex: children.props.tabIndex ?? 0,
9234
9789
  'aria-expanded': isOpen,
9235
9790
  'aria-haspopup': 'dialog',
9236
9791
  });
9237
- return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [enhancedChildren, isOpen && (jsxRuntimeExports.jsxs("div", { ref: popoverRef, className: classes, style: popoverStyle, role: "dialog", "aria-modal": "false", ...props, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-popover__content", children: [showCloseButton && (jsxRuntimeExports.jsx("button", { className: "designbase-popover__close-button", onClick: handleCloseClick, "aria-label": "\uD31D\uC624\uBC84 \uB2EB\uAE30", type: "button" })), content] }), showArrow && (jsxRuntimeExports.jsx("div", { className: arrowClasses, style: arrowStyle }))] }))] }));
9792
+ return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [enhancedChildren, isOpen && (jsxRuntimeExports.jsxs("div", { ref: popoverRef, className: classes, style: style, role: "dialog", "aria-modal": "false", "data-placement-group": placementGroup, ...props, children: [jsxRuntimeExports.jsxs("div", { className: "designbase-popover__content", children: [(title || true) && (jsxRuntimeExports.jsxs("div", { className: "designbase-popover__header", children: [title && (jsxRuntimeExports.jsx("h3", { className: "designbase-popover__title", children: title })), jsxRuntimeExports.jsx(Button, { size: "s", variant: "tertiary", iconOnly: true, onPress: handleCloseClick, "aria-label": "\uD31D\uC624\uBC84 \uB2EB\uAE30", className: "designbase-popover__close-button", children: jsxRuntimeExports.jsx(CloseIcon, {}) })] })), jsxRuntimeExports.jsx("div", { className: "designbase-popover__body", children: content })] }), showArrow && jsxRuntimeExports.jsx("div", { className: arrowClasses, style: arrowStyle })] }))] }));
9238
9793
  };
9239
9794
  Popover.displayName = 'Popover';
9240
9795
 
@@ -9256,7 +9811,7 @@ const Progress = ({ value, max = 100, size = 'm', variant = 'default', type = 'l
9256
9811
  return (jsxRuntimeExports.jsx("div", { className: classes, children: type === 'linear' ? renderLinearProgress() : renderCircularProgress() }));
9257
9812
  };
9258
9813
 
9259
- const ProgressStep = ({ items, variant = 'default', size = 'm', layout = 'vertical', currentStep = 0, clickable = false, fullWidth = false, disabled = false, className, style, onStepClick, }) => {
9814
+ const ProgressStep = ({ items, size = 'm', layout = 'vertical', currentStep = 0, clickable = false, fullWidth = false, disabled = false, className, style, onStepClick, }) => {
9260
9815
  const handleStepClick = (item, index) => {
9261
9816
  if (disabled || item.disabled || !clickable)
9262
9817
  return;
@@ -9272,7 +9827,7 @@ const ProgressStep = ({ items, variant = 'default', size = 'm', layout = 'vertic
9272
9827
  return 'active';
9273
9828
  return 'pending';
9274
9829
  };
9275
- const classes = clsx('designbase-progress-step', `designbase-progress-step--${variant}`, `designbase-progress-step--${size}`, `designbase-progress-step--${layout}`, {
9830
+ const classes = clsx('designbase-progress-step', `designbase-progress-step--${size}`, `designbase-progress-step--${layout}`, {
9276
9831
  'designbase-progress-step--clickable': clickable,
9277
9832
  'designbase-progress-step--full-width': fullWidth,
9278
9833
  'designbase-progress-step--disabled': disabled,
@@ -10151,7 +10706,7 @@ const Section = ({ title, subtitle, description, size = 'm', variant = 'default'
10151
10706
  return (jsxRuntimeExports.jsxs("section", { className: classes, children: [hasHeader && (jsxRuntimeExports.jsx("div", { className: "designbase-section__header", children: header || (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [(title || subtitle) && (jsxRuntimeExports.jsxs("div", { className: "designbase-section__title-area", children: [title && (jsxRuntimeExports.jsx("h2", { className: "designbase-section__title", children: title })), subtitle && (jsxRuntimeExports.jsx("h3", { className: "designbase-section__subtitle", children: subtitle })), description && (jsxRuntimeExports.jsx("p", { className: "designbase-section__description", children: description }))] })), actions && (jsxRuntimeExports.jsx("div", { className: "designbase-section__actions", children: actions }))] })) })), jsxRuntimeExports.jsx("div", { className: "designbase-section__content", children: children }), footer && (jsxRuntimeExports.jsx("div", { className: "designbase-section__footer", children: footer }))] }));
10152
10707
  };
10153
10708
 
10154
- const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant = 'button', size = 'm', position = 'bottom', platforms = ['facebook', 'twitter', 'linkedin', 'whatsapp', 'email', 'link'], customPlatforms = {}, buttonText = '공유', buttonIcon = ShareAltIcon, modalTitle = '공유하기', copySuccessMessage = '링크가 클립보드에 복사되었습니다!', showQrCode = true, qrCodeSize = 200, className, style, onShare, onShareError, onCopySuccess, onCopyError, }) => {
10709
+ const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant = 'button', size = 'm', position = 'bottom', platforms = ['facebook', 'x', 'linkedin', 'whatsapp', 'email', 'link'], customPlatforms = {}, buttonText = '공유', buttonIcon = ShareAltIcon, modalTitle = '공유하기', copySuccessMessage = '링크가 클립보드에 복사되었습니다!', showQrCode = true, qrCodeSize = 200, className, style, onShare, onShareError, onCopySuccess, onCopyError, }) => {
10155
10710
  const [isOpen, setIsOpen] = useState(false);
10156
10711
  const [isCopied, setIsCopied] = useState(false);
10157
10712
  const [qrCodeData, setQrCodeData] = useState('');
@@ -10159,19 +10714,19 @@ const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant
10159
10714
  const defaultPlatforms = {
10160
10715
  facebook: {
10161
10716
  name: 'Facebook',
10162
- icon: ShareAltIcon,
10717
+ icon: FacebookIcon,
10163
10718
  color: '#1877f2',
10164
10719
  shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
10165
10720
  },
10166
- twitter: {
10167
- name: 'Twitter',
10168
- icon: ShareAltIcon,
10169
- color: '#1da1f2',
10721
+ x: {
10722
+ name: 'X',
10723
+ icon: XIcon,
10724
+ color: '#000000',
10170
10725
  shareUrl: 'https://twitter.com/intent/tweet?url={url}&text={title}&hashtags={hashtags}',
10171
10726
  },
10172
10727
  instagram: {
10173
10728
  name: 'Instagram',
10174
- icon: ShareAltIcon,
10729
+ icon: InstagramIcon,
10175
10730
  color: '#e4405f',
10176
10731
  shareUrl: 'https://www.instagram.com/',
10177
10732
  customShare: () => {
@@ -10181,25 +10736,25 @@ const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant
10181
10736
  },
10182
10737
  linkedin: {
10183
10738
  name: 'LinkedIn',
10184
- icon: ShareAltIcon,
10739
+ icon: LinkedinIcon,
10185
10740
  color: '#0077b5',
10186
10741
  shareUrl: 'https://www.linkedin.com/sharing/share-offsite/?url={url}',
10187
10742
  },
10188
10743
  pinterest: {
10189
10744
  name: 'Pinterest',
10190
- icon: ShareAltIcon,
10745
+ icon: PinterestIcon,
10191
10746
  color: '#bd081c',
10192
10747
  shareUrl: 'https://pinterest.com/pin/create/button/?url={url}&description={title}&media={imageUrl}',
10193
10748
  },
10194
10749
  whatsapp: {
10195
10750
  name: 'WhatsApp',
10196
- icon: ShareAltIcon,
10751
+ icon: WhatsappIcon,
10197
10752
  color: '#25d366',
10198
10753
  shareUrl: 'https://wa.me/?text={title}%20{url}',
10199
10754
  },
10200
10755
  telegram: {
10201
10756
  name: 'Telegram',
10202
- icon: ShareAltIcon,
10757
+ icon: TelegramIcon,
10203
10758
  color: '#0088cc',
10204
10759
  shareUrl: 'https://t.me/share/url?url={url}&text={title}',
10205
10760
  },
@@ -10218,7 +10773,7 @@ const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant
10218
10773
  },
10219
10774
  qr: {
10220
10775
  name: 'QR 코드',
10221
- icon: ShareAltIcon,
10776
+ icon: ScanQrcodeIcon,
10222
10777
  color: '#6c757d',
10223
10778
  shareUrl: '',
10224
10779
  customShare: () => handleShowQrCode(),
@@ -10294,7 +10849,7 @@ const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant
10294
10849
  // 공유 버튼 렌더링
10295
10850
  const renderShareButton = () => {
10296
10851
  const ButtonIcon = buttonIcon;
10297
- return (jsxRuntimeExports.jsx(Button, { variant: "secondary", size: size, onClick: () => setIsOpen(!isOpen), className: "designbase-share__trigger", icon: ButtonIcon, children: buttonText }));
10852
+ return (jsxRuntimeExports.jsx(Button, { variant: "secondary", size: size, onPress: () => setIsOpen(!isOpen), className: "designbase-share__trigger", startIcon: ButtonIcon, children: buttonText }));
10298
10853
  };
10299
10854
  // 플랫폼 버튼 렌더링
10300
10855
  const renderPlatformButton = (platform) => {
@@ -10308,7 +10863,7 @@ const Share = ({ url, title, description = '', imageUrl, hashtags = [], variant
10308
10863
  }), style: { '--platform-color': config.color }, onClick: () => handlePlatformShare(platform), title: config.name, "aria-label": `${config.name}에 공유하기`, children: [jsxRuntimeExports.jsx(PlatformIcon, { className: "designbase-share__platform-icon" }), jsxRuntimeExports.jsx("span", { className: "designbase-share__platform-name", children: config.name }), isLinkCopy && isCopied && (jsxRuntimeExports.jsxs("span", { className: "designbase-share__copy-success", children: [jsxRuntimeExports.jsx(CopyIcon, {}), "\uBCF5\uC0AC\uB428!"] }))] }, platform));
10309
10864
  };
10310
10865
  // 공유 영역 렌더링
10311
- const renderShareContent = () => (jsxRuntimeExports.jsxs("div", { className: "designbase-share__content", children: [jsxRuntimeExports.jsx("div", { className: "designbase-share__platforms", children: activePlatforms.map(renderPlatformButton) }), showQrCode && qrCodeData && (jsxRuntimeExports.jsxs("div", { className: "designbase-share__qr-section", children: [jsxRuntimeExports.jsx("h4", { className: "designbase-share__qr-title", children: "QR \uCF54\uB4DC" }), jsxRuntimeExports.jsx("div", { className: "designbase-share__qr-code", children: jsxRuntimeExports.jsx("img", { src: generateQrCode(), alt: "QR Code", width: qrCodeSize, height: qrCodeSize }) }), jsxRuntimeExports.jsx("p", { className: "designbase-share__qr-description", children: "QR \uCF54\uB4DC\uB97C \uC2A4\uCE94\uD558\uC5EC \uB9C1\uD06C\uC5D0 \uC811\uC18D\uD558\uC138\uC694" })] })), jsxRuntimeExports.jsxs("div", { className: "designbase-share__link-preview", children: [jsxRuntimeExports.jsx("h4", { className: "designbase-share__link-title", children: "\uACF5\uC720 \uB9C1\uD06C" }), jsxRuntimeExports.jsxs("div", { className: "designbase-share__link-container", children: [jsxRuntimeExports.jsx("input", { type: "text", value: url, readOnly: true, className: "designbase-share__link-input" }), jsxRuntimeExports.jsx(Button, { size: "s", variant: "primary", onClick: handleCopyLink, className: "designbase-share__copy-button", icon: isCopied ? CopyIcon : LinkIcon, children: isCopied ? '복사됨!' : '복사' })] })] })] }));
10866
+ const renderShareContent = () => (jsxRuntimeExports.jsxs("div", { className: "designbase-share__content", children: [jsxRuntimeExports.jsx("div", { className: "designbase-share__platforms", children: activePlatforms.map(renderPlatformButton) }), showQrCode && qrCodeData && (jsxRuntimeExports.jsxs("div", { className: "designbase-share__qr-section", children: [jsxRuntimeExports.jsx("h4", { className: "designbase-share__qr-title", children: "QR \uCF54\uB4DC" }), jsxRuntimeExports.jsx("div", { className: "designbase-share__qr-code", children: jsxRuntimeExports.jsx("img", { src: generateQrCode(), alt: "QR Code", width: qrCodeSize, height: qrCodeSize }) }), jsxRuntimeExports.jsx("p", { className: "designbase-share__qr-description", children: "QR \uCF54\uB4DC\uB97C \uC2A4\uCE94\uD558\uC5EC \uB9C1\uD06C\uC5D0 \uC811\uC18D\uD558\uC138\uC694" })] })), jsxRuntimeExports.jsxs("div", { className: "designbase-share__link-preview", children: [jsxRuntimeExports.jsx("h4", { className: "designbase-share__link-title", children: "\uACF5\uC720 \uB9C1\uD06C" }), jsxRuntimeExports.jsxs("div", { className: "designbase-share__link-container", children: [jsxRuntimeExports.jsx(Input, { type: "text", value: url, readOnly: true, size: "s", fullWidth: true, className: "designbase-share__link-input" }), jsxRuntimeExports.jsx(Button, { size: "s", variant: "primary", onPress: handleCopyLink, className: "designbase-share__copy-button", startIcon: isCopied ? CopyIcon : LinkIcon, children: isCopied ? '복사됨!' : '복사' })] })] })] }));
10312
10867
  // 변형별 렌더링
10313
10868
  switch (variant) {
10314
10869
  case 'dropdown':
@@ -10379,22 +10934,14 @@ const Sidebar = ({ size = 'm', variant = 'default', position = 'left', logo, onL
10379
10934
  e.preventDefault();
10380
10935
  onLogoClick();
10381
10936
  }
10382
- }, children: logo || jsxRuntimeExports.jsx(Logo, { size: size === 's' ? 's' : size === 'l' ? 'l' : 'm' }) }), collapsible && (jsxRuntimeExports.jsx(Button, { variant: "ghost", size: "s", iconOnly: true, className: "designbase-sidebar__toggle", onPress: handleToggle, "aria-label": collapsed ? '사이드바 펼치기' : '사이드바 접기', children: jsxRuntimeExports.jsx(ChevronLeftIcon, { className: clsx('designbase-sidebar__toggle-icon', {
10937
+ }, children: logo || jsxRuntimeExports.jsx(Logo, { size: "s" }) }), collapsible && (jsxRuntimeExports.jsx(Button, { variant: "ghost", size: "s", iconOnly: true, className: "designbase-sidebar__toggle", onPress: handleToggle, "aria-label": collapsed ? '사이드바 펼치기' : '사이드바 접기', children: jsxRuntimeExports.jsx(ChevronLeftIcon, { className: clsx('designbase-sidebar__toggle-icon', {
10383
10938
  'designbase-sidebar__toggle-icon--collapsed': collapsed,
10384
10939
  }) }) }))] }), jsxRuntimeExports.jsx("nav", { className: "designbase-sidebar__nav", children: jsxRuntimeExports.jsx("ul", { className: "designbase-sidebar__nav-list", children: items.map((item) => renderSidebarItem(item)) }) }), userProfile && !collapsed && (jsxRuntimeExports.jsxs("div", { className: "designbase-sidebar__user", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-sidebar__user-info", onClick: () => onUserMenuItemClick?.({ id: 'profile', label: '프로필', href: '#' }), role: "button", tabIndex: 0, onKeyDown: (e) => {
10385
10940
  if (e.key === 'Enter' || e.key === ' ') {
10386
10941
  e.preventDefault();
10387
10942
  onUserMenuItemClick?.({ id: 'profile', label: '프로필', href: '#' });
10388
10943
  }
10389
- }, style: { cursor: 'pointer' }, children: [userProfile.avatar ? (jsxRuntimeExports.jsx("img", { src: userProfile.avatar, alt: userProfile.name, className: "designbase-sidebar__user-avatar" })) : (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-avatar-placeholder", children: userProfile.name.charAt(0).toUpperCase() })), jsxRuntimeExports.jsxs("div", { className: "designbase-sidebar__user-details", children: [jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-name", children: userProfile.name }), userProfile.email && (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-email", children: userProfile.email })), userProfile.role && (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-role", children: userProfile.role }))] })] }), userMenuItems.length > 0 && (jsxRuntimeExports.jsx("ul", { className: "designbase-sidebar__user-menu", children: userMenuItems.map((item) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsxs("a", { href: item.href, className: clsx('designbase-sidebar__user-menu-link', {
10390
- 'designbase-sidebar__user-menu-link--disabled': item.disabled,
10391
- }), onClick: (e) => {
10392
- if (item.disabled) {
10393
- e.preventDefault();
10394
- return;
10395
- }
10396
- handleUserMenuItemClick(item);
10397
- }, children: [item.icon && React.createElement(item.icon, { size: 16 }), item.label] }) }, item.id))) }))] })), userProfile && collapsed && (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-collapsed", onClick: () => onUserMenuItemClick?.({ id: 'profile', label: '프로필', href: '#' }), role: "button", tabIndex: 0, onKeyDown: (e) => {
10944
+ }, style: { cursor: 'pointer' }, children: [userProfile.avatar ? (jsxRuntimeExports.jsx("img", { src: userProfile.avatar, alt: userProfile.name, className: "designbase-sidebar__user-avatar" })) : (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-avatar-placeholder", children: userProfile.name.charAt(0).toUpperCase() })), jsxRuntimeExports.jsxs("div", { className: "designbase-sidebar__user-details", children: [jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-name", children: userProfile.name }), userProfile.email && (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-email", children: userProfile.email })), userProfile.role && (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-role", children: userProfile.role }))] })] }), userMenuItems.length > 0 && (jsxRuntimeExports.jsx("ul", { className: "designbase-sidebar__user-menu", children: userMenuItems.map((item) => (jsxRuntimeExports.jsx("li", { children: jsxRuntimeExports.jsx(MenuItem, { id: item.id, label: item.label, href: item.href, icon: item.icon, disabled: item.disabled, type: "block", style: "accordion", onClick: () => handleUserMenuItemClick(item) }) }, item.id))) }))] })), userProfile && collapsed && (jsxRuntimeExports.jsx("div", { className: "designbase-sidebar__user-collapsed", onClick: () => onUserMenuItemClick?.({ id: 'profile', label: '프로필', href: '#' }), role: "button", tabIndex: 0, onKeyDown: (e) => {
10398
10945
  if (e.key === 'Enter' || e.key === ' ') {
10399
10946
  e.preventDefault();
10400
10947
  onUserMenuItemClick?.({ id: 'profile', label: '프로필', href: '#' });
@@ -10630,8 +11177,12 @@ const Stepper = ({ value = 0, min = 0, max = 100, step = 1, size = 'm', variant
10630
11177
  return (jsxRuntimeExports.jsxs("div", { className: classes, children: [jsxRuntimeExports.jsx("button", { type: "button", className: decrementClasses, onClick: handleDecrement, disabled: disabled || readonly || isMinReached, "aria-label": "\uAC10\uC18C", "aria-describedby": "stepper-description", children: jsxRuntimeExports.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: jsxRuntimeExports.jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) }) }), jsxRuntimeExports.jsx("div", { className: "designbase-stepper__value", children: jsxRuntimeExports.jsx("span", { className: "designbase-stepper__value-text", children: currentValue }) }), jsxRuntimeExports.jsx("button", { type: "button", className: incrementClasses, onClick: handleIncrement, disabled: disabled || readonly || isMaxReached, "aria-label": "\uC99D\uAC00", "aria-describedby": "stepper-description", children: jsxRuntimeExports.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [jsxRuntimeExports.jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), jsxRuntimeExports.jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })] }) }), jsxRuntimeExports.jsxs("div", { id: "stepper-description", className: "sr-only", children: ["\uC22B\uC790 \uC870\uC815\uAE30. \uD654\uC0B4\uD45C \uD0A4\uB098 +/- \uD0A4\uB97C \uC0AC\uC6A9\uD558\uC5EC \uAC12\uC744 \uC870\uC815\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uAC12: ", currentValue, ", \uCD5C\uC18C\uAC12: ", min, ", \uCD5C\uB300\uAC12: ", max, ", \uB2E8\uACC4: ", step] })] }));
10631
11178
  };
10632
11179
 
10633
- const Table = ({ data, columns, title, showCountBadge = false, showFilter = false, filterOptions = [], onFilterChange, size = 'm', variant = 'default', selectable = false, multiSelect = false, selectedRows = [], onSelectionChange, sortable = false, sortColumn, sortDirection, onSortChange, loading = false, emptyMessage = '데이터가 없습니다.', onRowClick, rowKey = 'id', height, scrollable = false, className, ...props }) => {
11180
+ const Table = ({ data, columns, title, showCountBadge = false, showFilter = false, filterOptions = [], onFilterChange, size = 'm', variant = 'default', selectable = false, multiSelect = false, selectedRows = [], onSelectionChange, sortable = false, sortColumn, sortDirection, onSortChange, loading = false, emptyMessage = '데이터가 없습니다.', onRowClick, rowKey = 'id', height, scrollable = false, pagination = false, page, pageSize, totalItems, onPageChange, onPageSizeChange, pageSizeOptions, className, ...props }) => {
10634
11181
  const [internalSelectedRows, setInternalSelectedRows] = useState(selectedRows);
11182
+ const [internalPage, setInternalPage] = useState(page || 1);
11183
+ const [internalPageSize, setInternalPageSize] = useState(pageSize || 10);
11184
+ const [internalSortColumn, setInternalSortColumn] = useState(sortColumn);
11185
+ const [internalSortDirection, setInternalSortDirection] = useState(sortDirection ?? null);
10635
11186
  const getRowKey = (item, index) => {
10636
11187
  if (typeof rowKey === 'string') {
10637
11188
  return item[rowKey] || index.toString();
@@ -10641,8 +11192,27 @@ const Table = ({ data, columns, title, showCountBadge = false, showFilter = fals
10641
11192
  const handleSort = (column) => {
10642
11193
  if (!sortable || !column.sortable)
10643
11194
  return;
10644
- const newDirection = sortColumn === column.key && sortDirection === 'asc' ? 'desc' : 'asc';
10645
- onSortChange?.(column.key, newDirection);
11195
+ const currentCol = effectiveSortColumn;
11196
+ const currentDir = effectiveSortDirection;
11197
+ let nextDir = 'asc';
11198
+ if (currentCol === column.key) {
11199
+ if (currentDir === 'asc')
11200
+ nextDir = 'desc';
11201
+ else if (currentDir === 'desc')
11202
+ nextDir = null;
11203
+ else
11204
+ nextDir = 'asc';
11205
+ }
11206
+ else {
11207
+ nextDir = 'asc';
11208
+ }
11209
+ if (onSortChange) {
11210
+ onSortChange(column.key, nextDir);
11211
+ }
11212
+ else {
11213
+ setInternalSortColumn(nextDir ? column.key : undefined);
11214
+ setInternalSortDirection(nextDir);
11215
+ }
10646
11216
  };
10647
11217
  const handleRowSelect = (itemRowKey, checked) => {
10648
11218
  let newSelectedRows;
@@ -10666,24 +11236,34 @@ const Table = ({ data, columns, title, showCountBadge = false, showFilter = fals
10666
11236
  setInternalSelectedRows(newSelectedRows);
10667
11237
  onSelectionChange?.(newSelectedRows);
10668
11238
  };
11239
+ const effectiveSortColumn = sortColumn !== undefined ? sortColumn : internalSortColumn;
11240
+ const effectiveSortDirection = sortDirection !== undefined ? sortDirection : internalSortDirection;
10669
11241
  const sortedData = useMemo(() => {
10670
- if (!sortColumn || !sortDirection)
11242
+ if (!effectiveSortColumn || !effectiveSortDirection)
10671
11243
  return data;
10672
- const column = columns.find(col => col.key === sortColumn);
11244
+ const column = columns.find(col => col.key === effectiveSortColumn);
10673
11245
  if (!column || !column.sortable)
10674
11246
  return data;
10675
11247
  return [...data].sort((a, b) => {
10676
11248
  const aValue = column.accessor ? column.accessor(a) : a[column.key];
10677
11249
  const bValue = column.accessor ? column.accessor(b) : b[column.key];
10678
11250
  if (aValue < bValue)
10679
- return sortDirection === 'asc' ? -1 : 1;
11251
+ return effectiveSortDirection === 'asc' ? -1 : 1;
10680
11252
  if (aValue > bValue)
10681
- return sortDirection === 'asc' ? 1 : -1;
11253
+ return effectiveSortDirection === 'asc' ? 1 : -1;
10682
11254
  return 0;
10683
11255
  });
10684
- }, [data, sortColumn, sortDirection, columns]);
11256
+ }, [data, effectiveSortColumn, effectiveSortDirection, columns, internalSortColumn, internalSortDirection]);
10685
11257
  const isAllSelected = data.length > 0 && internalSelectedRows.length === data.length;
10686
11258
  const isIndeterminate = internalSelectedRows.length > 0 && internalSelectedRows.length < data.length;
11259
+ // 페이지네이션 계산
11260
+ const effectivePageSize = pageSize || internalPageSize;
11261
+ const total = totalItems ?? sortedData.length;
11262
+ const totalPages = Math.max(1, Math.ceil(total / effectivePageSize));
11263
+ const currentPage = Math.min(page || internalPage, totalPages);
11264
+ const startIndex = (currentPage - 1) * effectivePageSize;
11265
+ const endIndex = startIndex + effectivePageSize;
11266
+ const paginatedData = pagination ? sortedData.slice(startIndex, endIndex) : sortedData;
10687
11267
  const classes = clsx('designbase-table', `designbase-table--${size}`, `designbase-table--${variant}`, {
10688
11268
  'designbase-table--selectable': selectable,
10689
11269
  'designbase-table--sortable': sortable,
@@ -10696,13 +11276,13 @@ const Table = ({ data, columns, title, showCountBadge = false, showFilter = fals
10696
11276
  const renderSortIcon = (column) => {
10697
11277
  if (!sortable || !column.sortable)
10698
11278
  return null;
10699
- const isSorted = sortColumn === column.key;
10700
- const isAsc = isSorted && sortDirection === 'asc';
10701
- const isDesc = isSorted && sortDirection === 'desc';
10702
- return (jsxRuntimeExports.jsx("span", { className: "designbase-table__sort-icon", children: jsxRuntimeExports.jsx(ChevronsUpIcon, { size: 16, className: clsx({
11279
+ const isSorted = effectiveSortColumn === column.key;
11280
+ const isAsc = isSorted && effectiveSortDirection === 'asc';
11281
+ const isDesc = isSorted && effectiveSortDirection === 'desc';
11282
+ return (jsxRuntimeExports.jsx("span", { className: "designbase-table__sort-icon", children: isSorted ? (jsxRuntimeExports.jsx(VerticalArrowsIcon, { size: 14, className: clsx({
10703
11283
  'designbase-table__sort-icon--asc': isAsc,
10704
11284
  'designbase-table__sort-icon--desc': isDesc,
10705
- }) }) }));
11285
+ }) })) : (jsxRuntimeExports.jsx(CaretUpdownFilledIcon, { size: 14, className: "designbase-table__sort-icon--idle" })) }));
10706
11286
  };
10707
11287
  const renderCell = (column, item, index) => {
10708
11288
  const value = column.accessor ? column.accessor(item) : item[column.key];
@@ -10720,7 +11300,7 @@ const Table = ({ data, columns, title, showCountBadge = false, showFilter = fals
10720
11300
  [`designbase-table__th--${column.align || 'left'}`]: column.align,
10721
11301
  'designbase-table__th--fixed-left': column.fixed === 'left',
10722
11302
  'designbase-table__th--fixed-right': column.fixed === 'right',
10723
- }), style: { width: column.width }, onClick: () => handleSort(column), children: jsxRuntimeExports.jsxs("div", { className: "designbase-table__th-content", children: [jsxRuntimeExports.jsx("span", { className: "designbase-table__th-text", children: column.header }), renderSortIcon(column)] }) }, column.key)))] }) }), jsxRuntimeExports.jsx("tbody", { className: "designbase-table__tbody", children: sortedData.length === 0 ? (jsxRuntimeExports.jsx("tr", { className: "designbase-table__tr", children: jsxRuntimeExports.jsx("td", { colSpan: columns.length + (selectable ? 1 : 0), className: "designbase-table__td designbase-table__td--empty", children: jsxRuntimeExports.jsx("div", { className: "designbase-table__empty", children: jsxRuntimeExports.jsx("span", { children: emptyMessage }) }) }) })) : (sortedData.map((item, index) => {
11303
+ }), style: { width: column.width }, onClick: () => handleSort(column), children: jsxRuntimeExports.jsxs("div", { className: "designbase-table__th-content", children: [jsxRuntimeExports.jsx("span", { className: "designbase-table__th-text", children: column.header }), renderSortIcon(column)] }) }, column.key)))] }) }), jsxRuntimeExports.jsx("tbody", { className: "designbase-table__tbody", children: paginatedData.length === 0 ? (jsxRuntimeExports.jsx("tr", { className: "designbase-table__tr", children: jsxRuntimeExports.jsx("td", { colSpan: columns.length + (selectable ? 1 : 0), className: "designbase-table__td designbase-table__td--empty", children: jsxRuntimeExports.jsx(EmptyState, { title: "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4", description: emptyMessage, variant: "no-data", size: "m" }) }) })) : (paginatedData.map((item, index) => {
10724
11304
  const key = getRowKey(item, index);
10725
11305
  const isSelected = internalSelectedRows.includes(key);
10726
11306
  return (jsxRuntimeExports.jsxs("tr", { className: clsx('designbase-table__tr', {
@@ -10731,7 +11311,7 @@ const Table = ({ data, columns, title, showCountBadge = false, showFilter = fals
10731
11311
  'designbase-table__td--fixed-left': column.fixed === 'left',
10732
11312
  'designbase-table__td--fixed-right': column.fixed === 'right',
10733
11313
  }), children: renderCell(column, item, index) }, column.key)))] }, key));
10734
- })) })] }) })] }));
11314
+ })) })] }) }), pagination && (jsxRuntimeExports.jsx("div", { className: "designbase-table__footer", children: jsxRuntimeExports.jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, totalItems: total, pageSize: effectivePageSize, pageSizeOptions: pageSizeOptions || [10, 20, 50], onPageChange: (p) => { setInternalPage(p); onPageChange?.(p); }, onPageSizeChange: (s) => { setInternalPage(1); setInternalPageSize(s); onPageSizeChange?.(s); onPageChange?.(1); }, showPageSizeSelector: true, showTotal: true, alignment: "right", size: size }) }))] }));
10735
11315
  };
10736
11316
  Table.displayName = 'Table';
10737
11317
 
@@ -10852,198 +11432,7 @@ const Tabs = ({ items, defaultSelectedId, selectedId, orientation = 'horizontal'
10852
11432
  };
10853
11433
  Tabs.displayName = 'Tabs';
10854
11434
 
10855
- const TimePicker = ({ value, onChange, format = '24h', mode = 'dropdown', hourStep = 1, minuteStep = 15, minTime, maxTime, size = 'm', variant = 'default', disabled = false, readonly = false, placeholder = '시간을 선택하세요', displayFormat, className, }) => {
10856
- const [isOpen, setIsOpen] = React.useState(false);
10857
- const [selectedHour, setSelectedHour] = React.useState(() => {
10858
- if (value) {
10859
- return value.getHours();
10860
- }
10861
- return 0;
10862
- });
10863
- const [selectedMinute, setSelectedMinute] = React.useState(() => {
10864
- if (value) {
10865
- return value.getMinutes();
10866
- }
10867
- return 0;
10868
- });
10869
- const [selectedPeriod, setSelectedPeriod] = React.useState(() => {
10870
- if (value && format === '12h') {
10871
- return value.getHours() >= 12 ? 'PM' : 'AM';
10872
- }
10873
- return 'AM';
10874
- });
10875
- const containerRef = React.useRef(null);
10876
- // 시간 유효성 검사
10877
- const isTimeDisabled = (hour, minute) => {
10878
- if (minTime) {
10879
- const minHour = minTime.getHours();
10880
- const minMinute = minTime.getMinutes();
10881
- if (hour < minHour || (hour === minHour && minute < minMinute)) {
10882
- return true;
10883
- }
10884
- }
10885
- if (maxTime) {
10886
- const maxHour = maxTime.getHours();
10887
- const maxMinute = maxTime.getMinutes();
10888
- if (hour > maxHour || (hour === maxHour && minute > maxMinute)) {
10889
- return true;
10890
- }
10891
- }
10892
- return false;
10893
- };
10894
- // 24시간을 12시간으로 변환
10895
- const to12Hour = (hour) => {
10896
- if (hour === 0)
10897
- return 12;
10898
- if (hour > 12)
10899
- return hour - 12;
10900
- return hour;
10901
- };
10902
- // 12시간을 24시간으로 변환
10903
- const to24Hour = (hour, period) => {
10904
- if (period === 'AM') {
10905
- return hour === 12 ? 0 : hour;
10906
- }
10907
- else {
10908
- return hour === 12 ? 12 : hour + 12;
10909
- }
10910
- };
10911
- // 시간 변경 핸들러
10912
- const handleTimeChange = (hour, minute, period) => {
10913
- if (isTimeDisabled(hour, minute))
10914
- return;
10915
- let finalHour = hour;
10916
- if (format === '12h' && period) {
10917
- finalHour = to24Hour(hour, period);
10918
- }
10919
- const newTime = new Date();
10920
- newTime.setHours(finalHour, minute, 0, 0);
10921
- setSelectedHour(hour);
10922
- setSelectedMinute(minute);
10923
- if (period) {
10924
- setSelectedPeriod(period);
10925
- }
10926
- onChange?.(newTime);
10927
- if (mode === 'dropdown') {
10928
- setIsOpen(false);
10929
- }
10930
- };
10931
- // 시간 표시 형식
10932
- const formatDisplayTime = () => {
10933
- if (!value)
10934
- return '';
10935
- if (displayFormat) {
10936
- return value.toLocaleTimeString('ko-KR', {
10937
- hour: '2-digit',
10938
- minute: '2-digit',
10939
- hour12: format === '12h',
10940
- });
10941
- }
10942
- const hour = format === '12h' ? to12Hour(value.getHours()) : value.getHours();
10943
- const minute = value.getMinutes();
10944
- const period = format === '12h' ? (value.getHours() >= 12 ? 'PM' : 'AM') : '';
10945
- return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}${period ? ` ${period}` : ''}`;
10946
- };
10947
- // 시간 옵션 생성
10948
- const generateHourOptions = () => {
10949
- const maxHour = format === '12h' ? 12 : 24;
10950
- const hours = [];
10951
- for (let i = 0; i < maxHour; i += hourStep) {
10952
- if (format === '12h' && i === 0)
10953
- continue; // 12시간 형식에서는 0시 제외
10954
- hours.push(i);
10955
- }
10956
- return hours;
10957
- };
10958
- // 분 옵션 생성
10959
- const generateMinuteOptions = () => {
10960
- const minutes = [];
10961
- for (let i = 0; i < 60; i += minuteStep) {
10962
- minutes.push(i);
10963
- }
10964
- return minutes;
10965
- };
10966
- // 외부 클릭 감지
10967
- React.useEffect(() => {
10968
- const handleClickOutside = (event) => {
10969
- if (containerRef.current && !containerRef.current.contains(event.target)) {
10970
- setIsOpen(false);
10971
- }
10972
- };
10973
- if (isOpen) {
10974
- document.addEventListener('mousedown', handleClickOutside);
10975
- }
10976
- return () => {
10977
- document.removeEventListener('mousedown', handleClickOutside);
10978
- };
10979
- }, [isOpen]);
10980
- // 키보드 네비게이션
10981
- const handleKeyDown = (event) => {
10982
- if (disabled || readonly)
10983
- return;
10984
- switch (event.key) {
10985
- case 'Enter':
10986
- case ' ':
10987
- event.preventDefault();
10988
- setIsOpen(!isOpen);
10989
- break;
10990
- case 'Escape':
10991
- setIsOpen(false);
10992
- break;
10993
- }
10994
- };
10995
- const hourOptions = generateHourOptions();
10996
- const minuteOptions = generateMinuteOptions();
10997
- const classes = clsx('designbase-time-picker', `designbase-time-picker--size-${size}`, `designbase-time-picker--variant-${variant}`, `designbase-time-picker--mode-${mode}`, {
10998
- 'designbase-time-picker--disabled': disabled,
10999
- 'designbase-time-picker--readonly': readonly,
11000
- 'designbase-time-picker--open': isOpen,
11001
- }, className);
11002
- if (mode === 'inline') {
11003
- return (jsxRuntimeExports.jsx("div", { className: classes, children: jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__inline", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__column", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__column-header", children: "\uC2DC\uAC04" }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__options", children: hourOptions.map((hour) => {
11004
- const displayHour = format === '12h' ? to12Hour(hour) : hour;
11005
- const isDisabled = isTimeDisabled(hour, selectedMinute);
11006
- const isSelected = selectedHour === displayHour;
11007
- return (jsxRuntimeExports.jsx("button", { className: clsx('designbase-time-picker__option', {
11008
- 'designbase-time-picker__option--selected': isSelected,
11009
- 'designbase-time-picker__option--disabled': isDisabled,
11010
- }), onClick: () => handleTimeChange(hour, selectedMinute, selectedPeriod), disabled: isDisabled, children: displayHour.toString().padStart(2, '0') }, hour));
11011
- }) })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__column", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__column-header", children: "\uBD84" }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__options", children: minuteOptions.map((minute) => {
11012
- const isDisabled = isTimeDisabled(selectedHour, minute);
11013
- const isSelected = selectedMinute === minute;
11014
- return (jsxRuntimeExports.jsx("button", { className: clsx('designbase-time-picker__option', {
11015
- 'designbase-time-picker__option--selected': isSelected,
11016
- 'designbase-time-picker__option--disabled': isDisabled,
11017
- }), onClick: () => handleTimeChange(selectedHour, minute, selectedPeriod), disabled: isDisabled, children: minute.toString().padStart(2, '0') }, minute));
11018
- }) })] }), format === '12h' && (jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__column", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__column-header", children: "\uAD6C\uBD84" }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__options", children: ['AM', 'PM'].map((period) => {
11019
- const isSelected = selectedPeriod === period;
11020
- return (jsxRuntimeExports.jsx("button", { className: clsx('designbase-time-picker__option', {
11021
- 'designbase-time-picker__option--selected': isSelected,
11022
- }), onClick: () => handleTimeChange(selectedHour, selectedMinute, period), children: period }, period));
11023
- }) })] }))] }) }));
11024
- }
11025
- return (jsxRuntimeExports.jsxs("div", { className: classes, ref: containerRef, children: [jsxRuntimeExports.jsxs("button", { className: "designbase-time-picker__trigger", onClick: () => !disabled && !readonly && setIsOpen(!isOpen), onKeyDown: handleKeyDown, disabled: disabled, type: "button", "aria-haspopup": "listbox", "aria-expanded": isOpen, children: [jsxRuntimeExports.jsx("span", { className: "designbase-time-picker__display", children: value ? formatDisplayTime() : placeholder }), jsxRuntimeExports.jsx("span", { className: "designbase-time-picker__arrow", children: jsxRuntimeExports.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: jsxRuntimeExports.jsx("path", { d: "M3 4.5L6 7.5L9 4.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }), isOpen && (jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__dropdown", children: jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__dropdown-content", children: [jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__column", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__column-header", children: "\uC2DC\uAC04" }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__options", children: hourOptions.map((hour) => {
11026
- const displayHour = format === '12h' ? to12Hour(hour) : hour;
11027
- const isDisabled = isTimeDisabled(hour, selectedMinute);
11028
- const isSelected = selectedHour === displayHour;
11029
- return (jsxRuntimeExports.jsx("button", { className: clsx('designbase-time-picker__option', {
11030
- 'designbase-time-picker__option--selected': isSelected,
11031
- 'designbase-time-picker__option--disabled': isDisabled,
11032
- }), onClick: () => handleTimeChange(hour, selectedMinute, selectedPeriod), disabled: isDisabled, children: displayHour.toString().padStart(2, '0') }, hour));
11033
- }) })] }), jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__column", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__column-header", children: "\uBD84" }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__options", children: minuteOptions.map((minute) => {
11034
- const isDisabled = isTimeDisabled(selectedHour, minute);
11035
- const isSelected = selectedMinute === minute;
11036
- return (jsxRuntimeExports.jsx("button", { className: clsx('designbase-time-picker__option', {
11037
- 'designbase-time-picker__option--selected': isSelected,
11038
- 'designbase-time-picker__option--disabled': isDisabled,
11039
- }), onClick: () => handleTimeChange(selectedHour, minute, selectedPeriod), disabled: isDisabled, children: minute.toString().padStart(2, '0') }, minute));
11040
- }) })] }), format === '12h' && (jsxRuntimeExports.jsxs("div", { className: "designbase-time-picker__column", children: [jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__column-header", children: "\uAD6C\uBD84" }), jsxRuntimeExports.jsx("div", { className: "designbase-time-picker__options", children: ['AM', 'PM'].map((period) => {
11041
- const isSelected = selectedPeriod === period;
11042
- return (jsxRuntimeExports.jsx("button", { className: clsx('designbase-time-picker__option', {
11043
- 'designbase-time-picker__option--selected': isSelected,
11044
- }), onClick: () => handleTimeChange(selectedHour, selectedMinute, period), children: period }, period));
11045
- }) })] }))] }) }))] }));
11046
- };
11435
+ TimePicker;
11047
11436
 
11048
11437
  const Timeline = ({ items, position = 'alternate', variant = 'default', size = 'm', color = 'primary', clickable = false, fullWidth = false, disabled = false, className, style, onItemClick, }) => {
11049
11438
  const handleItemClick = (item, index) => {
@@ -11206,6 +11595,22 @@ Toggle.displayName = 'Toggle';
11206
11595
 
11207
11596
  const Toolbar = ({ items, size = 'm', variant = 'default', position = 'top', fullWidth = false, fixed = false, shadow = true, rounded = true, className, children, }) => {
11208
11597
  const [openDropdown, setOpenDropdown] = React.useState(null);
11598
+ const mapSizeToButtonSize = (size) => {
11599
+ switch (size) {
11600
+ case 's': return 's';
11601
+ case 'm': return 'm';
11602
+ case 'l': return 'l';
11603
+ default: return 'm';
11604
+ }
11605
+ };
11606
+ const getIconComponentFromNode = (node) => {
11607
+ if (!node)
11608
+ return undefined;
11609
+ if (React.isValidElement(node) && typeof node.type === 'function') {
11610
+ return node.type;
11611
+ }
11612
+ return undefined;
11613
+ };
11209
11614
  const handleItemClick = (item) => {
11210
11615
  if (item.disabled)
11211
11616
  return;
@@ -11225,10 +11630,14 @@ const Toolbar = ({ items, size = 'm', variant = 'default', position = 'top', ful
11225
11630
  const renderItem = (item) => {
11226
11631
  switch (item.type) {
11227
11632
  case 'button':
11228
- return (jsxRuntimeExports.jsxs("button", { className: clsx('designbase-toolbar__item', 'designbase-toolbar__item--button', {
11229
- 'designbase-toolbar__item--active': item.active,
11230
- 'designbase-toolbar__item--disabled': item.disabled,
11231
- }), onClick: () => handleItemClick(item), disabled: item.disabled, title: item.tooltip, children: [item.icon && (jsxRuntimeExports.jsx("span", { className: "designbase-toolbar__item-icon", children: item.icon })), item.label && (jsxRuntimeExports.jsx("span", { className: "designbase-toolbar__item-label", children: item.label }))] }, item.id));
11633
+ {
11634
+ const isIconOnly = !item.label;
11635
+ const StartIconComp = getIconComponentFromNode(item.icon);
11636
+ return (jsxRuntimeExports.jsx(Button, { variant: "tertiary", size: mapSizeToButtonSize(size), className: clsx('designbase-toolbar__item', 'designbase-toolbar__item--button', {
11637
+ 'designbase-toolbar__item--active': item.active,
11638
+ 'designbase-toolbar__item--disabled': item.disabled,
11639
+ }), iconOnly: isIconOnly, startIcon: !isIconOnly ? StartIconComp : undefined, onPress: () => handleItemClick(item), disabled: item.disabled, "aria-label": isIconOnly ? (item.tooltip || item.label || item.id) : undefined, title: item.tooltip, children: isIconOnly && item.icon ? item.icon : item.label }, item.id));
11640
+ }
11232
11641
  case 'dropdown':
11233
11642
  return (jsxRuntimeExports.jsxs("div", { className: clsx('designbase-toolbar__item', 'designbase-toolbar__item--dropdown', {
11234
11643
  'designbase-toolbar__item--open': openDropdown === item.id,
@@ -11263,219 +11672,211 @@ const Toolbar = ({ items, size = 'm', variant = 'default', position = 'top', ful
11263
11672
  return (jsxRuntimeExports.jsx("div", { className: classes, children: jsxRuntimeExports.jsxs("div", { className: "designbase-toolbar__content", children: [Object.entries(groupedItems).map(([groupName, groupItems], groupIndex) => (jsxRuntimeExports.jsxs("div", { className: "designbase-toolbar__group", children: [groupItems.map(renderItem), groupIndex < Object.keys(groupedItems).length - 1 && (jsxRuntimeExports.jsx("div", { className: "designbase-toolbar__group-separator" }))] }, groupName))), children && (jsxRuntimeExports.jsx("div", { className: "designbase-toolbar__children", children: children }))] }) }));
11264
11673
  };
11265
11674
 
11266
- const Tooltip = ({ content, children, position = 'top', size = 'm', variant = 'default', delay = 200, hideDelay = 0, alwaysShow = false, disabled = false, maxWidth = 200, showArrow = true, className, ...props }) => {
11267
- const [isVisible, setIsVisible] = useState(false);
11268
- const [tooltipStyle, setTooltipStyle] = useState({});
11675
+ const GAP = 8; // 트리거와의 간격
11676
+ const ARW = 6; // 화살표 반쪽 길이(삼각형 변 길이)
11677
+ const PAD = 8; // 화살표가 박스 안에서 안전하게 보일 최소 여백
11678
+ const Tooltip = ({ content, children, position = 'top', size = 'm', variant = 'default', delay = 200, hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 240, showArrow = true, className, ...props }) => {
11679
+ const [visible, setVisible] = useState(false);
11680
+ const [style, setStyle] = useState({});
11269
11681
  const [arrowStyle, setArrowStyle] = useState({});
11270
- // 🔑 DOM 래퍼(ref/이벤트가 항상 붙도록)
11271
- const triggerWrapperRef = useRef(null);
11682
+ const [placementGroup, setPlacementGroup] = useState('top');
11683
+ const triggerRef = useRef(null);
11272
11684
  const tooltipRef = useRef(null);
11273
- // 브라우저 setTimeout은 number 반환
11274
- const timeoutRef = useRef(null);
11685
+ const timers = useRef({});
11686
+ const rafId = useRef(null);
11275
11687
  const clearTimer = () => {
11276
- if (timeoutRef.current !== null) {
11277
- window.clearTimeout(timeoutRef.current);
11278
- timeoutRef.current = null;
11688
+ if (timers.current.t) {
11689
+ clearTimeout(timers.current.t);
11690
+ timers.current.t = undefined;
11279
11691
  }
11280
11692
  };
11281
- // 툴팁 위치 계산
11693
+ const groupOf = (p) => p.startsWith('top') ? 'top' :
11694
+ p.startsWith('bottom') ? 'bottom' :
11695
+ p.startsWith('left') ? 'left' : 'right';
11696
+ // 위치 계산 (⚠️ 뷰포트 클램핑 후 화살표도 보정)
11282
11697
  const calculatePosition = useCallback(() => {
11283
- if (!triggerWrapperRef.current || !tooltipRef.current)
11698
+ if (!triggerRef.current || !tooltipRef.current)
11284
11699
  return;
11285
- const triggerRect = triggerWrapperRef.current.getBoundingClientRect();
11286
- const tooltipRect = tooltipRef.current.getBoundingClientRect();
11287
- const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
11288
- const scrollY = window.pageYOffset || document.documentElement.scrollTop;
11289
- let top = 0;
11290
- let left = 0;
11291
- let arrowTop = 0;
11292
- let arrowLeft = 0;
11293
- const isVertical = position.startsWith('top') || position.startsWith('bottom');
11294
- const isHorizontal = position.startsWith('left') || position.startsWith('right');
11700
+ const tRect = triggerRef.current.getBoundingClientRect();
11701
+ const pRect = tooltipRef.current.getBoundingClientRect();
11702
+ let top = 0, left = 0;
11703
+ // 1차: 이상적(미보정) 화살표 좌표
11704
+ let aTop = 0, aLeft = 0;
11295
11705
  switch (position) {
11296
11706
  case 'top':
11297
- top = triggerRect.top + scrollY - tooltipRect.height - 8;
11298
- left = triggerRect.left + scrollX + triggerRect.width / 2 - tooltipRect.width / 2;
11299
- // CSS에서 화살표는 자동으로 중앙에 위치하므로 스타일 제거
11300
- arrowTop = 0;
11301
- arrowLeft = 0;
11707
+ top = tRect.top - pRect.height - GAP;
11708
+ left = tRect.left + tRect.width / 2 - pRect.width / 2;
11709
+ aTop = pRect.height;
11710
+ aLeft = pRect.width / 2 - ARW;
11302
11711
  break;
11303
11712
  case 'top-start':
11304
- top = triggerRect.top + scrollY - tooltipRect.height - 8;
11305
- left = triggerRect.left + scrollX;
11306
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11307
- arrowTop = 0;
11308
- arrowLeft = 0;
11713
+ top = tRect.top - pRect.height - GAP;
11714
+ left = tRect.left;
11715
+ aTop = pRect.height;
11716
+ aLeft = PAD;
11309
11717
  break;
11310
11718
  case 'top-end':
11311
- top = triggerRect.top + scrollY - tooltipRect.height - 8;
11312
- left = triggerRect.right + scrollX - tooltipRect.width;
11313
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11314
- arrowTop = 0;
11315
- arrowLeft = 0;
11719
+ top = tRect.top - pRect.height - GAP;
11720
+ left = tRect.right - pRect.width;
11721
+ aTop = pRect.height;
11722
+ aLeft = pRect.width - PAD;
11316
11723
  break;
11317
11724
  case 'bottom':
11318
- top = triggerRect.bottom + scrollY + 8;
11319
- left = triggerRect.left + scrollX + triggerRect.width / 2 - tooltipRect.width / 2;
11320
- // CSS에서 화살표는 자동으로 중앙에 위치하므로 스타일 제거
11321
- arrowTop = 0;
11322
- arrowLeft = 0;
11725
+ top = tRect.bottom + GAP;
11726
+ left = tRect.left + tRect.width / 2 - pRect.width / 2;
11727
+ aTop = -ARW;
11728
+ aLeft = pRect.width / 2 - ARW;
11323
11729
  break;
11324
11730
  case 'bottom-start':
11325
- top = triggerRect.bottom + scrollY + 8;
11326
- left = triggerRect.left + scrollX;
11327
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11328
- arrowTop = 0;
11329
- arrowLeft = 0;
11731
+ top = tRect.bottom + GAP;
11732
+ left = tRect.left;
11733
+ aTop = -ARW;
11734
+ aLeft = PAD;
11330
11735
  break;
11331
11736
  case 'bottom-end':
11332
- top = triggerRect.bottom + scrollY + 8;
11333
- left = triggerRect.right + scrollX - tooltipRect.width;
11334
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11335
- arrowTop = 0;
11336
- arrowLeft = 0;
11737
+ top = tRect.bottom + GAP;
11738
+ left = tRect.right - pRect.width;
11739
+ aTop = -ARW;
11740
+ aLeft = pRect.width - PAD;
11337
11741
  break;
11338
11742
  case 'left':
11339
- top = triggerRect.top + scrollY + triggerRect.height / 2 - tooltipRect.height / 2;
11340
- left = triggerRect.left + scrollX - tooltipRect.width - 8;
11341
- // CSS에서 화살표는 자동으로 중앙에 위치하므로 스타일 제거
11342
- arrowTop = 0;
11343
- arrowLeft = 0;
11743
+ top = tRect.top + tRect.height / 2 - pRect.height / 2;
11744
+ left = tRect.left - pRect.width - GAP;
11745
+ aTop = pRect.height / 2 - ARW;
11746
+ aLeft = pRect.width;
11344
11747
  break;
11345
11748
  case 'left-start':
11346
- top = triggerRect.top + scrollY;
11347
- left = triggerRect.left + scrollX - tooltipRect.width - 8;
11348
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11349
- arrowTop = 0;
11350
- arrowLeft = 0;
11749
+ top = tRect.top;
11750
+ left = tRect.left - pRect.width - GAP;
11751
+ aTop = PAD;
11752
+ aLeft = pRect.width;
11351
11753
  break;
11352
11754
  case 'left-end':
11353
- top = triggerRect.bottom + scrollY - tooltipRect.height;
11354
- left = triggerRect.left + scrollX - tooltipRect.width - 8;
11355
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11356
- arrowTop = 0;
11357
- arrowLeft = 0;
11755
+ top = tRect.bottom - pRect.height;
11756
+ left = tRect.left - pRect.width - GAP;
11757
+ aTop = pRect.height - PAD;
11758
+ aLeft = pRect.width;
11358
11759
  break;
11359
11760
  case 'right':
11360
- top = triggerRect.top + scrollY + triggerRect.height / 2 - tooltipRect.height / 2;
11361
- left = triggerRect.right + scrollX + 8;
11362
- // CSS에서 화살표는 자동으로 중앙에 위치하므로 스타일 제거
11363
- arrowTop = 0;
11364
- arrowLeft = 0;
11761
+ top = tRect.top + tRect.height / 2 - pRect.height / 2;
11762
+ left = tRect.right + GAP;
11763
+ aTop = pRect.height / 2 - ARW;
11764
+ aLeft = -ARW;
11365
11765
  break;
11366
11766
  case 'right-start':
11367
- top = triggerRect.top + scrollY;
11368
- left = triggerRect.right + scrollX + 8;
11369
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11370
- arrowTop = 0;
11371
- arrowLeft = 0;
11767
+ top = tRect.top;
11768
+ left = tRect.right + GAP;
11769
+ aTop = PAD;
11770
+ aLeft = -ARW;
11372
11771
  break;
11373
11772
  case 'right-end':
11374
- top = triggerRect.bottom + scrollY - tooltipRect.height;
11375
- left = triggerRect.right + scrollX + 8;
11376
- // CSS에서 화살표는 자동으로 위치하므로 스타일 제거
11377
- arrowTop = 0;
11378
- arrowLeft = 0;
11773
+ top = tRect.bottom - pRect.height;
11774
+ left = tRect.right + GAP;
11775
+ aTop = pRect.height - PAD;
11776
+ aLeft = -ARW;
11379
11777
  break;
11380
11778
  }
11381
- // 화면 경계 체크 및 조정
11382
- const viewportWidth = window.innerWidth;
11383
- const viewportHeight = window.innerHeight;
11384
- // 수평 경계
11385
- if (left < 8) {
11779
+ // 뷰포트 클램핑
11780
+ const vw = window.innerWidth;
11781
+ const vh = window.innerHeight;
11782
+ if (left < 8)
11386
11783
  left = 8;
11387
- if (isHorizontal)
11388
- arrowLeft = Math.max(arrowLeft, 4);
11389
- }
11390
- else if (left + tooltipRect.width > viewportWidth - 8) {
11391
- left = viewportWidth - tooltipRect.width - 8;
11392
- if (isHorizontal)
11393
- arrowLeft = Math.min(arrowLeft, tooltipRect.width - 4);
11394
- }
11395
- // 수직 경계
11396
- if (top < 8) {
11784
+ if (left + pRect.width > vw - 8)
11785
+ left = vw - pRect.width - 8;
11786
+ if (top < 8)
11397
11787
  top = 8;
11398
- if (isVertical)
11399
- arrowTop = Math.max(arrowTop, 4);
11788
+ if (top + pRect.height > vh - 8)
11789
+ top = vh - pRect.height - 8;
11790
+ // 🔁 클램핑으로 박스 위치가 바뀌었을 수 있으니, 화살표를 박스 내부에서 다시 보정
11791
+ const g = groupOf(position);
11792
+ if (g === 'top' || g === 'bottom') {
11793
+ // 트리거 중앙 X 를 툴팁 좌표계로 변환
11794
+ const triggerCenterX = tRect.left + tRect.width / 2;
11795
+ const localX = triggerCenterX - left - ARW; // 화살표 기준점을 고려
11796
+ // 8px ~ (width-8px) 범위로 제한
11797
+ aLeft = Math.min(pRect.width - PAD, Math.max(PAD, localX));
11798
+ // aTop은 이미 위/아래에 고정(-ARW 또는 높이)
11400
11799
  }
11401
- else if (top + tooltipRect.height > viewportHeight - 8) {
11402
- top = viewportHeight - tooltipRect.height - 8;
11403
- if (isVertical)
11404
- arrowTop = Math.min(arrowTop, tooltipRect.height - 4);
11800
+ else {
11801
+ const triggerCenterY = tRect.top + tRect.height / 2;
11802
+ const localY = triggerCenterY - top - ARW;
11803
+ aTop = Math.min(pRect.height - PAD, Math.max(PAD, localY));
11804
+ // aLeft는 이미 좌/우에 고정(-ARW 또는 너비)
11405
11805
  }
11406
- setTooltipStyle({
11806
+ setStyle({
11407
11807
  position: 'fixed',
11408
- top: `${top}px`,
11409
- left: `${left}px`,
11410
- maxWidth: `${maxWidth}px`,
11411
- zIndex: 9999
11808
+ top,
11809
+ left,
11810
+ maxWidth,
11811
+ zIndex: 9999,
11812
+ pointerEvents: 'none', // hover 유지
11412
11813
  });
11413
- // CSS에서 화살표 위치를 자동으로 처리하므로 인라인 스타일 제거
11414
- setArrowStyle({});
11814
+ setArrowStyle({ position: 'absolute', top: aTop, left: aLeft });
11815
+ setPlacementGroup(g);
11415
11816
  }, [position, maxWidth]);
11416
- // 툴팁 표시
11417
- const showTooltip = useCallback(() => {
11418
- if (disabled || alwaysShow)
11817
+ // 초기 페인트 전에 위치 확정
11818
+ useLayoutEffect(() => {
11819
+ if (visible || alwaysShow)
11820
+ calculatePosition();
11821
+ }, [visible, alwaysShow, calculatePosition]);
11822
+ // 스크롤/리사이즈/크기변화 추적 (rAF 스로틀)
11823
+ useEffect(() => {
11824
+ if (!(visible || alwaysShow))
11419
11825
  return;
11420
- clearTimer();
11421
- timeoutRef.current = window.setTimeout(() => {
11422
- setIsVisible(true);
11423
- requestAnimationFrame(() => {
11826
+ const onMove = () => {
11827
+ if (rafId.current != null)
11828
+ cancelAnimationFrame(rafId.current);
11829
+ rafId.current = requestAnimationFrame(() => {
11424
11830
  calculatePosition();
11831
+ rafId.current = null;
11425
11832
  });
11426
- }, delay);
11427
- }, [disabled, alwaysShow, delay, calculatePosition]);
11428
- // 툴팁 숨김
11429
- const hideTooltip = useCallback(() => {
11430
- if (disabled || alwaysShow)
11833
+ };
11834
+ window.addEventListener('scroll', onMove, { capture: true, passive: true });
11835
+ window.addEventListener('resize', onMove, { passive: true });
11836
+ let ro = null;
11837
+ const ResizeObs = window.ResizeObserver;
11838
+ if (ResizeObs) {
11839
+ ro = new ResizeObs(() => onMove());
11840
+ if (tooltipRef.current)
11841
+ ro.observe(tooltipRef.current);
11842
+ if (triggerRef.current)
11843
+ ro.observe(triggerRef.current);
11844
+ }
11845
+ return () => {
11846
+ window.removeEventListener('scroll', onMove, { capture: true });
11847
+ window.removeEventListener('resize', onMove);
11848
+ ro?.disconnect?.();
11849
+ if (rafId.current != null)
11850
+ cancelAnimationFrame(rafId.current);
11851
+ };
11852
+ }, [visible, alwaysShow, calculatePosition]);
11853
+ // show/hide
11854
+ const show = useCallback(() => {
11855
+ if (disabled)
11431
11856
  return;
11432
11857
  clearTimer();
11433
- timeoutRef.current = window.setTimeout(() => {
11434
- setIsVisible(false);
11435
- }, hideDelay);
11436
- }, [disabled, alwaysShow, hideDelay]);
11437
- // 키보드 이스케이프
11438
- const handleKeyDown = useCallback((e) => {
11439
- if (e.key === 'Escape')
11440
- hideTooltip();
11441
- }, [hideTooltip]);
11442
- // 리사이즈/스크롤 중 위치 업데이트
11443
- useEffect(() => {
11444
- if (!isVisible)
11858
+ timers.current.t = setTimeout(() => setVisible(true), Math.max(0, delay));
11859
+ }, [disabled, delay]);
11860
+ const hide = useCallback(() => {
11861
+ if (disabled)
11445
11862
  return;
11446
- const onResize = () => calculatePosition();
11447
- const onScroll = () => calculatePosition();
11448
- window.addEventListener('resize', onResize);
11449
- window.addEventListener('scroll', onScroll, true);
11450
- return () => {
11451
- window.removeEventListener('resize', onResize);
11452
- window.removeEventListener('scroll', onScroll, true);
11453
- };
11454
- }, [isVisible, calculatePosition]);
11455
- // 언마운트 시 타이머 정리
11863
+ clearTimer();
11864
+ timers.current.t = setTimeout(() => setVisible(false), Math.max(0, hideDelay));
11865
+ }, [disabled, hideDelay]);
11456
11866
  useEffect(() => () => clearTimer(), []);
11457
- // 항상표시 모드
11458
- useEffect(() => {
11459
- if (alwaysShow && !disabled) {
11460
- setIsVisible(true);
11461
- requestAnimationFrame(() => calculatePosition());
11462
- }
11463
- else if (!alwaysShow) {
11464
- setIsVisible(false);
11867
+ useEffect(() => { if (!disabled && alwaysShow)
11868
+ setVisible(true);
11869
+ else if (!alwaysShow)
11870
+ setVisible(false); }, [alwaysShow, disabled]);
11871
+ const onKeyDown = useCallback((e) => {
11872
+ if (e.key === 'Escape') {
11873
+ clearTimer();
11874
+ setVisible(false);
11465
11875
  }
11466
- }, [alwaysShow, disabled, calculatePosition]);
11467
- // content가 동적으로 바뀌어도 위치 갱신
11468
- useEffect(() => {
11469
- if (!isVisible)
11470
- return;
11471
- const ro = new ResizeObserver(() => calculatePosition());
11472
- if (tooltipRef.current)
11473
- ro.observe(tooltipRef.current);
11474
- return () => ro.disconnect();
11475
- }, [isVisible, calculatePosition, content]);
11476
- const classes = clsx('designbase-tooltip', `designbase-tooltip--${size}`, `designbase-tooltip--${variant}`, `designbase-tooltip--${position}`, { 'designbase-tooltip--visible': isVisible, 'designbase-tooltip--disabled': disabled }, className);
11876
+ }, []);
11877
+ const classes = clsx('designbase-tooltip', `designbase-tooltip--${size}`, `designbase-tooltip--${variant}`, `designbase-tooltip--${position}`, { 'designbase-tooltip--visible': visible || alwaysShow, 'designbase-tooltip--disabled': disabled }, className);
11477
11878
  const arrowClasses = clsx('designbase-tooltip__arrow', `designbase-tooltip__arrow--${position}`);
11478
- return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("span", { ref: triggerWrapperRef, className: "designbase-tooltip__trigger", onMouseEnter: showTooltip, onMouseLeave: hideTooltip, onFocus: showTooltip, onBlur: hideTooltip, onKeyDown: handleKeyDown, tabIndex: 0, "aria-describedby": isVisible ? 'designbase-tooltip' : undefined, children: children }), isVisible && (jsxRuntimeExports.jsxs("div", { ref: tooltipRef, className: classes, style: tooltipStyle, role: "tooltip", id: "designbase-tooltip", "aria-hidden": !isVisible, ...props, children: [jsxRuntimeExports.jsx("div", { className: "designbase-tooltip__content", children: content }), showArrow && jsxRuntimeExports.jsx("div", { className: arrowClasses, style: arrowStyle })] }))] }));
11879
+ return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("span", { ref: triggerRef, className: "designbase-tooltip__trigger", onMouseEnter: show, onMouseLeave: hide, onFocus: show, onBlur: hide, onKeyDown: onKeyDown, tabIndex: 0, "aria-describedby": visible || alwaysShow ? 'db-tooltip' : undefined, children: children }), (visible || alwaysShow) && (jsxRuntimeExports.jsxs("div", { ref: tooltipRef, className: classes, style: style, role: "tooltip", id: "db-tooltip", "aria-hidden": !(visible || alwaysShow), "data-placement-group": placementGroup, ...props, children: [jsxRuntimeExports.jsx("div", { className: "designbase-tooltip__content", children: content }), showArrow && jsxRuntimeExports.jsx("div", { className: arrowClasses, style: arrowStyle })] }))] }));
11479
11880
  };
11480
11881
  Tooltip.displayName = 'Tooltip';
11481
11882
 
@@ -11950,5 +12351,5 @@ const toggleTheme = () => {
11950
12351
  console.log('toggleTheme called');
11951
12352
  };
11952
12353
 
11953
- export { Accordion, Alert, AnimationBackground, AnimationText, AudioPlayer, Avatar, Backdrop, Badge, Banner, BottomSheet, Breadcrumbs, Button, Calendar, Card, Carousel, Checkbox, Chip, Confirm, Container, ContextMenu, 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, Navbar, Pagination, Popover, Progress, ProgressStep, Progressbar, Radio, 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 };
12354
+ export { Accordion, Alert, AnimationBackground, AnimationText, AudioPlayer, Avatar, Backdrop, Badge, Banner, BottomSheet, Breadcrumbs, Button, Calendar, Card, Carousel, Checkbox, Chip, ColorPicker$1 as ColorPicker, Confirm, Container, ContextMenu, 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, RangeSlider, Rating, Reorder, ResizablePanels, ScrollArea, SearchBar, Section, SegmentControl, Select, Share, Sidebar, Skeleton, Spinner, SplitView, Stack, Stat, Stepper, Table, Tabs, Textarea, TimePicker$1 as TimePicker, Timeline, Toast, Toggle, Toolbar, Tooltip, VideoPlayer, YouTubePlayer, getTheme, setTheme, toggleTheme };
11954
12355
  //# sourceMappingURL=index.esm.js.map