@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.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +105 -50
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +105 -50
- 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 +105 -50
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 }) }))] }),
|
|
4603
|
-
|
|
4604
|
-
|
|
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();
|