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