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