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