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