@designbasekorea/ui 0.2.29 → 0.2.30

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
@@ -5352,7 +5352,11 @@ const hsvToRgb = (h, s, v) => {
5352
5352
  gp = 0;
5353
5353
  bp = x;
5354
5354
  }
5355
- return { r: Math.round((rp + m) * 255), g: Math.round((gp + m) * 255), b: Math.round((bp + m) * 255) };
5355
+ return {
5356
+ r: Math.round((rp + m) * 255),
5357
+ g: Math.round((gp + m) * 255),
5358
+ b: Math.round((bp + m) * 255),
5359
+ };
5356
5360
  };
5357
5361
  const rgbToHsl = (r, g, b) => {
5358
5362
  r /= 255;
@@ -5377,11 +5381,15 @@ const rgbToHsl = (r, g, b) => {
5377
5381
  }
5378
5382
  h *= 60;
5379
5383
  }
5380
- return { h: Math.round(h * 100) / 100, s: Math.round(s * 10000) / 100, l: Math.round(l * 10000) / 100 };
5384
+ return {
5385
+ h: Math.round(h * 100) / 100,
5386
+ s: Math.round(s * 10000) / 100,
5387
+ l: Math.round(l * 10000) / 100,
5388
+ };
5381
5389
  };
5382
5390
  /* ----------------------- 컴포넌트 ----------------------- */
5383
5391
  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
- /** 내부 상태 (HSV + alpha%) */
5392
+ /** 내부 HSV + alpha */
5385
5393
  const [h, setH] = useState(211);
5386
5394
  const [s, setS] = useState(100);
5387
5395
  const [v, setV] = useState(50);
@@ -5389,15 +5397,13 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5389
5397
  const [isOpen, setIsOpen] = useState(false);
5390
5398
  const [format, setFormat] = useState('hex');
5391
5399
  const [isCopied, setIsCopied] = useState(false);
5392
- // 하단 인풋(문자열) — 엔터로만 확정
5393
5400
  const [colorInput, setColorInput] = useState('');
5394
5401
  const [alphaInput, setAlphaInput] = useState('100');
5395
- // 모달용 임시 상태 (취소 시 되돌리기)
5396
5402
  const [tempColor, setTempColor] = useState('');
5397
5403
  const pickerRef = useRef(null);
5398
5404
  const areaRef = useRef(null);
5399
5405
  const dragging = useRef(false);
5400
- /** 초기화 */
5406
+ /** 초기값 세팅 */
5401
5407
  useEffect(() => {
5402
5408
  const initial = (value || defaultValue).toUpperCase();
5403
5409
  const rgb = hexToRgb(initial);
@@ -5407,12 +5413,10 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5407
5413
  setS(s);
5408
5414
  setV(v);
5409
5415
  }
5410
- // 인풋 초기 표현
5411
5416
  setColorInput(initial);
5412
5417
  setAlphaInput('100');
5413
- // eslint-disable-next-line react-hooks/exhaustive-deps
5414
5418
  }, []);
5415
- /** 외부 value 변화 시(제어형)만 HSV 반영 */
5419
+ /** 제어형(value) HSV */
5416
5420
  useEffect(() => {
5417
5421
  if (!value)
5418
5422
  return;
@@ -5424,11 +5428,11 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5424
5428
  setV(v);
5425
5429
  }
5426
5430
  }, [value]);
5427
- /** 파생값 */
5431
+ /** RGB/HSL 계산 */
5428
5432
  const rgb = useMemo(() => hsvToRgb(h, s, v), [h, s, v]);
5429
5433
  const hex = useMemo(() => rgbToHex(rgb.r, rgb.g, rgb.b), [rgb]);
5430
5434
  const hsl = useMemo(() => rgbToHsl(rgb.r, rgb.g, rgb.b), [rgb]);
5431
- /** 포맷 출력 문자열 */
5435
+ /** 출력값 formatted */
5432
5436
  const formatted = useMemo(() => {
5433
5437
  const alpha = a / 100;
5434
5438
  switch (format) {
@@ -5440,22 +5444,24 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5440
5444
  default: return hex;
5441
5445
  }
5442
5446
  }, [format, hex, rgb, hsl, a]);
5443
- /** 외부 onChange 알림만 (입력창 동기화는 여기서 하지 않음!) */
5447
+ /** 무한 루프 방지: formatted이 부모 value랑 같으면 onChange 호출하지 않음 */
5444
5448
  useEffect(() => {
5449
+ if (value !== undefined && value.toUpperCase() === formatted.toUpperCase())
5450
+ return;
5445
5451
  onChange?.(formatted);
5446
- // eslint-disable-next-line react-hooks/exhaustive-deps
5447
- }, [formatted]);
5452
+ }, [formatted, onChange, value]);
5448
5453
  /** 드롭다운 외부 클릭 닫기 */
5449
5454
  useEffect(() => {
5450
5455
  const handler = (e) => {
5451
- if (pickerRef.current && !pickerRef.current.contains(e.target))
5456
+ if (pickerRef.current && !pickerRef.current.contains(e.target)) {
5452
5457
  setIsOpen(false);
5458
+ }
5453
5459
  };
5454
5460
  if (isOpen && type === 'dropdown')
5455
5461
  document.addEventListener('mousedown', handler);
5456
5462
  return () => document.removeEventListener('mousedown', handler);
5457
5463
  }, [isOpen, type]);
5458
- /** 컬러 영역 (S x V) 마우스/터치 */
5464
+ /** S/V 영역 업데이트 */
5459
5465
  const updateFromArea = (clientX, clientY) => {
5460
5466
  const el = areaRef.current;
5461
5467
  if (!el)
@@ -5482,12 +5488,11 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5482
5488
  const onAlphaChange = useCallback((e) => {
5483
5489
  const newAlpha = parseInt(e.target.value, 10);
5484
5490
  setA(newAlpha);
5485
- setAlphaInput(String(newAlpha)); // 슬라이더 ↔ 인풋 동기화(표시만)
5491
+ setAlphaInput(String(newAlpha));
5486
5492
  }, []);
5487
- /** 컬러 텍스트 인풋: 엔터로만 확정 */
5493
+ /** 컬러 인풋 (엔터 확정) */
5488
5494
  const commitColorInput = useCallback(() => {
5489
5495
  const str = colorInput.trim();
5490
- // HEX (#RRGGBB)
5491
5496
  if (/^#([0-9A-Fa-f]{6})$/.test(str)) {
5492
5497
  const rgb = hexToRgb(str);
5493
5498
  const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
@@ -5497,7 +5502,6 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5497
5502
  setColorInput(str.toUpperCase());
5498
5503
  return;
5499
5504
  }
5500
- // rgb(r,g,b)
5501
5505
  let m = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.exec(str);
5502
5506
  if (m) {
5503
5507
  const r = clamp(parseInt(m[1], 10), 0, 255);
@@ -5510,7 +5514,6 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5510
5514
  setColorInput(`rgb(${r}, ${g}, ${b})`.toUpperCase());
5511
5515
  return;
5512
5516
  }
5513
- // rgba(r,g,b,a)
5514
5517
  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
5518
  if (m) {
5516
5519
  const r = clamp(parseInt(m[1], 10), 0, 255);
@@ -5526,18 +5529,14 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5526
5529
  setColorInput(`rgba(${r}, ${g}, ${b}, ${alpha})`.toUpperCase());
5527
5530
  return;
5528
5531
  }
5529
- // 잘못된 값 → 원복
5530
5532
  setColorInput(formatted.toUpperCase());
5531
5533
  }, [colorInput, formatted]);
5532
5534
  const onColorKeyDown = (e) => {
5533
5535
  if (e.key === 'Enter')
5534
5536
  commitColorInput();
5535
5537
  };
5536
- const onColorBlur = () => {
5537
- // 요구사항: 엔터로만 반영, 블러 시엔 원복
5538
- setColorInput(formatted.toUpperCase());
5539
- };
5540
- /** 알파 인풋: 엔터로만 확정 (0~100) */
5538
+ const onColorBlur = () => setColorInput(formatted.toUpperCase());
5539
+ /** 알파 인풋 (엔터 확정) */
5541
5540
  const commitAlphaInput = useCallback(() => {
5542
5541
  const n = Number(alphaInput.trim());
5543
5542
  if (!Number.isNaN(n) && n >= 0 && n <= 100) {
@@ -5545,17 +5544,13 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5545
5544
  setAlphaInput(String(Math.round(n)));
5546
5545
  return;
5547
5546
  }
5548
- // 잘못된 값 → 원복
5549
5547
  setAlphaInput(String(a));
5550
5548
  }, [alphaInput, a]);
5551
5549
  const onAlphaInputKeyDown = (e) => {
5552
5550
  if (e.key === 'Enter')
5553
5551
  commitAlphaInput();
5554
5552
  };
5555
- const onAlphaInputBlur = () => {
5556
- // 엔터로만 반영, 블러 시 원복
5557
- setAlphaInput(String(a));
5558
- };
5553
+ const onAlphaInputBlur = () => setAlphaInput(String(a));
5559
5554
  /** 복사 */
5560
5555
  const onCopy = useCallback(async () => {
5561
5556
  try {
@@ -5579,18 +5574,15 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5579
5574
  setS(hsv.s);
5580
5575
  setV(hsv.v);
5581
5576
  }
5582
- else {
5583
- console.log('EyeDropper API is not supported');
5584
- }
5585
5577
  }
5586
5578
  catch (e) {
5587
5579
  console.error(e);
5588
5580
  }
5589
5581
  }, []);
5590
- /** 모달 열기/적용/취소 (deps에서 formatted 제거 → 재생성 방지) */
5582
+ /** modal open(중요: formatted deps 제거) */
5591
5583
  const handleModalOpen = useCallback(() => {
5592
5584
  if (type === 'modal')
5593
- setTempColor(hex); // 현재 HEX 저장
5585
+ setTempColor(hex);
5594
5586
  setIsOpen(true);
5595
5587
  }, [type, hex]);
5596
5588
  const handleModalApply = useCallback(() => {
@@ -5603,53 +5595,55 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
5603
5595
  if (type === 'modal') {
5604
5596
  const rgb = hexToRgb(tempColor);
5605
5597
  if (rgb) {
5606
- const { h, s, v } = rgbToHsv(rgb.r, rgb.g, rgb.b);
5607
- setH(h);
5608
- setS(s);
5609
- setV(v);
5598
+ const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
5599
+ setH(hsv.h);
5600
+ setS(hsv.s);
5601
+ setV(hsv.v);
5610
5602
  }
5611
5603
  onCancel?.();
5612
5604
  setIsOpen(false);
5613
5605
  }
5614
5606
  }, [type, tempColor, onCancel]);
5615
- /** 배경 스타일들 */
5607
+ /** 스타일 */
5616
5608
  const hueTrackStyle = useMemo(() => ({
5617
5609
  background: `linear-gradient(to right,
5618
- hsl(0,100%,50%),
5619
- hsl(60,100%,50%),
5620
- hsl(120,100%,50%),
5621
- hsl(180,100%,50%),
5622
- hsl(240,100%,50%),
5623
- hsl(300,100%,50%),
5624
- hsl(360,100%,50%))`,
5610
+ hsl(0,100%,50%),
5611
+ hsl(60,100%,50%),
5612
+ hsl(120,100%,50%),
5613
+ hsl(180,100%,50%),
5614
+ hsl(240,100%,50%),
5615
+ hsl(300,100%,50%),
5616
+ hsl(360,100%,50%))`,
5625
5617
  }), []);
5626
- // 상단 좌→우: 채도 증가 / 상단→하단: 밝기 감소
5627
5618
  const areaBackground = useMemo(() => ({
5628
5619
  backgroundImage: `
5629
- linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)),
5630
- linear-gradient(to right, #ffffff, hsl(${h}, 100%, 50%))
5631
- `,
5620
+ linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)),
5621
+ linear-gradient(to right, #ffffff, hsl(${h}, 100%, 50%))
5622
+ `,
5632
5623
  }), [h]);
5633
- // ✔ 고정 흑백 알파 트랙 (색상 무관)
5634
5624
  const alphaTrackStyle = useMemo(() => ({
5635
5625
  backgroundImage: `
5636
- linear-gradient(45deg, var(--db-border-base) 25%, transparent 25%),
5637
- linear-gradient(-45deg, var(--db-border-base) 25%, transparent 25%),
5638
- linear-gradient(45deg, transparent 75%, var(--db-border-base) 75%),
5639
- linear-gradient(-45deg, transparent 75%, var(--db-border-base) 75%),
5640
- linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,1))
5641
- `,
5626
+ linear-gradient(45deg, var(--db-border-base) 25%, transparent 25%),
5627
+ linear-gradient(-45deg, var(--db-border-base) 25%, transparent 25%),
5628
+ linear-gradient(45deg, transparent 75%, var(--db-border-base) 75%),
5629
+ linear-gradient(-45deg, transparent 75%, var(--db-border-base) 75%),
5630
+ linear-gradient(to right, rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0), rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1))
5631
+ `,
5642
5632
  backgroundSize: '8px 8px,8px 8px,8px 8px,8px 8px,100% 100%',
5643
5633
  backgroundPosition: '0 0,0 4px,4px -4px,-4px 0,0 0',
5644
5634
  backgroundColor: 'var(--db-surface-base)',
5645
- }), []);
5635
+ }), [rgb]);
5646
5636
  const classes = clsx('designbase-color-picker', `designbase-color-picker--${size}`, `designbase-color-picker--${position}`, {
5647
5637
  'designbase-color-picker--disabled': disabled,
5648
5638
  'designbase-color-picker--readonly': readonly,
5649
5639
  'designbase-color-picker--open': isOpen,
5650
5640
  'designbase-color-picker--no-input': !showInput,
5651
5641
  }, className);
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 }) })] }));
5642
+ 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: {
5643
+ backgroundColor: showAlpha
5644
+ ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a / 100})`
5645
+ : hex
5646
+ } }) }), 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 }) })] }));
5653
5647
  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: [
5654
5648
  { label: 'HEX', value: 'hex' },
5655
5649
  { label: 'RGB', value: 'rgb' },