@designbasekorea/ui 0.2.28 → 0.2.29

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
@@ -5278,7 +5278,7 @@
5278
5278
  };
5279
5279
  Chip.displayName = 'Chip';
5280
5280
 
5281
- /** 유틸 */
5281
+ /* ----------------------- 유틸 ----------------------- */
5282
5282
  const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
5283
5283
  const hexToRgb = (hex) => {
5284
5284
  const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
@@ -5290,6 +5290,7 @@
5290
5290
  const toHex = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');
5291
5291
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
5292
5292
  };
5293
+ /** RGB ↔ HSV (HSV: h[0-360], s[0-100], v[0-100]) */
5293
5294
  const rgbToHsv = (r, g, b) => {
5294
5295
  r /= 255;
5295
5296
  g /= 255;
@@ -5379,6 +5380,7 @@
5379
5380
  }
5380
5381
  return { h: Math.round(h * 100) / 100, s: Math.round(s * 10000) / 100, l: Math.round(l * 10000) / 100 };
5381
5382
  };
5383
+ /* ----------------------- 컴포넌트 ----------------------- */
5382
5384
  const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left', value, defaultValue = '#006FFF', showInput = true, showAlpha = true, showFormatSelector = true, showCopyButton = true, disabled = false, readonly = false, onChange, onApply, onCancel, className, }) => {
5383
5385
  /** 내부 상태 (HSV + alpha%) */
5384
5386
  const [h, setH] = React.useState(211);
@@ -5388,10 +5390,10 @@
5388
5390
  const [isOpen, setIsOpen] = React.useState(false);
5389
5391
  const [format, setFormat] = React.useState('hex');
5390
5392
  const [isCopied, setIsCopied] = React.useState(false);
5391
- // 하단 컬러 인풋(문자열), 알파 인풋(문자열) – 엔터로만 확정
5393
+ // 하단 인풋(문자열) 엔터로만 확정
5392
5394
  const [colorInput, setColorInput] = React.useState('');
5393
5395
  const [alphaInput, setAlphaInput] = React.useState('100');
5394
- // 모달용 임시 상태 (적용/취소 버튼용)
5396
+ // 모달용 임시 상태 (취소 시 되돌리기)
5395
5397
  const [tempColor, setTempColor] = React.useState('');
5396
5398
  const pickerRef = React.useRef(null);
5397
5399
  const areaRef = React.useRef(null);
@@ -5405,12 +5407,13 @@
5405
5407
  setH(h);
5406
5408
  setS(s);
5407
5409
  setV(v);
5408
- setColorInput(initial);
5409
- setAlphaInput('100');
5410
5410
  }
5411
+ // 인풋 초기 표현
5412
+ setColorInput(initial);
5413
+ setAlphaInput('100');
5411
5414
  // eslint-disable-next-line react-hooks/exhaustive-deps
5412
5415
  }, []);
5413
- /** 외부 value 변화 */
5416
+ /** 외부 value 변화 시(제어형)만 HSV 반영 */
5414
5417
  React.useEffect(() => {
5415
5418
  if (!value)
5416
5419
  return;
@@ -5426,7 +5429,7 @@
5426
5429
  const rgb = React.useMemo(() => hsvToRgb(h, s, v), [h, s, v]);
5427
5430
  const hex = React.useMemo(() => rgbToHex(rgb.r, rgb.g, rgb.b), [rgb]);
5428
5431
  const hsl = React.useMemo(() => rgbToHsl(rgb.r, rgb.g, rgb.b), [rgb]);
5429
- /** 표시 문자열 */
5432
+ /** 포맷 출력 문자열 */
5430
5433
  const formatted = React.useMemo(() => {
5431
5434
  const alpha = a / 100;
5432
5435
  switch (format) {
@@ -5438,11 +5441,9 @@
5438
5441
  default: return hex;
5439
5442
  }
5440
5443
  }, [format, hex, rgb, hsl, a]);
5441
- /** 외부 onChange 인풋 동기화 */
5444
+ /** 외부 onChange 알림만 (입력창 동기화는 여기서 하지 않음!) */
5442
5445
  React.useEffect(() => {
5443
5446
  onChange?.(formatted);
5444
- setColorInput(formatted.toUpperCase());
5445
- setAlphaInput(String(a));
5446
5447
  // eslint-disable-next-line react-hooks/exhaustive-deps
5447
5448
  }, [formatted]);
5448
5449
  /** 드롭다운 외부 클릭 닫기 */
@@ -5455,7 +5456,7 @@
5455
5456
  document.addEventListener('mousedown', handler);
5456
5457
  return () => document.removeEventListener('mousedown', handler);
5457
5458
  }, [isOpen, type]);
5458
- /** 컬러 필드 핸들링 */
5459
+ /** 컬러 영역 (S x V) 마우스/터치 */
5459
5460
  const updateFromArea = (clientX, clientY) => {
5460
5461
  const el = areaRef.current;
5461
5462
  if (!el)
@@ -5478,27 +5479,26 @@
5478
5479
  return; const t = e.touches[0]; updateFromArea(t.clientX, t.clientY); };
5479
5480
  const onAreaTouchEnd = () => { dragging.current = false; };
5480
5481
  /** 슬라이더 */
5481
- const onHueChange = React.useCallback((e) => {
5482
- setH(parseInt(e.target.value, 10));
5483
- }, []);
5482
+ const onHueChange = React.useCallback((e) => setH(parseInt(e.target.value, 10)), []);
5484
5483
  const onAlphaChange = React.useCallback((e) => {
5485
5484
  const newAlpha = parseInt(e.target.value, 10);
5486
5485
  setA(newAlpha);
5487
- setAlphaInput(String(newAlpha)); // 실시간 동기화
5486
+ setAlphaInput(String(newAlpha)); // 슬라이더 ↔ 인풋 동기화(표시만)
5488
5487
  }, []);
5489
- /** 컬러 인풋: 엔터로만 확정 */
5490
- const tryCommitColorInput = () => {
5488
+ /** 컬러 텍스트 인풋: 엔터로만 확정 */
5489
+ const commitColorInput = React.useCallback(() => {
5491
5490
  const str = colorInput.trim();
5492
- // HEX
5491
+ // HEX (#RRGGBB)
5493
5492
  if (/^#([0-9A-Fa-f]{6})$/.test(str)) {
5494
5493
  const rgb = hexToRgb(str);
5495
5494
  const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
5496
5495
  setH(hsv.h);
5497
5496
  setS(hsv.s);
5498
5497
  setV(hsv.v);
5498
+ setColorInput(str.toUpperCase());
5499
5499
  return;
5500
5500
  }
5501
- // rgb()
5501
+ // rgb(r,g,b)
5502
5502
  let m = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.exec(str);
5503
5503
  if (m) {
5504
5504
  const r = clamp(parseInt(m[1], 10), 0, 255);
@@ -5508,9 +5508,10 @@
5508
5508
  setH(hsv.h);
5509
5509
  setS(hsv.s);
5510
5510
  setV(hsv.v);
5511
+ setColorInput(`rgb(${r}, ${g}, ${b})`.toUpperCase());
5511
5512
  return;
5512
5513
  }
5513
- // rgba()
5514
+ // rgba(r,g,b,a)
5514
5515
  m = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.\d+)\s*\)$/i.exec(str);
5515
5516
  if (m) {
5516
5517
  const r = clamp(parseInt(m[1], 10), 0, 255);
@@ -5522,35 +5523,38 @@
5522
5523
  setS(hsv.s);
5523
5524
  setV(hsv.v);
5524
5525
  setA(Math.round(alpha * 100));
5526
+ setAlphaInput(String(Math.round(alpha * 100)));
5527
+ setColorInput(`rgba(${r}, ${g}, ${b}, ${alpha})`.toUpperCase());
5525
5528
  return;
5526
5529
  }
5527
- // 실패원래 값으로 복구
5530
+ // 잘못된 원복
5528
5531
  setColorInput(formatted.toUpperCase());
5529
- };
5532
+ }, [colorInput, formatted]);
5530
5533
  const onColorKeyDown = (e) => {
5531
5534
  if (e.key === 'Enter')
5532
- tryCommitColorInput();
5535
+ commitColorInput();
5533
5536
  };
5534
5537
  const onColorBlur = () => {
5535
- // 요구사항: 엔터로만 변경. 블러 시에는 값 복구만.
5538
+ // 요구사항: 엔터로만 반영, 블러 시엔 원복
5536
5539
  setColorInput(formatted.toUpperCase());
5537
5540
  };
5538
- /** 알파 인풋: 엔터로만 확정 (0-100) */
5539
- const tryCommitAlphaInput = () => {
5541
+ /** 알파 인풋: 엔터로만 확정 (0~100) */
5542
+ const commitAlphaInput = React.useCallback(() => {
5540
5543
  const n = Number(alphaInput.trim());
5541
5544
  if (!Number.isNaN(n) && n >= 0 && n <= 100) {
5542
5545
  setA(Math.round(n));
5546
+ setAlphaInput(String(Math.round(n)));
5543
5547
  return;
5544
5548
  }
5545
- // 실패복구
5549
+ // 잘못된 원복
5546
5550
  setAlphaInput(String(a));
5547
- };
5551
+ }, [alphaInput, a]);
5548
5552
  const onAlphaInputKeyDown = (e) => {
5549
5553
  if (e.key === 'Enter')
5550
- tryCommitAlphaInput();
5554
+ commitAlphaInput();
5551
5555
  };
5552
5556
  const onAlphaInputBlur = () => {
5553
- // 엔터로만 반영. 블러 시 복원.
5557
+ // 엔터로만 반영, 블러 시 원복
5554
5558
  setAlphaInput(String(a));
5555
5559
  };
5556
5560
  /** 복사 */
@@ -5584,13 +5588,12 @@
5584
5588
  console.error(e);
5585
5589
  }
5586
5590
  }, []);
5587
- /** 모달용 핸들러 */
5591
+ /** 모달 열기/적용/취소 (deps에서 formatted 제거 → 재생성 방지) */
5588
5592
  const handleModalOpen = React.useCallback(() => {
5589
- if (type === 'modal') {
5590
- setTempColor(formatted);
5591
- }
5593
+ if (type === 'modal')
5594
+ setTempColor(hex); // 현재 HEX 저장
5592
5595
  setIsOpen(true);
5593
- }, [type, formatted]);
5596
+ }, [type, hex]);
5594
5597
  const handleModalApply = React.useCallback(() => {
5595
5598
  if (type === 'modal') {
5596
5599
  onApply?.(formatted);
@@ -5599,7 +5602,6 @@
5599
5602
  }, [type, formatted, onApply]);
5600
5603
  const handleModalCancel = React.useCallback(() => {
5601
5604
  if (type === 'modal') {
5602
- // 원래 색상으로 복원
5603
5605
  const rgb = hexToRgb(tempColor);
5604
5606
  if (rgb) {
5605
5607
  const { h, s, v } = rgbToHsv(rgb.r, rgb.g, rgb.b);
@@ -5611,7 +5613,7 @@
5611
5613
  setIsOpen(false);
5612
5614
  }
5613
5615
  }, [type, tempColor, onCancel]);
5614
- /** 배경 스타일 */
5616
+ /** 배경 스타일들 */
5615
5617
  const hueTrackStyle = React.useMemo(() => ({
5616
5618
  background: `linear-gradient(to right,
5617
5619
  hsl(0,100%,50%),
@@ -5622,13 +5624,14 @@
5622
5624
  hsl(300,100%,50%),
5623
5625
  hsl(360,100%,50%))`,
5624
5626
  }), []);
5627
+ // 상단 좌→우: 채도 증가 / 상단→하단: 밝기 감소
5625
5628
  const areaBackground = React.useMemo(() => ({
5626
5629
  backgroundImage: `
5627
5630
  linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)),
5628
5631
  linear-gradient(to right, #ffffff, hsl(${h}, 100%, 50%))
5629
5632
  `,
5630
5633
  }), [h]);
5631
- /** ✔︎ 알파 트랙: 색상 무관, 고정 흑백 그라디언트 */
5634
+ // 고정 흑백 알파 트랙 (색상 무관)
5632
5635
  const alphaTrackStyle = React.useMemo(() => ({
5633
5636
  backgroundImage: `
5634
5637
  linear-gradient(45deg, var(--db-border-base) 25%, transparent 25%),
@@ -5647,7 +5650,7 @@
5647
5650
  'designbase-color-picker--open': isOpen,
5648
5651
  'designbase-color-picker--no-input': !showInput,
5649
5652
  }, className);
5650
- const Trigger = (jsxRuntime.jsxs("div", { className: "designbase-color-picker__trigger", onClick: () => !disabled && !readonly && handleModalOpen(), children: [jsxRuntime.jsx("div", { className: "designbase-color-picker__color-display", children: jsxRuntime.jsx("div", { className: "designbase-color-picker__color-box", style: { backgroundColor: showAlpha ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a / 100})` : hex } }) }), showInput && (jsxRuntime.jsx("input", { type: "text", value: colorInput, onChange: (e) => setColorInput(e.target.value), onKeyDown: onColorKeyDown, onBlur: onColorBlur, onClick: (e) => e.stopPropagation(), disabled: disabled, readOnly: readonly, className: "designbase-color-picker__input", placeholder: "#000000" })), showInput && showCopyButton && (jsxRuntime.jsx("button", { type: "button", className: "designbase-color-picker__copy-button-inline", onClick: (e) => { e.stopPropagation(); onCopy(); }, disabled: disabled, "aria-label": "Copy color value", children: isCopied ? jsxRuntime.jsx(icons.DoneIcon, { size: 14 }) : jsxRuntime.jsx(icons.CopyIcon, { size: 14 }) })), jsxRuntime.jsx("button", { type: "button", className: "designbase-color-picker__toggle", disabled: disabled, "aria-label": "Toggle color picker", children: jsxRuntime.jsx(icons.ChevronDownIcon, { size: 16 }) })] }));
5653
+ const Trigger = (jsxRuntime.jsxs("div", { className: "designbase-color-picker__trigger", onClick: () => !disabled && !readonly && (type === 'modal' ? handleModalOpen() : setIsOpen(v => !v)), children: [jsxRuntime.jsx("div", { className: "designbase-color-picker__color-display", children: jsxRuntime.jsx("div", { className: "designbase-color-picker__color-box", style: { backgroundColor: showAlpha ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a / 100})` : hex } }) }), showInput && (jsxRuntime.jsx("input", { type: "text", value: colorInput, onChange: (e) => setColorInput(e.target.value), onKeyDown: onColorKeyDown, onBlur: onColorBlur, onClick: (e) => e.stopPropagation(), disabled: disabled, readOnly: readonly, className: "designbase-color-picker__input", placeholder: "#000000" })), showInput && showCopyButton && (jsxRuntime.jsx("button", { type: "button", className: "designbase-color-picker__copy-button-inline", onClick: (e) => { e.stopPropagation(); onCopy(); }, disabled: disabled, "aria-label": "Copy color value", children: isCopied ? jsxRuntime.jsx(icons.DoneIcon, { size: 14 }) : jsxRuntime.jsx(icons.CopyIcon, { size: 14 }) })), jsxRuntime.jsx("button", { type: "button", className: "designbase-color-picker__toggle", disabled: disabled, "aria-label": "Toggle color picker", children: jsxRuntime.jsx(icons.ChevronDownIcon, { size: 16 }) })] }));
5651
5654
  const Selector = (jsxRuntime.jsxs("div", { className: "designbase-color-picker__selector", children: [jsxRuntime.jsx("div", { className: "designbase-color-picker__color-area", children: jsxRuntime.jsx("div", { ref: areaRef, className: "designbase-color-picker__color-field", style: areaBackground, onMouseDown: onAreaMouseDown, onMouseMove: onAreaMouseMove, onMouseUp: onAreaMouseUp, onMouseLeave: onAreaLeave, onTouchStart: onAreaTouchStart, onTouchMove: onAreaTouchMove, onTouchEnd: onAreaTouchEnd, children: jsxRuntime.jsx("div", { className: "designbase-color-picker__color-pointer", style: { left: `${s}%`, top: `${100 - v}%`, backgroundColor: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})` } }) }) }), jsxRuntime.jsxs("div", { className: "designbase-color-picker__controls", children: [jsxRuntime.jsx(Button, { variant: "tertiary", size: "m", iconOnly: true, onClick: onEyedrop, "aria-label": "Eyedropper tool", children: jsxRuntime.jsx(icons.EyedropperIcon, {}) }), jsxRuntime.jsxs("div", { className: "designbase-color-picker__slider-container", children: [jsxRuntime.jsx("div", { className: "designbase-color-picker__hue-slider", children: jsxRuntime.jsx("input", { type: "range", min: 0, max: 360, value: h, onChange: onHueChange, className: "designbase-color-picker__slider designbase-color-picker__slider--hue", style: hueTrackStyle }) }), showAlpha && (jsxRuntime.jsx("div", { className: "designbase-color-picker__alpha-slider", children: jsxRuntime.jsx("input", { type: "range", min: 0, max: 100, value: a, onChange: onAlphaChange, className: "designbase-color-picker__slider designbase-color-picker__slider--alpha", style: alphaTrackStyle }) }))] })] }), jsxRuntime.jsxs("div", { className: "designbase-color-picker__value-display", children: [showFormatSelector && (jsxRuntime.jsx(Select, { value: format, onChange: (v) => setFormat(v), showClearButton: false, options: [
5652
5655
  { label: 'HEX', value: 'hex' },
5653
5656
  { label: 'RGB', value: 'rgb' },
@@ -5655,7 +5658,7 @@
5655
5658
  { label: 'HSL', value: 'hsl' },
5656
5659
  { label: 'HSLA', value: 'hsla' },
5657
5660
  ], size: "s", position: "top" })), jsxRuntime.jsx(Input, { type: "text", value: colorInput, onChange: setColorInput, onKeyDown: onColorKeyDown, onBlur: onColorBlur, placeholder: "#000000", size: "s", className: "designbase-color-picker__value-input" }), showAlpha && (jsxRuntime.jsxs("div", { className: "designbase-color-picker__alpha-input-wrap", children: [jsxRuntime.jsx(Input, { type: "text", inputMode: "numeric", value: alphaInput, onChange: (value) => setAlphaInput(value.replace(/[^\d]/g, '').slice(0, 3)), onKeyDown: onAlphaInputKeyDown, onBlur: onAlphaInputBlur, placeholder: "100", size: "s", className: "designbase-color-picker__alpha-input", "aria-label": "Alpha percent" }), jsxRuntime.jsx("span", { className: "designbase-color-picker__alpha-suffix", children: "%" })] })), showCopyButton && (jsxRuntime.jsx(Button, { variant: "tertiary", size: "m", iconOnly: true, onClick: onCopy, "aria-label": "Copy color value", children: isCopied ? jsxRuntime.jsx(icons.DoneIcon, {}) : jsxRuntime.jsx(icons.CopyIcon, {}) }))] })] }));
5658
- return (jsxRuntime.jsxs("div", { ref: pickerRef, className: classes, children: [Trigger, type === 'dropdown' && isOpen && (jsxRuntime.jsx("div", { className: "designbase-color-picker__dropdown", onClick: (e) => e.stopPropagation(), children: Selector })), type === 'modal' && (jsxRuntime.jsxs(Modal, { isOpen: isOpen, onClose: handleModalCancel, title: "\uC0C9\uC0C1 \uC120\uD0DD", size: "s", children: [jsxRuntime.jsx(ModalBody, { children: Selector }), jsxRuntime.jsx(ModalFooter, { children: jsxRuntime.jsxs("div", { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' }, children: [jsxRuntime.jsx(Button, { variant: "tertiary", size: "s", onClick: handleModalCancel, children: "\uCDE8\uC18C" }), jsxRuntime.jsx(Button, { variant: "primary", size: "s", onClick: handleModalApply, children: "\uC801\uC6A9" })] }) })] }))] }));
5661
+ return (jsxRuntime.jsxs("div", { ref: pickerRef, className: classes, children: [Trigger, type === 'dropdown' && isOpen && (jsxRuntime.jsx("div", { className: "designbase-color-picker__dropdown", onClick: (e) => e.stopPropagation(), children: Selector })), type === 'modal' && (jsxRuntime.jsxs(Modal, { isOpen: isOpen, onClose: handleModalCancel, title: "\uC0C9\uC0C1 \uC120\uD0DD", size: "s", children: [jsxRuntime.jsx(ModalBody, { children: Selector }), jsxRuntime.jsx(ModalFooter, { children: jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 8, justifyContent: 'flex-end' }, children: [jsxRuntime.jsx(Button, { variant: "tertiary", size: "s", onClick: handleModalCancel, children: "\uCDE8\uC18C" }), jsxRuntime.jsx(Button, { variant: "primary", size: "s", onClick: handleModalApply, children: "\uC801\uC6A9" })] }) })] }))] }));
5659
5662
  };
5660
5663
  ColorPicker.displayName = 'ColorPicker';
5661
5664