@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.umd.js CHANGED
@@ -4451,7 +4451,53 @@
4451
4451
  });
4452
4452
  Checkbox.displayName = 'Checkbox';
4453
4453
 
4454
- 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 }) => {
4454
+ const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', deletable = false, onDelete, onClick, startIcon, endIcon, disabled = false, selected = false, fullWidth = false, className, ...props }) => {
4455
+ // 사이즈별 아이콘 크기
4456
+ const getIconSize = () => {
4457
+ switch (size) {
4458
+ case 's':
4459
+ return 12;
4460
+ case 'm':
4461
+ return 16;
4462
+ case 'l':
4463
+ return 20;
4464
+ default:
4465
+ return 16;
4466
+ }
4467
+ };
4468
+ const iconSize = getIconSize();
4469
+ const handleDelete = (e) => {
4470
+ e.stopPropagation();
4471
+ if (!disabled && onDelete) {
4472
+ onDelete();
4473
+ }
4474
+ };
4475
+ const handleClick = () => {
4476
+ if (!disabled && onClick) {
4477
+ onClick();
4478
+ }
4479
+ };
4480
+ const classes = clsx('designbase-chip', `designbase-chip--${size}`, `designbase-chip--${variant}`, {
4481
+ 'designbase-chip--deletable': deletable,
4482
+ 'designbase-chip--disabled': disabled,
4483
+ 'designbase-chip--selected': selected,
4484
+ 'designbase-chip--clickable': onClick,
4485
+ 'designbase-chip--full-width': fullWidth,
4486
+ }, className);
4487
+ return (jsxRuntime.jsxs("div", { className: classes, role: onClick ? 'button' : undefined, tabIndex: onClick && !disabled ? 0 : undefined, onClick: handleClick, onKeyDown: (e) => {
4488
+ if (e.key === 'Enter' || e.key === ' ') {
4489
+ e.preventDefault();
4490
+ handleClick();
4491
+ }
4492
+ }, ...props, children: [startIcon && (jsxRuntime.jsx("span", { className: "designbase-chip__start-icon", children: React.isValidElement(startIcon)
4493
+ ? React.cloneElement(startIcon, { size: iconSize })
4494
+ : startIcon })), jsxRuntime.jsx("span", { className: "designbase-chip__label", children: label }), endIcon && !deletable && (jsxRuntime.jsx("span", { className: "designbase-chip__end-icon", children: React.isValidElement(endIcon)
4495
+ ? React.cloneElement(endIcon, { size: iconSize })
4496
+ : 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" }) }) }))] }));
4497
+ };
4498
+ Chip.displayName = 'Chip';
4499
+
4500
+ 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 }) => {
4455
4501
  const [internalValue, setInternalValue] = React.useState(defaultValue);
4456
4502
  const [recentSearches, setRecentSearches] = React.useState([]);
4457
4503
  const [showRecentSearches, setShowRecentSearches] = React.useState(false);
@@ -4498,6 +4544,26 @@
4498
4544
  setInternalValue(value);
4499
4545
  }
4500
4546
  }, [value]);
4547
+ // 전역 단축키 포커스
4548
+ React.useEffect(() => {
4549
+ if (!showShortcut || disabled || readOnly)
4550
+ return;
4551
+ const handleGlobalKeyDown = (e) => {
4552
+ const isModifier = e.metaKey || e.ctrlKey;
4553
+ let keyToMatch = 'k'; // 기본값 (⌘K)
4554
+ if (shortcutLabel) {
4555
+ const lastChar = shortcutLabel.slice(-1).toLowerCase();
4556
+ if (lastChar)
4557
+ keyToMatch = lastChar;
4558
+ }
4559
+ if (isModifier && e.key.toLowerCase() === keyToMatch) {
4560
+ e.preventDefault();
4561
+ inputRef.current?.focus();
4562
+ }
4563
+ };
4564
+ window.addEventListener('keydown', handleGlobalKeyDown);
4565
+ return () => window.removeEventListener('keydown', handleGlobalKeyDown);
4566
+ }, [showShortcut, shortcutLabel, disabled, readOnly]);
4501
4567
  const handleChange = (e) => {
4502
4568
  const newValue = e.target.value;
4503
4569
  setInternalValue(newValue);
@@ -4582,6 +4648,20 @@
4582
4648
  }
4583
4649
  handleSearch(suggestion);
4584
4650
  };
4651
+ const handleSearchResultClick = (result) => {
4652
+ onSearchResultClick?.(result);
4653
+ setShowRecentSearches(false); // Close dropdown
4654
+ };
4655
+ const handleCategoryClick = (category) => {
4656
+ onCategoryChange?.(category);
4657
+ };
4658
+ const hasSearchResults = searchResults !== undefined && searchResults.length > 0;
4659
+ const hasValue = currentValue && currentValue.length > 0;
4660
+ const showDropdown = isFocused && (categories.length > 0 ||
4661
+ (enableRecentSearches && recentSearches.length > 0 && !hasValue) ||
4662
+ (suggestedSearches.length > 0 && !hasValue) ||
4663
+ (searchResults !== undefined && hasValue) ||
4664
+ (emptyStateMessage && !hasValue && (!enableRecentSearches || recentSearches.length === 0) && suggestedSearches.length === 0));
4585
4665
  const classes = clsx('designbase-search-bar', `designbase-search-bar--${size}`, `designbase-search-bar--${variant}`, {
4586
4666
  'designbase-search-bar--disabled': disabled,
4587
4667
  'designbase-search-bar--readonly': readOnly,
@@ -4597,9 +4677,30 @@
4597
4677
  ? suggestedSearches[currentSuggestion]
4598
4678
  : placeholder;
4599
4679
  const shortcutBadgeSize = size === 'l' ? 'm' : 's';
4600
- 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', {
4601
- 'designbase-search-bar__suggestion-item--active': index === currentSuggestion
4602
- }), onClick: () => handleSuggestionClick(suggestion), children: suggestion }, index))) })] }))] }));
4680
+ 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) => {
4681
+ // prevent blur
4682
+ e.preventDefault();
4683
+ handleCategoryClick(category);
4684
+ } }, 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) => {
4685
+ e.preventDefault();
4686
+ handleSearchResultClick(result);
4687
+ }, 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." })) })) : (
4688
+ /* 2. 최근 검색어 & 추천 검색어 (검색어가 없을 때) */
4689
+ 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) => {
4690
+ e.preventDefault();
4691
+ handleClearAllRecentSearches();
4692
+ }, "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) => {
4693
+ e.preventDefault();
4694
+ handleRecentSearchClick(searchTerm);
4695
+ }, children: searchTerm }), jsxRuntime.jsx("button", { type: "button", className: "designbase-search-bar__recent-remove-button", onMouseDown: (e) => {
4696
+ e.preventDefault();
4697
+ handleRemoveRecentSearch(searchTerm);
4698
+ }, "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', {
4699
+ 'designbase-search-bar__suggestion-item--active': index === currentSuggestion
4700
+ }), onMouseDown: (e) => {
4701
+ e.preventDefault();
4702
+ handleSuggestionClick(suggestion);
4703
+ }, children: suggestion }, index))) })] })), emptyStateMessage && !hasValue && (!enableRecentSearches || recentSearches.length === 0) && suggestedSearches.length === 0 && (jsxRuntime.jsx("div", { className: "designbase-search-bar__empty-state", children: emptyStateMessage }))] }))] }))] }));
4603
4704
  };
4604
4705
  SearchBar.displayName = 'SearchBar';
4605
4706
 
@@ -5771,52 +5872,6 @@
5771
5872
  }, 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" }) }))] })] }));
5772
5873
  };
5773
5874
 
5774
- const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', deletable = false, onDelete, onClick, startIcon, endIcon, disabled = false, selected = false, fullWidth = false, className, ...props }) => {
5775
- // 사이즈별 아이콘 크기
5776
- const getIconSize = () => {
5777
- switch (size) {
5778
- case 's':
5779
- return 12;
5780
- case 'm':
5781
- return 16;
5782
- case 'l':
5783
- return 20;
5784
- default:
5785
- return 16;
5786
- }
5787
- };
5788
- const iconSize = getIconSize();
5789
- const handleDelete = (e) => {
5790
- e.stopPropagation();
5791
- if (!disabled && onDelete) {
5792
- onDelete();
5793
- }
5794
- };
5795
- const handleClick = () => {
5796
- if (!disabled && onClick) {
5797
- onClick();
5798
- }
5799
- };
5800
- const classes = clsx('designbase-chip', `designbase-chip--${size}`, `designbase-chip--${variant}`, {
5801
- 'designbase-chip--deletable': deletable,
5802
- 'designbase-chip--disabled': disabled,
5803
- 'designbase-chip--selected': selected,
5804
- 'designbase-chip--clickable': onClick,
5805
- 'designbase-chip--full-width': fullWidth,
5806
- }, className);
5807
- return (jsxRuntime.jsxs("div", { className: classes, role: onClick ? 'button' : undefined, tabIndex: onClick && !disabled ? 0 : undefined, onClick: handleClick, onKeyDown: (e) => {
5808
- if (e.key === 'Enter' || e.key === ' ') {
5809
- e.preventDefault();
5810
- handleClick();
5811
- }
5812
- }, ...props, children: [startIcon && (jsxRuntime.jsx("span", { className: "designbase-chip__start-icon", children: React.isValidElement(startIcon)
5813
- ? React.cloneElement(startIcon, { size: iconSize })
5814
- : startIcon })), jsxRuntime.jsx("span", { className: "designbase-chip__label", children: label }), endIcon && !deletable && (jsxRuntime.jsx("span", { className: "designbase-chip__end-icon", children: React.isValidElement(endIcon)
5815
- ? React.cloneElement(endIcon, { size: iconSize })
5816
- : 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" }) }) }))] }));
5817
- };
5818
- Chip.displayName = 'Chip';
5819
-
5820
5875
  /* ----------------------- 유틸 ----------------------- */
5821
5876
  const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
5822
5877
  const normalizeHex = (s) => (s || '').trim().toUpperCase();