@designbasekorea/ui 0.1.44 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +24 -20
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +238 -228
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +237 -227
- package/dist/index.js.map +1 -1
- package/dist/index.umd.css +1 -1
- package/dist/index.umd.css.map +1 -1
- package/dist/index.umd.js +237 -227
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, forwardRef, useId
|
|
2
|
+
import React, { useState, useCallback, useEffect, useRef, useMemo, useContext, useLayoutEffect, forwardRef, useId } from 'react';
|
|
3
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
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
+
// 추천 검색어 롤링 (value가 없을 때만)
|
|
8171
8384
|
useEffect(() => {
|
|
8172
|
-
if (suggestedSearches.length > 0 && !currentValue) {
|
|
8385
|
+
if (suggestedSearches.length > 0 && !currentValue && currentValue === '') {
|
|
8173
8386
|
suggestionIntervalRef.current = setInterval(() => {
|
|
8174
8387
|
setCurrentSuggestion(prev => (prev + 1) % suggestedSearches.length);
|
|
8175
8388
|
}, suggestionRollingInterval);
|
|
@@ -8268,6 +8481,11 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
|
|
|
8268
8481
|
const handleSuggestionClick = (suggestion) => {
|
|
8269
8482
|
setInternalValue(suggestion);
|
|
8270
8483
|
onChange?.(suggestion);
|
|
8484
|
+
// 롤링 중단
|
|
8485
|
+
if (suggestionIntervalRef.current) {
|
|
8486
|
+
clearInterval(suggestionIntervalRef.current);
|
|
8487
|
+
suggestionIntervalRef.current = null;
|
|
8488
|
+
}
|
|
8271
8489
|
handleSearch(suggestion);
|
|
8272
8490
|
};
|
|
8273
8491
|
const classes = clsx('designbase-search-bar', `designbase-search-bar--${size}`, `designbase-search-bar--${variant}`, {
|
|
@@ -8280,8 +8498,8 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
|
|
|
8280
8498
|
'designbase-search-bar__input--disabled': disabled,
|
|
8281
8499
|
'designbase-search-bar__input--readonly': readOnly,
|
|
8282
8500
|
});
|
|
8283
|
-
// 현재 플레이스홀더 (추천
|
|
8284
|
-
const currentPlaceholder = suggestedSearches.length > 0 && !currentValue
|
|
8501
|
+
// 현재 플레이스홀더 (value가 없을 때만 추천 검색어 롤링)
|
|
8502
|
+
const currentPlaceholder = suggestedSearches.length > 0 && !currentValue && currentValue === ''
|
|
8285
8503
|
? suggestedSearches[currentSuggestion]
|
|
8286
8504
|
: placeholder;
|
|
8287
8505
|
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', {
|
|
@@ -8513,7 +8731,7 @@ const Pagination = ({ currentPage, totalPages, totalItems, pageSize = 10, pageSi
|
|
|
8513
8731
|
};
|
|
8514
8732
|
Pagination.displayName = 'Pagination';
|
|
8515
8733
|
|
|
8516
|
-
const GAP
|
|
8734
|
+
const GAP = 8; // trigger와 popover 사이 간격
|
|
8517
8735
|
const Popover = ({ content, children, title, position = 'top', size = 'm', variant = 'default', trigger = 'click', delay = 0, // 클릭/포커스는 즉시, 호버는 아래에서만 적용
|
|
8518
8736
|
hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 300, showArrow = true, closeOnOutsideClick = true, closeOnEscape = true, open: controlledOpen, onOpenChange, className, ...props }) => {
|
|
8519
8737
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
@@ -8546,74 +8764,74 @@ hideDelay = 80, alwaysShow = false, disabled = false, maxWidth = 300, showArrow
|
|
|
8546
8764
|
};
|
|
8547
8765
|
switch (position) {
|
|
8548
8766
|
case 'top':
|
|
8549
|
-
top = tRect.top - pRect.height - GAP
|
|
8767
|
+
top = tRect.top - pRect.height - GAP;
|
|
8550
8768
|
left = tRect.left + tRect.width / 2 - pRect.width / 2;
|
|
8551
8769
|
aTop = pRect.height;
|
|
8552
8770
|
aLeft = pRect.width / 2 - 4;
|
|
8553
8771
|
break;
|
|
8554
8772
|
case 'top-start':
|
|
8555
|
-
top = tRect.top - pRect.height - GAP
|
|
8773
|
+
top = tRect.top - pRect.height - GAP;
|
|
8556
8774
|
left = tRect.left;
|
|
8557
8775
|
aTop = pRect.height;
|
|
8558
8776
|
aLeft = 12;
|
|
8559
8777
|
break;
|
|
8560
8778
|
case 'top-end':
|
|
8561
|
-
top = tRect.top - pRect.height - GAP
|
|
8779
|
+
top = tRect.top - pRect.height - GAP;
|
|
8562
8780
|
left = tRect.right - pRect.width;
|
|
8563
8781
|
aTop = pRect.height;
|
|
8564
8782
|
aLeft = pRect.width - 12;
|
|
8565
8783
|
break;
|
|
8566
8784
|
case 'bottom':
|
|
8567
|
-
top = tRect.bottom + GAP
|
|
8785
|
+
top = tRect.bottom + GAP;
|
|
8568
8786
|
left = tRect.left + tRect.width / 2 - pRect.width / 2;
|
|
8569
8787
|
aTop = -4;
|
|
8570
8788
|
aLeft = pRect.width / 2 - 4;
|
|
8571
8789
|
break;
|
|
8572
8790
|
case 'bottom-start':
|
|
8573
|
-
top = tRect.bottom + GAP
|
|
8791
|
+
top = tRect.bottom + GAP;
|
|
8574
8792
|
left = tRect.left;
|
|
8575
8793
|
aTop = -4;
|
|
8576
8794
|
aLeft = 12;
|
|
8577
8795
|
break;
|
|
8578
8796
|
case 'bottom-end':
|
|
8579
|
-
top = tRect.bottom + GAP
|
|
8797
|
+
top = tRect.bottom + GAP;
|
|
8580
8798
|
left = tRect.right - pRect.width;
|
|
8581
8799
|
aTop = -4;
|
|
8582
8800
|
aLeft = pRect.width - 12;
|
|
8583
8801
|
break;
|
|
8584
8802
|
case 'left':
|
|
8585
8803
|
top = tRect.top + tRect.height / 2 - pRect.height / 2;
|
|
8586
|
-
left = tRect.left - pRect.width - GAP
|
|
8804
|
+
left = tRect.left - pRect.width - GAP;
|
|
8587
8805
|
aTop = pRect.height / 2 - 4;
|
|
8588
8806
|
aLeft = pRect.width;
|
|
8589
8807
|
break;
|
|
8590
8808
|
case 'left-start':
|
|
8591
8809
|
top = tRect.top;
|
|
8592
|
-
left = tRect.left - pRect.width - GAP
|
|
8810
|
+
left = tRect.left - pRect.width - GAP;
|
|
8593
8811
|
aTop = 12;
|
|
8594
8812
|
aLeft = pRect.width;
|
|
8595
8813
|
break;
|
|
8596
8814
|
case 'left-end':
|
|
8597
8815
|
top = tRect.bottom - pRect.height;
|
|
8598
|
-
left = tRect.left - pRect.width - GAP
|
|
8816
|
+
left = tRect.left - pRect.width - GAP;
|
|
8599
8817
|
aTop = pRect.height - 12;
|
|
8600
8818
|
aLeft = pRect.width;
|
|
8601
8819
|
break;
|
|
8602
8820
|
case 'right':
|
|
8603
8821
|
top = tRect.top + tRect.height / 2 - pRect.height / 2;
|
|
8604
|
-
left = tRect.right + GAP
|
|
8822
|
+
left = tRect.right + GAP;
|
|
8605
8823
|
aTop = pRect.height / 2 - 4;
|
|
8606
8824
|
aLeft = -4;
|
|
8607
8825
|
break;
|
|
8608
8826
|
case 'right-start':
|
|
8609
8827
|
top = tRect.top;
|
|
8610
|
-
left = tRect.right + GAP
|
|
8828
|
+
left = tRect.right + GAP;
|
|
8611
8829
|
aTop = 12;
|
|
8612
8830
|
aLeft = -4;
|
|
8613
8831
|
break;
|
|
8614
8832
|
case 'right-end':
|
|
8615
8833
|
top = tRect.bottom - pRect.height;
|
|
8616
|
-
left = tRect.right + GAP
|
|
8834
|
+
left = tRect.right + GAP;
|
|
8617
8835
|
aTop = pRect.height - 12;
|
|
8618
8836
|
aLeft = -4;
|
|
8619
8837
|
break;
|
|
@@ -10686,214 +10904,6 @@ const Toolbar = ({ items, size = 'm', variant = 'default', position = 'top', ful
|
|
|
10686
10904
|
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
10905
|
};
|
|
10688
10906
|
|
|
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
10907
|
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
10908
|
const videoRef = useRef(null);
|
|
10899
10909
|
const containerRef = useRef(null);
|