@designbasekorea/ui 0.1.43 → 0.1.45

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,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, forwardRef, useId, useLayoutEffect } from 'react';
3
- import { ChevronDownIcon, StarIcon, TrendingUpIcon, StarFilledIcon, CartIcon, CloseIcon, ArrowRightIcon, InfoFilledIcon, ErrorFilledIcon, WarningFilledIcon, CircleCheckFilledIcon, RefreshIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, RepeatIcon, MuteFilledIcon, VolumeUpIcon, SettingsIcon, UserIcon, HideIcon, ShowIcon, SearchIcon, ChevronUpIcon, GalleryIcon, HeartIcon, BookmarkIcon, ShareAltIcon, DownloadIcon, ArrowLeftIcon, ShrinkIcon, ExpandIcon, DoneIcon as DoneIcon$1, CopyIcon, BulbIcon, CloudCloseIcon, BellActiveIcon, AwardIcon, CalendarIcon, PlusIcon, ErrorIcon, ClockIcon, MinusIcon as MinusIcon$1, VideoIcon, CodeIcon, WriteIcon, UploadIcon, XIcon, ArrowBarLeftIcon, ArrowBarRightIcon, StarHalfIcon, MoveIcon, MoreVerticalIcon, ArrowDownIcon, ArrowUpLeftIcon, ArrowUpRightIcon, ArrowDownLeftIcon, ArrowDownRightIcon, FacebookIcon, InstagramIcon, LinkedinIcon, PinterestIcon, WhatsappIcon, TelegramIcon, MailIcon, LinkIcon, ScanQrcodeIcon, VerticalArrowsIcon, CaretUpdownFilledIcon } from '@designbasekorea/icons';
2
+ import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, useLayoutEffect, forwardRef, useId } from 'react';
3
+ import { ChevronDownIcon, StarIcon, TrendingUpIcon, StarFilledIcon, CartIcon, CloseIcon, ArrowRightIcon, InfoFilledIcon, ErrorFilledIcon, WarningFilledIcon, CircleCheckFilledIcon, RefreshIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, RepeatIcon, MuteFilledIcon, VolumeUpIcon, SettingsIcon, UserIcon, HideIcon, ShowIcon, SearchIcon, ChevronUpIcon, GalleryIcon, HeartIcon, BookmarkIcon, ShareAltIcon, DownloadIcon, ArrowLeftIcon, ShrinkIcon, ExpandIcon, DoneIcon as DoneIcon$1, CopyIcon, BulbIcon, CloudCloseIcon, BellActiveIcon, AwardIcon, CalendarIcon, PlusIcon, ErrorIcon, ClockIcon, MinusIcon as MinusIcon$1, VideoIcon, CodeIcon, WriteIcon, UploadIcon, ArrowBarLeftIcon, ArrowBarRightIcon, StarHalfIcon, MoveIcon, MoreVerticalIcon, ArrowDownIcon, ArrowUpLeftIcon, ArrowUpRightIcon, ArrowDownLeftIcon, ArrowDownRightIcon, FacebookIcon, XIcon, InstagramIcon, LinkedinIcon, PinterestIcon, WhatsappIcon, TelegramIcon, MailIcon, LinkIcon, ScanQrcodeIcon, VerticalArrowsIcon, CaretUpdownFilledIcon } from '@designbasekorea/icons';
4
4
  import { flushSync, createPortal } from 'react-dom';
5
5
 
6
6
  function r(e){var t,f,n="";if("string"==typeof e||"number"==typeof e)n+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=" "),n+=f);}else for(f in e)e[f]&&(n&&(n+=" "),n+=f);return n}function clsx(){for(var e,t,f=0,n="",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=" "),n+=t);return n}
@@ -2194,7 +2194,215 @@ const Spinner = ({ type = 'circular', size = 'm', color, speed = 1, label = '로
2194
2194
  };
2195
2195
  Spinner.displayName = 'Spinner';
2196
2196
 
2197
- const Button = forwardRef(({ variant = 'primary', size = 'm', radius, fullWidth = false, disabled = false, loading = false, iconOnly = false, startIcon: StartIcon, endIcon: EndIcon, className, children, onPress, type = 'button', ...props }, forwardedRef) => {
2197
+ const GAP$1 = 8; // 트리거와의 간격
2198
+ const ARW = 6; // 화살표 반쪽 길이(삼각형 변 길이)
2199
+ const PAD = 8; // 화살표가 박스 안에서 안전하게 보일 최소 여백
2200
+ const Tooltip = ({ content, children, position = 'top', size = 'm', variant = 'default', delay = 200, hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 240, showArrow = true, className, ...props }) => {
2201
+ const [visible, setVisible] = useState(false);
2202
+ const [style, setStyle] = useState({});
2203
+ const [arrowStyle, setArrowStyle] = useState({});
2204
+ const [placementGroup, setPlacementGroup] = useState('top');
2205
+ const triggerRef = useRef(null);
2206
+ const tooltipRef = useRef(null);
2207
+ const timers = useRef({});
2208
+ const rafId = useRef(null);
2209
+ const clearTimer = () => {
2210
+ if (timers.current.t) {
2211
+ clearTimeout(timers.current.t);
2212
+ timers.current.t = undefined;
2213
+ }
2214
+ };
2215
+ const groupOf = (p) => p.startsWith('top') ? 'top' :
2216
+ p.startsWith('bottom') ? 'bottom' :
2217
+ p.startsWith('left') ? 'left' : 'right';
2218
+ // 위치 계산 (⚠️ 뷰포트 클램핑 후 화살표도 보정)
2219
+ const calculatePosition = useCallback(() => {
2220
+ if (!triggerRef.current || !tooltipRef.current)
2221
+ return;
2222
+ const tRect = triggerRef.current.getBoundingClientRect();
2223
+ const pRect = tooltipRef.current.getBoundingClientRect();
2224
+ let top = 0, left = 0;
2225
+ // 1차: 이상적(미보정) 화살표 좌표
2226
+ let aTop = 0, aLeft = 0;
2227
+ switch (position) {
2228
+ case 'top':
2229
+ top = tRect.top - pRect.height - GAP$1;
2230
+ left = tRect.left + tRect.width / 2 - pRect.width / 2;
2231
+ aTop = pRect.height;
2232
+ aLeft = pRect.width / 2 - ARW;
2233
+ break;
2234
+ case 'top-start':
2235
+ top = tRect.top - pRect.height - GAP$1;
2236
+ left = tRect.left;
2237
+ aTop = pRect.height;
2238
+ aLeft = PAD;
2239
+ break;
2240
+ case 'top-end':
2241
+ top = tRect.top - pRect.height - GAP$1;
2242
+ left = tRect.right - pRect.width;
2243
+ aTop = pRect.height;
2244
+ aLeft = pRect.width - PAD;
2245
+ break;
2246
+ case 'bottom':
2247
+ top = tRect.bottom + GAP$1;
2248
+ left = tRect.left + tRect.width / 2 - pRect.width / 2;
2249
+ aTop = -ARW;
2250
+ aLeft = pRect.width / 2 - ARW;
2251
+ break;
2252
+ case 'bottom-start':
2253
+ top = tRect.bottom + GAP$1;
2254
+ left = tRect.left;
2255
+ aTop = -ARW;
2256
+ aLeft = PAD;
2257
+ break;
2258
+ case 'bottom-end':
2259
+ top = tRect.bottom + GAP$1;
2260
+ left = tRect.right - pRect.width;
2261
+ aTop = -ARW;
2262
+ aLeft = pRect.width - PAD;
2263
+ break;
2264
+ case 'left':
2265
+ top = tRect.top + tRect.height / 2 - pRect.height / 2;
2266
+ left = tRect.left - pRect.width - GAP$1;
2267
+ aTop = pRect.height / 2 - ARW;
2268
+ aLeft = pRect.width;
2269
+ break;
2270
+ case 'left-start':
2271
+ top = tRect.top;
2272
+ left = tRect.left - pRect.width - GAP$1;
2273
+ aTop = PAD;
2274
+ aLeft = pRect.width;
2275
+ break;
2276
+ case 'left-end':
2277
+ top = tRect.bottom - pRect.height;
2278
+ left = tRect.left - pRect.width - GAP$1;
2279
+ aTop = pRect.height - PAD;
2280
+ aLeft = pRect.width;
2281
+ break;
2282
+ case 'right':
2283
+ top = tRect.top + tRect.height / 2 - pRect.height / 2;
2284
+ left = tRect.right + GAP$1;
2285
+ aTop = pRect.height / 2 - ARW;
2286
+ aLeft = -ARW;
2287
+ break;
2288
+ case 'right-start':
2289
+ top = tRect.top;
2290
+ left = tRect.right + GAP$1;
2291
+ aTop = PAD;
2292
+ aLeft = -ARW;
2293
+ break;
2294
+ case 'right-end':
2295
+ top = tRect.bottom - pRect.height;
2296
+ left = tRect.right + GAP$1;
2297
+ aTop = pRect.height - PAD;
2298
+ aLeft = -ARW;
2299
+ break;
2300
+ }
2301
+ // 뷰포트 클램핑
2302
+ const vw = window.innerWidth;
2303
+ const vh = window.innerHeight;
2304
+ if (left < 8)
2305
+ left = 8;
2306
+ if (left + pRect.width > vw - 8)
2307
+ left = vw - pRect.width - 8;
2308
+ if (top < 8)
2309
+ top = 8;
2310
+ if (top + pRect.height > vh - 8)
2311
+ top = vh - pRect.height - 8;
2312
+ // 🔁 클램핑으로 박스 위치가 바뀌었을 수 있으니, 화살표를 박스 내부에서 다시 보정
2313
+ const g = groupOf(position);
2314
+ if (g === 'top' || g === 'bottom') {
2315
+ // 트리거 중앙 X 를 툴팁 좌표계로 변환
2316
+ const triggerCenterX = tRect.left + tRect.width / 2;
2317
+ const localX = triggerCenterX - left - ARW; // 화살표 기준점을 고려
2318
+ // 8px ~ (width-8px) 범위로 제한
2319
+ aLeft = Math.min(pRect.width - PAD, Math.max(PAD, localX));
2320
+ // aTop은 이미 위/아래에 고정(-ARW 또는 높이)
2321
+ }
2322
+ else {
2323
+ const triggerCenterY = tRect.top + tRect.height / 2;
2324
+ const localY = triggerCenterY - top - ARW;
2325
+ aTop = Math.min(pRect.height - PAD, Math.max(PAD, localY));
2326
+ // aLeft는 이미 좌/우에 고정(-ARW 또는 너비)
2327
+ }
2328
+ setStyle({
2329
+ position: 'fixed',
2330
+ top,
2331
+ left,
2332
+ maxWidth,
2333
+ zIndex: 9999,
2334
+ pointerEvents: 'none', // hover 유지
2335
+ });
2336
+ setArrowStyle({ position: 'absolute', top: aTop, left: aLeft });
2337
+ setPlacementGroup(g);
2338
+ }, [position, maxWidth]);
2339
+ // 초기 페인트 전에 위치 확정
2340
+ useLayoutEffect(() => {
2341
+ if (visible || alwaysShow)
2342
+ calculatePosition();
2343
+ }, [visible, alwaysShow, calculatePosition]);
2344
+ // 스크롤/리사이즈/크기변화 추적 (rAF 스로틀)
2345
+ useEffect(() => {
2346
+ if (!(visible || alwaysShow))
2347
+ return;
2348
+ const onMove = () => {
2349
+ if (rafId.current != null)
2350
+ cancelAnimationFrame(rafId.current);
2351
+ rafId.current = requestAnimationFrame(() => {
2352
+ calculatePosition();
2353
+ rafId.current = null;
2354
+ });
2355
+ };
2356
+ window.addEventListener('scroll', onMove, { capture: true, passive: true });
2357
+ window.addEventListener('resize', onMove, { passive: true });
2358
+ let ro = null;
2359
+ const ResizeObs = window.ResizeObserver;
2360
+ if (ResizeObs) {
2361
+ ro = new ResizeObs(() => onMove());
2362
+ if (tooltipRef.current)
2363
+ ro.observe(tooltipRef.current);
2364
+ if (triggerRef.current)
2365
+ ro.observe(triggerRef.current);
2366
+ }
2367
+ return () => {
2368
+ window.removeEventListener('scroll', onMove, { capture: true });
2369
+ window.removeEventListener('resize', onMove);
2370
+ ro?.disconnect?.();
2371
+ if (rafId.current != null)
2372
+ cancelAnimationFrame(rafId.current);
2373
+ };
2374
+ }, [visible, alwaysShow, calculatePosition]);
2375
+ // show/hide
2376
+ const show = useCallback(() => {
2377
+ if (disabled)
2378
+ return;
2379
+ clearTimer();
2380
+ timers.current.t = setTimeout(() => setVisible(true), Math.max(0, delay));
2381
+ }, [disabled, delay]);
2382
+ const hide = useCallback(() => {
2383
+ if (disabled)
2384
+ return;
2385
+ clearTimer();
2386
+ timers.current.t = setTimeout(() => setVisible(false), Math.max(0, hideDelay));
2387
+ }, [disabled, hideDelay]);
2388
+ useEffect(() => () => clearTimer(), []);
2389
+ useEffect(() => { if (!disabled && alwaysShow)
2390
+ setVisible(true);
2391
+ else if (!alwaysShow)
2392
+ setVisible(false); }, [alwaysShow, disabled]);
2393
+ const onKeyDown = useCallback((e) => {
2394
+ if (e.key === 'Escape') {
2395
+ clearTimer();
2396
+ setVisible(false);
2397
+ }
2398
+ }, []);
2399
+ const classes = clsx('designbase-tooltip', `designbase-tooltip--${size}`, `designbase-tooltip--${variant}`, `designbase-tooltip--${position}`, { 'designbase-tooltip--visible': visible || alwaysShow, 'designbase-tooltip--disabled': disabled }, className);
2400
+ const arrowClasses = clsx('designbase-tooltip__arrow', `designbase-tooltip__arrow--${position}`);
2401
+ return (jsxs(Fragment, { children: [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) && (jsxs("div", { ref: tooltipRef, className: classes, style: style, role: "tooltip", id: "db-tooltip", "aria-hidden": !(visible || alwaysShow), "data-placement-group": placementGroup, ...props, children: [jsx("div", { className: "designbase-tooltip__content", children: content }), showArrow && jsx("div", { className: arrowClasses, style: arrowStyle })] }))] }));
2402
+ };
2403
+ Tooltip.displayName = 'Tooltip';
2404
+
2405
+ const Button = forwardRef(({ variant = 'primary', size = 'm', radius, fullWidth = false, disabled = false, loading = false, iconOnly = false, startIcon: StartIcon, endIcon: EndIcon, tooltip, tooltipProps, className, children, onPress, type = 'button', ...props }, forwardedRef) => {
2198
2406
  const ref = $df56164dff5785e2$export$4338b53315abf666(forwardedRef);
2199
2407
  const { buttonProps } = $701a24aa0da5b062$export$ea18c227d4417cc3({
2200
2408
  ...props,
@@ -2258,7 +2466,12 @@ const Button = forwardRef(({ variant = 'primary', size = 'm', radius, fullWidth
2258
2466
  }
2259
2467
  return (jsxs(Fragment, { children: [StartIcon && (jsx(StartIcon, { size: iconSize, className: "designbase-button__start-icon", color: getIconColor() })), children, EndIcon && (jsx(EndIcon, { size: iconSize, className: "designbase-button__end-icon", color: getIconColor() }))] }));
2260
2468
  };
2261
- return (jsx("button", { ...buttonProps, ref: ref, className: classes, "aria-label": iconOnly ? props['aria-label'] || children : undefined, children: renderContent() }));
2469
+ const buttonElement = (jsx("button", { ...buttonProps, ref: ref, className: classes, "aria-label": iconOnly ? props['aria-label'] || children : undefined, children: renderContent() }));
2470
+ // 툴팁이 있는 경우 Tooltip으로 감싸기
2471
+ if (tooltip) {
2472
+ return (jsx(Tooltip, { content: tooltip, position: "top", size: "s", variant: "default", ...tooltipProps, children: buttonElement }));
2473
+ }
2474
+ return buttonElement;
2262
2475
  });
2263
2476
  Button.displayName = 'Button';
2264
2477
 
@@ -8167,9 +8380,9 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
8167
8380
  }
8168
8381
  }
8169
8382
  }, [enableRecentSearches, recentSearchesKey]);
8170
- // 추천 검색어 롤링
8383
+ // 추천 검색어 롤링 (포커스 없이도 계속)
8171
8384
  useEffect(() => {
8172
- if (suggestedSearches.length > 0 && isFocused && !currentValue) {
8385
+ if (suggestedSearches.length > 0 && !currentValue) {
8173
8386
  suggestionIntervalRef.current = setInterval(() => {
8174
8387
  setCurrentSuggestion(prev => (prev + 1) % suggestedSearches.length);
8175
8388
  }, suggestionRollingInterval);
@@ -8185,7 +8398,7 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
8185
8398
  clearInterval(suggestionIntervalRef.current);
8186
8399
  }
8187
8400
  };
8188
- }, [suggestedSearches.length, isFocused, currentValue, suggestionRollingInterval]);
8401
+ }, [suggestedSearches.length, currentValue, suggestionRollingInterval]);
8189
8402
  useEffect(() => {
8190
8403
  if (value !== undefined) {
8191
8404
  setInternalValue(value);
@@ -8281,10 +8494,10 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
8281
8494
  'designbase-search-bar__input--readonly': readOnly,
8282
8495
  });
8283
8496
  // 현재 플레이스홀더 (추천 검색어가 있으면 롤링)
8284
- const currentPlaceholder = suggestedSearches.length > 0 && isFocused && !currentValue
8497
+ const currentPlaceholder = suggestedSearches.length > 0 && !currentValue
8285
8498
  ? suggestedSearches[currentSuggestion]
8286
8499
  : placeholder;
8287
- return (jsxs("div", { className: classes, role: "search", children: [jsxs("div", { className: "designbase-search-bar__container", children: [jsx("div", { className: "designbase-search-bar__search-icon", children: jsx(SearchIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }), jsx("input", { ref: inputRef, type: "text", className: inputClasses, value: currentValue, placeholder: currentPlaceholder, disabled: disabled, readOnly: readOnly, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, "aria-label": "\uAC80\uC0C9\uC5B4 \uC785\uB825", ...props }), currentValue && currentValue.length > 0 && !disabled && !readOnly && (jsx("button", { type: "button", className: "designbase-search-bar__clear-button", onClick: handleClear, "aria-label": "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30", children: jsx(ClearIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }))] }), showRecentSearches && recentSearches.length > 0 && (jsxs("div", { className: "designbase-search-bar__recent-searches", children: [jsxs("div", { className: "designbase-search-bar__recent-header", children: [jsx("span", { className: "designbase-search-bar__recent-title", children: "\uCD5C\uADFC \uAC80\uC0C9\uC5B4" }), jsx("button", { type: "button", className: "designbase-search-bar__clear-all-button", onClick: handleClearAllRecentSearches, "aria-label": "\uBAA8\uB4E0 \uCD5C\uADFC \uAC80\uC0C9\uC5B4 \uC0AD\uC81C", children: "\uC804\uCCB4 \uC0AD\uC81C" })] }), jsx("div", { className: "designbase-search-bar__recent-list", children: recentSearches.map((searchTerm, index) => (jsxs("div", { className: "designbase-search-bar__recent-item", children: [jsx("button", { type: "button", className: "designbase-search-bar__recent-search-button", onClick: () => handleRecentSearchClick(searchTerm), children: searchTerm }), jsx("button", { type: "button", className: "designbase-search-bar__recent-remove-button", onClick: () => handleRemoveRecentSearch(searchTerm), "aria-label": `${searchTerm} 삭제`, children: jsx(XIcon, { size: 16 }) })] }, index))) })] })), suggestedSearches.length > 0 && isFocused && !currentValue && (jsxs("div", { className: "designbase-search-bar__suggestions", children: [jsx("div", { className: "designbase-search-bar__suggestions-header", children: jsx("span", { className: "designbase-search-bar__suggestions-title", children: "\uCD94\uCC9C \uAC80\uC0C9\uC5B4" }) }), jsx("div", { className: "designbase-search-bar__suggestions-list", children: suggestedSearches.map((suggestion, index) => (jsx("button", { type: "button", className: clsx('designbase-search-bar__suggestion-item', {
8500
+ return (jsxs("div", { className: classes, role: "search", children: [jsxs("div", { className: "designbase-search-bar__container", children: [jsx("div", { className: "designbase-search-bar__search-icon", children: jsx(SearchIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }), jsx("input", { ref: inputRef, type: "text", className: inputClasses, value: currentValue, placeholder: currentPlaceholder, disabled: disabled, readOnly: readOnly, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, "aria-label": "\uAC80\uC0C9\uC5B4 \uC785\uB825", ...props }), currentValue && currentValue.length > 0 && !disabled && !readOnly && (jsx("button", { type: "button", className: "designbase-search-bar__clear-button", onClick: handleClear, "aria-label": "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30", children: jsx(ClearIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }))] }), showRecentSearches && recentSearches.length > 0 && (jsxs("div", { className: "designbase-search-bar__recent-searches", children: [jsxs("div", { className: "designbase-search-bar__recent-header", children: [jsx("span", { className: "designbase-search-bar__recent-title", children: "\uCD5C\uADFC \uAC80\uC0C9\uC5B4" }), jsx("button", { type: "button", className: "designbase-search-bar__clear-all-button", onClick: handleClearAllRecentSearches, "aria-label": "\uBAA8\uB4E0 \uCD5C\uADFC \uAC80\uC0C9\uC5B4 \uC0AD\uC81C", children: "\uC804\uCCB4 \uC0AD\uC81C" })] }), jsx("div", { className: "designbase-search-bar__recent-list", children: recentSearches.map((searchTerm, index) => (jsxs("div", { className: "designbase-search-bar__recent-item", children: [jsx("button", { type: "button", className: "designbase-search-bar__recent-search-button", onClick: () => handleRecentSearchClick(searchTerm), children: searchTerm }), jsx("button", { type: "button", className: "designbase-search-bar__recent-remove-button", onClick: () => handleRemoveRecentSearch(searchTerm), "aria-label": `${searchTerm} 삭제`, children: jsx(CloseIcon, { size: 16 }) })] }, index))) })] })), suggestedSearches.length > 0 && isFocused && !currentValue && (jsxs("div", { className: "designbase-search-bar__suggestions", children: [jsx("div", { className: "designbase-search-bar__suggestions-header", children: jsx("span", { className: "designbase-search-bar__suggestions-title", children: "\uCD94\uCC9C \uAC80\uC0C9\uC5B4" }) }), jsx("div", { className: "designbase-search-bar__suggestions-list", children: suggestedSearches.map((suggestion, index) => (jsx("button", { type: "button", className: clsx('designbase-search-bar__suggestion-item', {
8288
8501
  'designbase-search-bar__suggestion-item--active': index === currentSuggestion
8289
8502
  }), onClick: () => handleSuggestionClick(suggestion), children: suggestion }, index))) })] }))] }));
8290
8503
  };
@@ -8513,7 +8726,7 @@ const Pagination = ({ currentPage, totalPages, totalItems, pageSize = 10, pageSi
8513
8726
  };
8514
8727
  Pagination.displayName = 'Pagination';
8515
8728
 
8516
- const GAP$1 = 8; // trigger와 popover 사이 간격
8729
+ const GAP = 8; // trigger와 popover 사이 간격
8517
8730
  const Popover = ({ content, children, title, position = 'top', size = 'm', variant = 'default', trigger = 'click', delay = 0, // 클릭/포커스는 즉시, 호버는 아래에서만 적용
8518
8731
  hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 300, showArrow = true, closeOnOutsideClick = true, closeOnEscape = true, open: controlledOpen, onOpenChange, className, ...props }) => {
8519
8732
  const [internalOpen, setInternalOpen] = useState(false);
@@ -8546,74 +8759,74 @@ hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 300, showArrow
8546
8759
  };
8547
8760
  switch (position) {
8548
8761
  case 'top':
8549
- top = tRect.top - pRect.height - GAP$1;
8762
+ top = tRect.top - pRect.height - GAP;
8550
8763
  left = tRect.left + tRect.width / 2 - pRect.width / 2;
8551
8764
  aTop = pRect.height;
8552
8765
  aLeft = pRect.width / 2 - 4;
8553
8766
  break;
8554
8767
  case 'top-start':
8555
- top = tRect.top - pRect.height - GAP$1;
8768
+ top = tRect.top - pRect.height - GAP;
8556
8769
  left = tRect.left;
8557
8770
  aTop = pRect.height;
8558
8771
  aLeft = 12;
8559
8772
  break;
8560
8773
  case 'top-end':
8561
- top = tRect.top - pRect.height - GAP$1;
8774
+ top = tRect.top - pRect.height - GAP;
8562
8775
  left = tRect.right - pRect.width;
8563
8776
  aTop = pRect.height;
8564
8777
  aLeft = pRect.width - 12;
8565
8778
  break;
8566
8779
  case 'bottom':
8567
- top = tRect.bottom + GAP$1;
8780
+ top = tRect.bottom + GAP;
8568
8781
  left = tRect.left + tRect.width / 2 - pRect.width / 2;
8569
8782
  aTop = -4;
8570
8783
  aLeft = pRect.width / 2 - 4;
8571
8784
  break;
8572
8785
  case 'bottom-start':
8573
- top = tRect.bottom + GAP$1;
8786
+ top = tRect.bottom + GAP;
8574
8787
  left = tRect.left;
8575
8788
  aTop = -4;
8576
8789
  aLeft = 12;
8577
8790
  break;
8578
8791
  case 'bottom-end':
8579
- top = tRect.bottom + GAP$1;
8792
+ top = tRect.bottom + GAP;
8580
8793
  left = tRect.right - pRect.width;
8581
8794
  aTop = -4;
8582
8795
  aLeft = pRect.width - 12;
8583
8796
  break;
8584
8797
  case 'left':
8585
8798
  top = tRect.top + tRect.height / 2 - pRect.height / 2;
8586
- left = tRect.left - pRect.width - GAP$1;
8799
+ left = tRect.left - pRect.width - GAP;
8587
8800
  aTop = pRect.height / 2 - 4;
8588
8801
  aLeft = pRect.width;
8589
8802
  break;
8590
8803
  case 'left-start':
8591
8804
  top = tRect.top;
8592
- left = tRect.left - pRect.width - GAP$1;
8805
+ left = tRect.left - pRect.width - GAP;
8593
8806
  aTop = 12;
8594
8807
  aLeft = pRect.width;
8595
8808
  break;
8596
8809
  case 'left-end':
8597
8810
  top = tRect.bottom - pRect.height;
8598
- left = tRect.left - pRect.width - GAP$1;
8811
+ left = tRect.left - pRect.width - GAP;
8599
8812
  aTop = pRect.height - 12;
8600
8813
  aLeft = pRect.width;
8601
8814
  break;
8602
8815
  case 'right':
8603
8816
  top = tRect.top + tRect.height / 2 - pRect.height / 2;
8604
- left = tRect.right + GAP$1;
8817
+ left = tRect.right + GAP;
8605
8818
  aTop = pRect.height / 2 - 4;
8606
8819
  aLeft = -4;
8607
8820
  break;
8608
8821
  case 'right-start':
8609
8822
  top = tRect.top;
8610
- left = tRect.right + GAP$1;
8823
+ left = tRect.right + GAP;
8611
8824
  aTop = 12;
8612
8825
  aLeft = -4;
8613
8826
  break;
8614
8827
  case 'right-end':
8615
8828
  top = tRect.bottom - pRect.height;
8616
- left = tRect.right + GAP$1;
8829
+ left = tRect.right + GAP;
8617
8830
  aTop = pRect.height - 12;
8618
8831
  aLeft = -4;
8619
8832
  break;
@@ -10686,214 +10899,6 @@ const Toolbar = ({ items, size = 'm', variant = 'default', position = 'top', ful
10686
10899
  return (jsx("div", { className: classes, children: jsxs("div", { className: "designbase-toolbar__content", children: [Object.entries(groupedItems).map(([groupName, groupItems], groupIndex) => (jsxs("div", { className: "designbase-toolbar__group", children: [groupItems.map(renderItem), groupIndex < Object.keys(groupedItems).length - 1 && (jsx("div", { className: "designbase-toolbar__group-separator" }))] }, groupName))), children && (jsx("div", { className: "designbase-toolbar__children", children: children }))] }) }));
10687
10900
  };
10688
10901
 
10689
- const GAP = 8; // 트리거와의 간격
10690
- const ARW = 6; // 화살표 반쪽 길이(삼각형 변 길이)
10691
- const PAD = 8; // 화살표가 박스 안에서 안전하게 보일 최소 여백
10692
- const Tooltip = ({ content, children, position = 'top', size = 'm', variant = 'default', delay = 200, hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 240, showArrow = true, className, ...props }) => {
10693
- const [visible, setVisible] = useState(false);
10694
- const [style, setStyle] = useState({});
10695
- const [arrowStyle, setArrowStyle] = useState({});
10696
- const [placementGroup, setPlacementGroup] = useState('top');
10697
- const triggerRef = useRef(null);
10698
- const tooltipRef = useRef(null);
10699
- const timers = useRef({});
10700
- const rafId = useRef(null);
10701
- const clearTimer = () => {
10702
- if (timers.current.t) {
10703
- clearTimeout(timers.current.t);
10704
- timers.current.t = undefined;
10705
- }
10706
- };
10707
- const groupOf = (p) => p.startsWith('top') ? 'top' :
10708
- p.startsWith('bottom') ? 'bottom' :
10709
- p.startsWith('left') ? 'left' : 'right';
10710
- // 위치 계산 (⚠️ 뷰포트 클램핑 후 화살표도 보정)
10711
- const calculatePosition = useCallback(() => {
10712
- if (!triggerRef.current || !tooltipRef.current)
10713
- return;
10714
- const tRect = triggerRef.current.getBoundingClientRect();
10715
- const pRect = tooltipRef.current.getBoundingClientRect();
10716
- let top = 0, left = 0;
10717
- // 1차: 이상적(미보정) 화살표 좌표
10718
- let aTop = 0, aLeft = 0;
10719
- switch (position) {
10720
- case 'top':
10721
- top = tRect.top - pRect.height - GAP;
10722
- left = tRect.left + tRect.width / 2 - pRect.width / 2;
10723
- aTop = pRect.height;
10724
- aLeft = pRect.width / 2 - ARW;
10725
- break;
10726
- case 'top-start':
10727
- top = tRect.top - pRect.height - GAP;
10728
- left = tRect.left;
10729
- aTop = pRect.height;
10730
- aLeft = PAD;
10731
- break;
10732
- case 'top-end':
10733
- top = tRect.top - pRect.height - GAP;
10734
- left = tRect.right - pRect.width;
10735
- aTop = pRect.height;
10736
- aLeft = pRect.width - PAD;
10737
- break;
10738
- case 'bottom':
10739
- top = tRect.bottom + GAP;
10740
- left = tRect.left + tRect.width / 2 - pRect.width / 2;
10741
- aTop = -ARW;
10742
- aLeft = pRect.width / 2 - ARW;
10743
- break;
10744
- case 'bottom-start':
10745
- top = tRect.bottom + GAP;
10746
- left = tRect.left;
10747
- aTop = -ARW;
10748
- aLeft = PAD;
10749
- break;
10750
- case 'bottom-end':
10751
- top = tRect.bottom + GAP;
10752
- left = tRect.right - pRect.width;
10753
- aTop = -ARW;
10754
- aLeft = pRect.width - PAD;
10755
- break;
10756
- case 'left':
10757
- top = tRect.top + tRect.height / 2 - pRect.height / 2;
10758
- left = tRect.left - pRect.width - GAP;
10759
- aTop = pRect.height / 2 - ARW;
10760
- aLeft = pRect.width;
10761
- break;
10762
- case 'left-start':
10763
- top = tRect.top;
10764
- left = tRect.left - pRect.width - GAP;
10765
- aTop = PAD;
10766
- aLeft = pRect.width;
10767
- break;
10768
- case 'left-end':
10769
- top = tRect.bottom - pRect.height;
10770
- left = tRect.left - pRect.width - GAP;
10771
- aTop = pRect.height - PAD;
10772
- aLeft = pRect.width;
10773
- break;
10774
- case 'right':
10775
- top = tRect.top + tRect.height / 2 - pRect.height / 2;
10776
- left = tRect.right + GAP;
10777
- aTop = pRect.height / 2 - ARW;
10778
- aLeft = -ARW;
10779
- break;
10780
- case 'right-start':
10781
- top = tRect.top;
10782
- left = tRect.right + GAP;
10783
- aTop = PAD;
10784
- aLeft = -ARW;
10785
- break;
10786
- case 'right-end':
10787
- top = tRect.bottom - pRect.height;
10788
- left = tRect.right + GAP;
10789
- aTop = pRect.height - PAD;
10790
- aLeft = -ARW;
10791
- break;
10792
- }
10793
- // 뷰포트 클램핑
10794
- const vw = window.innerWidth;
10795
- const vh = window.innerHeight;
10796
- if (left < 8)
10797
- left = 8;
10798
- if (left + pRect.width > vw - 8)
10799
- left = vw - pRect.width - 8;
10800
- if (top < 8)
10801
- top = 8;
10802
- if (top + pRect.height > vh - 8)
10803
- top = vh - pRect.height - 8;
10804
- // 🔁 클램핑으로 박스 위치가 바뀌었을 수 있으니, 화살표를 박스 내부에서 다시 보정
10805
- const g = groupOf(position);
10806
- if (g === 'top' || g === 'bottom') {
10807
- // 트리거 중앙 X 를 툴팁 좌표계로 변환
10808
- const triggerCenterX = tRect.left + tRect.width / 2;
10809
- const localX = triggerCenterX - left - ARW; // 화살표 기준점을 고려
10810
- // 8px ~ (width-8px) 범위로 제한
10811
- aLeft = Math.min(pRect.width - PAD, Math.max(PAD, localX));
10812
- // aTop은 이미 위/아래에 고정(-ARW 또는 높이)
10813
- }
10814
- else {
10815
- const triggerCenterY = tRect.top + tRect.height / 2;
10816
- const localY = triggerCenterY - top - ARW;
10817
- aTop = Math.min(pRect.height - PAD, Math.max(PAD, localY));
10818
- // aLeft는 이미 좌/우에 고정(-ARW 또는 너비)
10819
- }
10820
- setStyle({
10821
- position: 'fixed',
10822
- top,
10823
- left,
10824
- maxWidth,
10825
- zIndex: 9999,
10826
- pointerEvents: 'none', // hover 유지
10827
- });
10828
- setArrowStyle({ position: 'absolute', top: aTop, left: aLeft });
10829
- setPlacementGroup(g);
10830
- }, [position, maxWidth]);
10831
- // 초기 페인트 전에 위치 확정
10832
- useLayoutEffect(() => {
10833
- if (visible || alwaysShow)
10834
- calculatePosition();
10835
- }, [visible, alwaysShow, calculatePosition]);
10836
- // 스크롤/리사이즈/크기변화 추적 (rAF 스로틀)
10837
- useEffect(() => {
10838
- if (!(visible || alwaysShow))
10839
- return;
10840
- const onMove = () => {
10841
- if (rafId.current != null)
10842
- cancelAnimationFrame(rafId.current);
10843
- rafId.current = requestAnimationFrame(() => {
10844
- calculatePosition();
10845
- rafId.current = null;
10846
- });
10847
- };
10848
- window.addEventListener('scroll', onMove, { capture: true, passive: true });
10849
- window.addEventListener('resize', onMove, { passive: true });
10850
- let ro = null;
10851
- const ResizeObs = window.ResizeObserver;
10852
- if (ResizeObs) {
10853
- ro = new ResizeObs(() => onMove());
10854
- if (tooltipRef.current)
10855
- ro.observe(tooltipRef.current);
10856
- if (triggerRef.current)
10857
- ro.observe(triggerRef.current);
10858
- }
10859
- return () => {
10860
- window.removeEventListener('scroll', onMove, { capture: true });
10861
- window.removeEventListener('resize', onMove);
10862
- ro?.disconnect?.();
10863
- if (rafId.current != null)
10864
- cancelAnimationFrame(rafId.current);
10865
- };
10866
- }, [visible, alwaysShow, calculatePosition]);
10867
- // show/hide
10868
- const show = useCallback(() => {
10869
- if (disabled)
10870
- return;
10871
- clearTimer();
10872
- timers.current.t = setTimeout(() => setVisible(true), Math.max(0, delay));
10873
- }, [disabled, delay]);
10874
- const hide = useCallback(() => {
10875
- if (disabled)
10876
- return;
10877
- clearTimer();
10878
- timers.current.t = setTimeout(() => setVisible(false), Math.max(0, hideDelay));
10879
- }, [disabled, hideDelay]);
10880
- useEffect(() => () => clearTimer(), []);
10881
- useEffect(() => { if (!disabled && alwaysShow)
10882
- setVisible(true);
10883
- else if (!alwaysShow)
10884
- setVisible(false); }, [alwaysShow, disabled]);
10885
- const onKeyDown = useCallback((e) => {
10886
- if (e.key === 'Escape') {
10887
- clearTimer();
10888
- setVisible(false);
10889
- }
10890
- }, []);
10891
- const classes = clsx('designbase-tooltip', `designbase-tooltip--${size}`, `designbase-tooltip--${variant}`, `designbase-tooltip--${position}`, { 'designbase-tooltip--visible': visible || alwaysShow, 'designbase-tooltip--disabled': disabled }, className);
10892
- const arrowClasses = clsx('designbase-tooltip__arrow', `designbase-tooltip__arrow--${position}`);
10893
- return (jsxs(Fragment, { children: [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) && (jsxs("div", { ref: tooltipRef, className: classes, style: style, role: "tooltip", id: "db-tooltip", "aria-hidden": !(visible || alwaysShow), "data-placement-group": placementGroup, ...props, children: [jsx("div", { className: "designbase-tooltip__content", children: content }), showArrow && jsx("div", { className: arrowClasses, style: arrowStyle })] }))] }));
10894
- };
10895
- Tooltip.displayName = 'Tooltip';
10896
-
10897
10902
  const VideoPlayer = ({ src, poster, title, description, size = 'm', variant = 'default', theme = 'auto', autoPlay = false, loop = false, muted = false, showControls = true, enableFullscreen = true, enableKeyboard = true, enableTouch = true, showProgress = true, showTime = true, showVolume = true, showSettings = false, playlist = [], currentIndex = 0, autoPause = true, playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 2], defaultPlaybackRate = 1, qualities = [], defaultQuality = '', subtitles = [], defaultSubtitle = '', onPlay, onPause, onEnded, onTimeUpdate, onVolumeChange, onFullscreenChange, onPlaylistChange, onPlaybackRateChange, onQualityChange, onSubtitleChange, onError, className, }) => {
10898
10903
  const videoRef = useRef(null);
10899
10904
  const containerRef = useRef(null);