@designbasekorea/ui 0.6.1 → 0.7.0

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.js CHANGED
@@ -4453,7 +4453,53 @@ const Checkbox = React.forwardRef(({ isSelected, defaultSelected, isIndeterminat
4453
4453
  });
4454
4454
  Checkbox.displayName = 'Checkbox';
4455
4455
 
4456
- const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size = 'm', variant = 'default', disabled = false, readOnly = false, fullWidth = false, searchIcon: SearchIconComponent = icons.SearchIcon, clearIcon: ClearIconComponent = icons.CloseIcon, enableRecentSearches = false, recentSearchesKey = 'searchbar-recent-searches', suggestedSearches = [], suggestionRollingInterval = 5000, onChange, onSearch, onFocus, onBlur, onKeyDown, showShortcut = false, shortcutLabel = '⌘K', className, ...rest }) => {
4456
+ const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', deletable = false, onDelete, onClick, startIcon, endIcon, disabled = false, selected = false, fullWidth = false, className, ...props }) => {
4457
+ // 사이즈별 아이콘 크기
4458
+ const getIconSize = () => {
4459
+ switch (size) {
4460
+ case 's':
4461
+ return 12;
4462
+ case 'm':
4463
+ return 16;
4464
+ case 'l':
4465
+ return 20;
4466
+ default:
4467
+ return 16;
4468
+ }
4469
+ };
4470
+ const iconSize = getIconSize();
4471
+ const handleDelete = (e) => {
4472
+ e.stopPropagation();
4473
+ if (!disabled && onDelete) {
4474
+ onDelete();
4475
+ }
4476
+ };
4477
+ const handleClick = () => {
4478
+ if (!disabled && onClick) {
4479
+ onClick();
4480
+ }
4481
+ };
4482
+ const classes = clsx('designbase-chip', `designbase-chip--${size}`, `designbase-chip--${variant}`, {
4483
+ 'designbase-chip--deletable': deletable,
4484
+ 'designbase-chip--disabled': disabled,
4485
+ 'designbase-chip--selected': selected,
4486
+ 'designbase-chip--clickable': onClick,
4487
+ 'designbase-chip--full-width': fullWidth,
4488
+ }, className);
4489
+ return (jsxRuntime.jsxs("div", { className: classes, role: onClick ? 'button' : undefined, tabIndex: onClick && !disabled ? 0 : undefined, onClick: handleClick, onKeyDown: (e) => {
4490
+ if (e.key === 'Enter' || e.key === ' ') {
4491
+ e.preventDefault();
4492
+ handleClick();
4493
+ }
4494
+ }, ...props, children: [startIcon && (jsxRuntime.jsx("span", { className: "designbase-chip__start-icon", children: React.isValidElement(startIcon)
4495
+ ? React.cloneElement(startIcon, { size: iconSize })
4496
+ : startIcon })), jsxRuntime.jsx("span", { className: "designbase-chip__label", children: label }), endIcon && !deletable && (jsxRuntime.jsx("span", { className: "designbase-chip__end-icon", children: React.isValidElement(endIcon)
4497
+ ? React.cloneElement(endIcon, { size: iconSize })
4498
+ : endIcon })), deletable && (jsxRuntime.jsx("button", { type: "button", className: "designbase-chip__delete-button", onClick: handleDelete, disabled: disabled, "aria-label": `${label} 삭제`, children: jsxRuntime.jsx("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsxRuntime.jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }));
4499
+ };
4500
+ Chip.displayName = 'Chip';
4501
+
4502
+ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size = 'm', variant = 'default', disabled = false, readOnly = false, fullWidth = false, searchIcon: SearchIconComponent = icons.SearchIcon, clearIcon: ClearIconComponent = icons.CloseIcon, enableRecentSearches = false, recentSearchesKey = 'searchbar-recent-searches', recentSearchesLayout = 'vertical', suggestedSearches = [], suggestionRollingInterval = 5000, categories = [], selectedCategory = '', onCategoryChange, searchResults, renderSearchResult, onSearchResultClick, emptyStateMessage, onChange, onSearch, onFocus, onBlur, onKeyDown, showShortcut = false, shortcutLabel = '⌘K', className, ...rest }) => {
4457
4503
  const [internalValue, setInternalValue] = React.useState(defaultValue);
4458
4504
  const [recentSearches, setRecentSearches] = React.useState([]);
4459
4505
  const [showRecentSearches, setShowRecentSearches] = React.useState(false);
@@ -4500,6 +4546,26 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
4500
4546
  setInternalValue(value);
4501
4547
  }
4502
4548
  }, [value]);
4549
+ // 전역 단축키 포커스
4550
+ React.useEffect(() => {
4551
+ if (!showShortcut || disabled || readOnly)
4552
+ return;
4553
+ const handleGlobalKeyDown = (e) => {
4554
+ const isModifier = e.metaKey || e.ctrlKey;
4555
+ let keyToMatch = 'k'; // 기본값 (⌘K)
4556
+ if (shortcutLabel) {
4557
+ const lastChar = shortcutLabel.slice(-1).toLowerCase();
4558
+ if (lastChar)
4559
+ keyToMatch = lastChar;
4560
+ }
4561
+ if (isModifier && e.key.toLowerCase() === keyToMatch) {
4562
+ e.preventDefault();
4563
+ inputRef.current?.focus();
4564
+ }
4565
+ };
4566
+ window.addEventListener('keydown', handleGlobalKeyDown);
4567
+ return () => window.removeEventListener('keydown', handleGlobalKeyDown);
4568
+ }, [showShortcut, shortcutLabel, disabled, readOnly]);
4503
4569
  const handleChange = (e) => {
4504
4570
  const newValue = e.target.value;
4505
4571
  setInternalValue(newValue);
@@ -4584,6 +4650,20 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
4584
4650
  }
4585
4651
  handleSearch(suggestion);
4586
4652
  };
4653
+ const handleSearchResultClick = (result) => {
4654
+ onSearchResultClick?.(result);
4655
+ setShowRecentSearches(false); // Close dropdown
4656
+ };
4657
+ const handleCategoryClick = (category) => {
4658
+ onCategoryChange?.(category);
4659
+ };
4660
+ const hasSearchResults = searchResults !== undefined && searchResults.length > 0;
4661
+ const hasValue = currentValue && currentValue.length > 0;
4662
+ const showDropdown = isFocused && (categories.length > 0 ||
4663
+ (enableRecentSearches && recentSearches.length > 0 && !hasValue) ||
4664
+ (suggestedSearches.length > 0 && !hasValue) ||
4665
+ (searchResults !== undefined && hasValue) ||
4666
+ (emptyStateMessage && !hasValue && (!enableRecentSearches || recentSearches.length === 0) && suggestedSearches.length === 0));
4587
4667
  const classes = clsx('designbase-search-bar', `designbase-search-bar--${size}`, `designbase-search-bar--${variant}`, {
4588
4668
  'designbase-search-bar--disabled': disabled,
4589
4669
  'designbase-search-bar--readonly': readOnly,
@@ -4599,9 +4679,30 @@ const SearchBar = ({ value, defaultValue = '', placeholder = '검색...', size =
4599
4679
  ? suggestedSearches[currentSuggestion]
4600
4680
  : placeholder;
4601
4681
  const shortcutBadgeSize = size === 'l' ? 'm' : 's';
4602
- return (jsxRuntime.jsxs("div", { className: classes, role: "search", children: [jsxRuntime.jsxs("div", { className: "designbase-search-bar__container", children: [jsxRuntime.jsx("div", { className: "designbase-search-bar__search-icon", children: jsxRuntime.jsx(SearchIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }), jsxRuntime.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", ...rest }), showShortcut && !currentValue && !disabled && !readOnly && (jsxRuntime.jsx(Badge, { className: "designbase-search-bar__shortcut-badge", size: shortcutBadgeSize, variant: "secondary", style: "text", "aria-hidden": "true", children: shortcutLabel })), currentValue && currentValue.length > 0 && !disabled && !readOnly && (jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__clear-button", onClick: handleClear, "aria-label": "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30", children: jsxRuntime.jsx(ClearIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }))] }), showRecentSearches && recentSearches.length > 0 && (jsxRuntime.jsxs("div", { className: "designbase-search-bar__recent-searches", children: [jsxRuntime.jsxs("div", { className: "designbase-search-bar__recent-header", children: [jsxRuntime.jsx("span", { className: "designbase-search-bar__recent-title", children: "\uCD5C\uADFC \uAC80\uC0C9\uC5B4" }), jsxRuntime.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" })] }), jsxRuntime.jsx("div", { className: "designbase-search-bar__recent-list", children: recentSearches.map((searchTerm, index) => (jsxRuntime.jsxs("div", { className: "designbase-search-bar__recent-item", children: [jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__recent-search-button", onClick: () => handleRecentSearchClick(searchTerm), children: searchTerm }), jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__recent-remove-button", onClick: () => handleRemoveRecentSearch(searchTerm), "aria-label": `${searchTerm} 삭제`, children: jsxRuntime.jsx(icons.CloseIcon, { size: 16 }) })] }, index))) })] })), suggestedSearches.length > 0 && isFocused && !currentValue && (jsxRuntime.jsxs("div", { className: "designbase-search-bar__suggestions", children: [jsxRuntime.jsx("div", { className: "designbase-search-bar__suggestions-header", children: jsxRuntime.jsx("span", { className: "designbase-search-bar__suggestions-title", children: "\uCD94\uCC9C \uAC80\uC0C9\uC5B4" }) }), jsxRuntime.jsx("div", { className: "designbase-search-bar__suggestions-list", children: suggestedSearches.map((suggestion, index) => (jsxRuntime.jsx("button", { type: "button", className: clsx('designbase-search-bar__suggestion-item', {
4603
- 'designbase-search-bar__suggestion-item--active': index === currentSuggestion
4604
- }), onClick: () => handleSuggestionClick(suggestion), children: suggestion }, index))) })] }))] }));
4682
+ return (jsxRuntime.jsxs("div", { className: classes, role: "search", children: [jsxRuntime.jsxs("div", { className: "designbase-search-bar__container", children: [jsxRuntime.jsx("div", { className: "designbase-search-bar__search-icon", children: jsxRuntime.jsx(SearchIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }), jsxRuntime.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", ...rest }), showShortcut && !currentValue && !disabled && !readOnly && (jsxRuntime.jsx(Badge, { className: "designbase-search-bar__shortcut-badge", size: shortcutBadgeSize, variant: "secondary", style: "text", "aria-hidden": "true", children: shortcutLabel })), currentValue && currentValue.length > 0 && !disabled && !readOnly && (jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__clear-button", onClick: handleClear, "aria-label": "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30", children: jsxRuntime.jsx(ClearIconComponent, { size: size === 's' ? 16 : size === 'l' ? 24 : 20 }) }))] }), showDropdown && (jsxRuntime.jsxs("div", { className: "designbase-search-bar__dropdown", children: [categories.length > 0 && (jsxRuntime.jsx("div", { className: "designbase-search-bar__categories", children: categories.map((category) => (jsxRuntime.jsx(Chip, { label: category, size: "s", variant: category === selectedCategory ? 'primary' : 'default', onClick: () => { }, onMouseDown: (e) => {
4683
+ // prevent blur
4684
+ e.preventDefault();
4685
+ handleCategoryClick(category);
4686
+ } }, category))) })), hasValue && searchResults !== undefined ? (jsxRuntime.jsx("div", { className: "designbase-search-bar__results", children: hasSearchResults ? (jsxRuntime.jsx("ul", { className: "designbase-search-bar__results-list", children: searchResults.map((result, index) => (jsxRuntime.jsx("li", { className: "designbase-search-bar__result-item", onMouseDown: (e) => {
4687
+ e.preventDefault();
4688
+ handleSearchResultClick(result);
4689
+ }, children: renderSearchResult ? renderSearchResult(result, index) : result.toString() }, index))) })) : (jsxRuntime.jsx("div", { className: "designbase-search-bar__results-empty", children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." })) })) : (
4690
+ /* 2. 최근 검색어 & 추천 검색어 (검색어가 없을 때) */
4691
+ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [enableRecentSearches && !hasValue && recentSearches.length > 0 && (jsxRuntime.jsxs("div", { className: clsx('designbase-search-bar__recent-searches', `designbase-search-bar__recent-searches--${recentSearchesLayout}`), children: [jsxRuntime.jsxs("div", { className: "designbase-search-bar__recent-header", children: [jsxRuntime.jsx("span", { className: "designbase-search-bar__recent-title", children: "\uCD5C\uADFC \uAC80\uC0C9\uC5B4" }), jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__clear-all-button", onMouseDown: (e) => {
4692
+ e.preventDefault();
4693
+ handleClearAllRecentSearches();
4694
+ }, "aria-label": "\uBAA8\uB4E0 \uCD5C\uADFC \uAC80\uC0C9\uC5B4 \uC0AD\uC81C", children: "\uC804\uCCB4 \uC0AD\uC81C" })] }), jsxRuntime.jsx("div", { className: "designbase-search-bar__recent-list", children: recentSearches.map((searchTerm, index) => (jsxRuntime.jsxs("div", { className: "designbase-search-bar__recent-item", children: [jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__recent-search-button", onMouseDown: (e) => {
4695
+ e.preventDefault();
4696
+ handleRecentSearchClick(searchTerm);
4697
+ }, children: searchTerm }), jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__recent-remove-button", onMouseDown: (e) => {
4698
+ e.preventDefault();
4699
+ handleRemoveRecentSearch(searchTerm);
4700
+ }, "aria-label": `${searchTerm} 삭제`, children: jsxRuntime.jsx(icons.CloseIcon, { size: 16 }) })] }, index))) })] })), suggestedSearches.length > 0 && !hasValue && (jsxRuntime.jsxs("div", { className: "designbase-search-bar__suggestions", children: [jsxRuntime.jsx("div", { className: "designbase-search-bar__suggestions-header", children: jsxRuntime.jsx("span", { className: "designbase-search-bar__suggestions-title", children: "\uCD94\uCC9C \uAC80\uC0C9\uC5B4" }) }), jsxRuntime.jsx("div", { className: "designbase-search-bar__suggestions-list", children: suggestedSearches.map((suggestion, index) => (jsxRuntime.jsx("button", { type: "button", className: clsx('designbase-search-bar__suggestion-item', {
4701
+ 'designbase-search-bar__suggestion-item--active': index === currentSuggestion
4702
+ }), onMouseDown: (e) => {
4703
+ e.preventDefault();
4704
+ handleSuggestionClick(suggestion);
4705
+ }, children: suggestion }, index))) })] })), emptyStateMessage && !hasValue && (!enableRecentSearches || recentSearches.length === 0) && suggestedSearches.length === 0 && (jsxRuntime.jsx("div", { className: "designbase-search-bar__empty-state", children: emptyStateMessage }))] }))] }))] }));
4605
4706
  };
4606
4707
  SearchBar.displayName = 'SearchBar';
4607
4708
 
@@ -5773,52 +5874,6 @@ const Carousel = ({ items, size = 'm', variant = 'default', theme = 'light', tra
5773
5874
  }, title: "\uB2E4\uC6B4\uB85C\uB4DC", children: jsxRuntime.jsx(icons.DownloadIcon, { size: iconSize, color: "currentColor" }) }))] })] }, item.id))) }), showNavigation && items.length > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { className: clsx('designbase-carousel__nav-button', 'designbase-carousel__nav-button--prev'), onClick: goToPrevious, disabled: disabled || readonly || isPrevDisabled, title: "\uC774\uC804 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ChevronLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntime.jsx("button", { className: clsx('designbase-carousel__nav-button', 'designbase-carousel__nav-button--next'), onClick: goToNext, disabled: disabled || readonly || isNextDisabled, title: "\uB2E4\uC74C \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ChevronRightIcon, { size: iconSize, color: "currentColor" }) })] })), enablePaging && items.length > 1 && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { className: "designbase-carousel__quick-nav-button designbase-carousel__quick-nav-button--first", onClick: goToFirst, disabled: disabled || readonly || currentIndex === 0, title: "\uCCAB \uBC88\uC9F8 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ArrowLeftIcon, { size: iconSize, color: "currentColor" }) }), jsxRuntime.jsx("button", { className: "designbase-carousel__quick-nav-button designbase-carousel__quick-nav-button--last", onClick: goToLast, disabled: disabled || readonly || currentIndex === items.length - 1, title: "\uB9C8\uC9C0\uB9C9 \uC2AC\uB77C\uC774\uB4DC", type: "button", children: jsxRuntime.jsx(icons.ArrowRightIcon, { size: iconSize, color: "currentColor" }) })] })), enableFullscreen && (jsxRuntime.jsx("button", { className: "designbase-carousel__fullscreen-button", onClick: handleFullscreenToggle, disabled: disabled || readonly, title: isFullscreen ? "전체화면 해제" : "전체화면", type: "button", children: isFullscreen ? jsxRuntime.jsx(icons.ShrinkIcon, { size: iconSize, color: "currentColor" }) : jsxRuntime.jsx(icons.ExpandIcon, { size: iconSize, color: "currentColor" }) }))] }), jsxRuntime.jsxs("div", { className: "designbase-carousel__controls", children: [showIndicators && items.length > 1 && (jsxRuntime.jsx("div", { className: "designbase-carousel__indicators", children: jsxRuntime.jsx(Indicator, { current: currentIndex, total: items.length, type: indicatorStyle === 'lines' ? 'line' : indicatorStyle === 'numbers' ? 'numbers' : 'dots', size: "s", clickable: true, onStepClick: goToSlide }) })), showAutoPlayControl && items.length > 1 && (jsxRuntime.jsx("button", { className: "designbase-carousel__autoplay-button", onClick: handleAutoPlayToggle, disabled: disabled || readonly, title: isAutoPlaying ? "자동 재생 정지" : "자동 재생 시작", type: "button", children: isAutoPlaying ? jsxRuntime.jsx(icons.PauseIcon, { size: iconSize, color: "currentColor" }) : jsxRuntime.jsx(icons.PlayIcon, { size: iconSize, color: "currentColor" }) }))] })] }));
5774
5875
  };
5775
5876
 
5776
- const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', deletable = false, onDelete, onClick, startIcon, endIcon, disabled = false, selected = false, fullWidth = false, className, ...props }) => {
5777
- // 사이즈별 아이콘 크기
5778
- const getIconSize = () => {
5779
- switch (size) {
5780
- case 's':
5781
- return 12;
5782
- case 'm':
5783
- return 16;
5784
- case 'l':
5785
- return 20;
5786
- default:
5787
- return 16;
5788
- }
5789
- };
5790
- const iconSize = getIconSize();
5791
- const handleDelete = (e) => {
5792
- e.stopPropagation();
5793
- if (!disabled && onDelete) {
5794
- onDelete();
5795
- }
5796
- };
5797
- const handleClick = () => {
5798
- if (!disabled && onClick) {
5799
- onClick();
5800
- }
5801
- };
5802
- const classes = clsx('designbase-chip', `designbase-chip--${size}`, `designbase-chip--${variant}`, {
5803
- 'designbase-chip--deletable': deletable,
5804
- 'designbase-chip--disabled': disabled,
5805
- 'designbase-chip--selected': selected,
5806
- 'designbase-chip--clickable': onClick,
5807
- 'designbase-chip--full-width': fullWidth,
5808
- }, className);
5809
- return (jsxRuntime.jsxs("div", { className: classes, role: onClick ? 'button' : undefined, tabIndex: onClick && !disabled ? 0 : undefined, onClick: handleClick, onKeyDown: (e) => {
5810
- if (e.key === 'Enter' || e.key === ' ') {
5811
- e.preventDefault();
5812
- handleClick();
5813
- }
5814
- }, ...props, children: [startIcon && (jsxRuntime.jsx("span", { className: "designbase-chip__start-icon", children: React.isValidElement(startIcon)
5815
- ? React.cloneElement(startIcon, { size: iconSize })
5816
- : startIcon })), jsxRuntime.jsx("span", { className: "designbase-chip__label", children: label }), endIcon && !deletable && (jsxRuntime.jsx("span", { className: "designbase-chip__end-icon", children: React.isValidElement(endIcon)
5817
- ? React.cloneElement(endIcon, { size: iconSize })
5818
- : endIcon })), deletable && (jsxRuntime.jsx("button", { type: "button", className: "designbase-chip__delete-button", onClick: handleDelete, disabled: disabled, "aria-label": `${label} 삭제`, children: jsxRuntime.jsx("svg", { width: iconSize, height: iconSize, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsxRuntime.jsx("path", { d: "M18 6L6 18M6 6l12 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }));
5819
- };
5820
- Chip.displayName = 'Chip';
5821
-
5822
5877
  /* ----------------------- 유틸 ----------------------- */
5823
5878
  const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
5824
5879
  const normalizeHex = (s) => (s || '').trim().toUpperCase();