@designbasekorea/ui 0.2.35 → 0.2.37
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.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +224 -114
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +224 -114
- package/dist/index.js.map +1 -1
- package/dist/index.umd.css +1 -1
- package/dist/index.umd.css.map +1 -1
- package/dist/index.umd.js +224 -114
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -5321,17 +5321,18 @@ Chip.displayName = 'Chip';
|
|
|
5321
5321
|
|
|
5322
5322
|
/* ----------------------- 유틸 ----------------------- */
|
|
5323
5323
|
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
5324
|
+
const normalizeHex = (s) => (s || '').trim().toUpperCase();
|
|
5325
|
+
const isHex = (s) => /^#?[0-9A-Fa-f]{6}$/.test(s || '');
|
|
5324
5326
|
const hexToRgb = (hex) => {
|
|
5325
5327
|
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
|
|
5326
5328
|
if (!m)
|
|
5327
5329
|
return null;
|
|
5328
5330
|
return { r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16) };
|
|
5329
5331
|
};
|
|
5330
|
-
const
|
|
5331
|
-
const
|
|
5332
|
-
return `#${
|
|
5332
|
+
const toHex = (r, g, b) => {
|
|
5333
|
+
const h = (x) => clamp(Math.round(x), 0, 255).toString(16).padStart(2, '0');
|
|
5334
|
+
return `#${h(r)}${h(g)}${h(b)}`.toUpperCase();
|
|
5333
5335
|
};
|
|
5334
|
-
/** RGB ↔ HSV (HSV: h[0-360], s[0-100], v[0-100]) */
|
|
5335
5336
|
const rgbToHsv = (r, g, b) => {
|
|
5336
5337
|
r /= 255;
|
|
5337
5338
|
g /= 255;
|
|
@@ -5355,7 +5356,7 @@ const rgbToHsv = (r, g, b) => {
|
|
|
5355
5356
|
}
|
|
5356
5357
|
const s = max === 0 ? 0 : d / max;
|
|
5357
5358
|
const v = max;
|
|
5358
|
-
return { h
|
|
5359
|
+
return { h, s: s * 100, v: v * 100 };
|
|
5359
5360
|
};
|
|
5360
5361
|
const hsvToRgb = (h, s, v) => {
|
|
5361
5362
|
s /= 100;
|
|
@@ -5423,92 +5424,151 @@ const rgbToHsl = (r, g, b) => {
|
|
|
5423
5424
|
}
|
|
5424
5425
|
h *= 60;
|
|
5425
5426
|
}
|
|
5426
|
-
return {
|
|
5427
|
-
h: Math.round(h * 100) / 100,
|
|
5428
|
-
s: Math.round(s * 10000) / 100,
|
|
5429
|
-
l: Math.round(l * 10000) / 100,
|
|
5430
|
-
};
|
|
5427
|
+
return { h, s: s * 100, l: l * 100 };
|
|
5431
5428
|
};
|
|
5432
5429
|
/* ----------------------- 컴포넌트 ----------------------- */
|
|
5433
|
-
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, }) => {
|
|
5434
|
-
/**
|
|
5435
|
-
const
|
|
5436
|
-
const
|
|
5437
|
-
const
|
|
5430
|
+
const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left', value, defaultValue = '#006FFF', showInput = true, showAlpha = true, showFormatSelector = true, showCopyButton = true, showEyedropper = true, disabled = false, readonly = false, onChangeFormat = 'hex', fireOnInit = false, changeDebounceMs = 0, emitDuringDrag = true, onChange, onApply, onCancel, className, }) => {
|
|
5431
|
+
/** 초기 HSV — StrictMode에서도 안전하도록 lazy init */
|
|
5432
|
+
const initialHex = (isHex(value) ? normalizeHex(value) : normalizeHex(defaultValue)) || '#006FFF';
|
|
5433
|
+
const initialRgb = hexToRgb(initialHex) || { r: 0, g: 111, b: 255 };
|
|
5434
|
+
const initialHsv = rgbToHsv(initialRgb.r, initialRgb.g, initialRgb.b);
|
|
5435
|
+
const [h, setH] = useState(() => initialHsv.h);
|
|
5436
|
+
const [s, setS] = useState(() => initialHsv.s);
|
|
5437
|
+
const [v, setV] = useState(() => initialHsv.v);
|
|
5438
5438
|
const [a, setA] = useState(100);
|
|
5439
5439
|
const [isOpen, setIsOpen] = useState(false);
|
|
5440
5440
|
const [format, setFormat] = useState('hex');
|
|
5441
5441
|
const [isCopied, setIsCopied] = useState(false);
|
|
5442
|
-
const [colorInput, setColorInput] = useState(
|
|
5442
|
+
const [colorInput, setColorInput] = useState(() => initialHex);
|
|
5443
5443
|
const [alphaInput, setAlphaInput] = useState('100');
|
|
5444
5444
|
const [tempColor, setTempColor] = useState('');
|
|
5445
5445
|
const pickerRef = useRef(null);
|
|
5446
5446
|
const areaRef = useRef(null);
|
|
5447
|
+
/** 드래그 상태/자체 발사 억제 */
|
|
5447
5448
|
const dragging = useRef(false);
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
setS(s);
|
|
5456
|
-
setV(v);
|
|
5457
|
-
}
|
|
5458
|
-
setColorInput(initial);
|
|
5459
|
-
setAlphaInput('100');
|
|
5460
|
-
}, []);
|
|
5461
|
-
/** 제어형(value) → HSV */
|
|
5449
|
+
const didMountRef = useRef(false);
|
|
5450
|
+
const lastEmittedHexRef = useRef(initialHex);
|
|
5451
|
+
const suppressNextValueSyncRef = useRef(false);
|
|
5452
|
+
const emitErrorCountRef = useRef(0);
|
|
5453
|
+
const emitBlockedUntilRef = useRef(0);
|
|
5454
|
+
const debounceTimerRef = useRef(null);
|
|
5455
|
+
/** 외부 value 변화 → 내부 HSV 반영(자기 발사 직후는 무시 가능) */
|
|
5462
5456
|
useEffect(() => {
|
|
5463
|
-
if (
|
|
5457
|
+
if (value == null)
|
|
5458
|
+
return;
|
|
5459
|
+
const norm = normalizeHex(value);
|
|
5460
|
+
if (!isHex(norm))
|
|
5461
|
+
return;
|
|
5462
|
+
// 자기 자신이 방금 보낸 값이면 동기화 스킵(미세 진동 방지)
|
|
5463
|
+
if (suppressNextValueSyncRef.current && norm === lastEmittedHexRef.current) {
|
|
5464
|
+
suppressNextValueSyncRef.current = false;
|
|
5464
5465
|
return;
|
|
5465
|
-
const rgb = hexToRgb(value);
|
|
5466
|
-
if (rgb) {
|
|
5467
|
-
const { h, s, v } = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5468
|
-
setH(h);
|
|
5469
|
-
setS(s);
|
|
5470
|
-
setV(v);
|
|
5471
5466
|
}
|
|
5467
|
+
const rgb = hexToRgb(norm);
|
|
5468
|
+
if (!rgb)
|
|
5469
|
+
return;
|
|
5470
|
+
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5471
|
+
setH(hsv.h);
|
|
5472
|
+
setS(hsv.s);
|
|
5473
|
+
setV(hsv.v);
|
|
5474
|
+
setColorInput(norm);
|
|
5472
5475
|
}, [value]);
|
|
5473
|
-
/**
|
|
5476
|
+
/** 파생값 */
|
|
5474
5477
|
const rgb = useMemo(() => hsvToRgb(h, s, v), [h, s, v]);
|
|
5475
|
-
const hex = useMemo(() =>
|
|
5478
|
+
const hex = useMemo(() => toHex(rgb.r, rgb.g, rgb.b), [rgb]);
|
|
5476
5479
|
const hsl = useMemo(() => rgbToHsl(rgb.r, rgb.g, rgb.b), [rgb]);
|
|
5477
|
-
/**
|
|
5478
|
-
const
|
|
5480
|
+
/** 표시 문자열(UI 포맷) */
|
|
5481
|
+
const uiFormatted = useMemo(() => {
|
|
5479
5482
|
const alpha = a / 100;
|
|
5480
5483
|
switch (format) {
|
|
5481
5484
|
case 'hex': return hex;
|
|
5482
5485
|
case 'rgb': return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
5483
5486
|
case 'rgba': return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha.toFixed(2)})`;
|
|
5484
|
-
case 'hsl': return `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
|
|
5485
|
-
case 'hsla': return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${alpha.toFixed(2)})`;
|
|
5487
|
+
case 'hsl': return `hsl(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%)`;
|
|
5488
|
+
case 'hsla': return `hsla(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%, ${alpha.toFixed(2)})`;
|
|
5486
5489
|
default: return hex;
|
|
5487
5490
|
}
|
|
5488
5491
|
}, [format, hex, rgb, hsl, a]);
|
|
5489
|
-
/**
|
|
5492
|
+
/** 바깥으로 내보낼 문자열 */
|
|
5493
|
+
const outward = useMemo(() => (onChangeFormat === 'hex' ? hex : uiFormatted), [hex, uiFormatted, onChangeFormat]);
|
|
5494
|
+
/** colorInput(표시 전용) 동기화 — 순수 표시만, 포커스 중이면 건드리지 않는 게 안전하지만 간단히 비교 후 반영 */
|
|
5490
5495
|
useEffect(() => {
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5496
|
+
const next = format === 'hex' ? normalizeHex(uiFormatted) : uiFormatted;
|
|
5497
|
+
if (colorInput !== next)
|
|
5498
|
+
setColorInput(next);
|
|
5494
5499
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5495
|
-
}, [
|
|
5496
|
-
/** formatted 변경시 colorInput 동기화 */
|
|
5497
|
-
useEffect(() => {
|
|
5498
|
-
setColorInput(formatted.toUpperCase());
|
|
5499
|
-
}, [formatted]);
|
|
5500
|
+
}, [uiFormatted, format]);
|
|
5500
5501
|
/** 드롭다운 외부 클릭 닫기 */
|
|
5501
5502
|
useEffect(() => {
|
|
5502
5503
|
const handler = (e) => {
|
|
5503
|
-
if (pickerRef.current && !pickerRef.current.contains(e.target))
|
|
5504
|
+
if (pickerRef.current && !pickerRef.current.contains(e.target))
|
|
5504
5505
|
setIsOpen(false);
|
|
5505
|
-
}
|
|
5506
5506
|
};
|
|
5507
5507
|
if (isOpen && type === 'dropdown')
|
|
5508
5508
|
document.addEventListener('mousedown', handler);
|
|
5509
5509
|
return () => document.removeEventListener('mousedown', handler);
|
|
5510
5510
|
}, [isOpen, type]);
|
|
5511
|
-
/**
|
|
5511
|
+
/** ===== onChange 발사 컨트롤러 ===== */
|
|
5512
|
+
const actuallyEmit = useCallback(() => {
|
|
5513
|
+
if (!onChange)
|
|
5514
|
+
return;
|
|
5515
|
+
const now = Date.now();
|
|
5516
|
+
if (emitBlockedUntilRef.current > now)
|
|
5517
|
+
return;
|
|
5518
|
+
const currentHex = normalizeHex(hex);
|
|
5519
|
+
const parentHex = isHex(value) ? normalizeHex(value) : null;
|
|
5520
|
+
// 부모값과 동치면 발사 금지
|
|
5521
|
+
if (parentHex && parentHex === currentHex)
|
|
5522
|
+
return;
|
|
5523
|
+
// 직전 발사와 동일하면 발사 금지
|
|
5524
|
+
if (lastEmittedHexRef.current === currentHex)
|
|
5525
|
+
return;
|
|
5526
|
+
try {
|
|
5527
|
+
lastEmittedHexRef.current = currentHex;
|
|
5528
|
+
suppressNextValueSyncRef.current = true; // 다음 value 동기화 스킵(왕복 진동 방지)
|
|
5529
|
+
onChange(onChangeFormat === 'hex' ? currentHex : outward);
|
|
5530
|
+
emitErrorCountRef.current = 0;
|
|
5531
|
+
}
|
|
5532
|
+
catch {
|
|
5533
|
+
emitErrorCountRef.current += 1;
|
|
5534
|
+
if (emitErrorCountRef.current >= 20) {
|
|
5535
|
+
emitBlockedUntilRef.current = Date.now() + 3000;
|
|
5536
|
+
console.warn('[ColorPicker] onChange errors too frequent; temporarily muted.');
|
|
5537
|
+
}
|
|
5538
|
+
}
|
|
5539
|
+
}, [hex, outward, onChange, onChangeFormat, value]);
|
|
5540
|
+
/** 변경 감지 → 발사(드래그 중 정책 + 디바운스 반영) */
|
|
5541
|
+
useEffect(() => {
|
|
5542
|
+
// 첫 렌더
|
|
5543
|
+
if (!didMountRef.current) {
|
|
5544
|
+
didMountRef.current = true;
|
|
5545
|
+
if (!fireOnInit)
|
|
5546
|
+
return;
|
|
5547
|
+
}
|
|
5548
|
+
// 드래그 중이면 정책 적용
|
|
5549
|
+
if (dragging.current && !emitDuringDrag) {
|
|
5550
|
+
// 드래그 끝에서 발사하므로 지금은 무시
|
|
5551
|
+
return;
|
|
5552
|
+
}
|
|
5553
|
+
if (!onChange)
|
|
5554
|
+
return;
|
|
5555
|
+
if (changeDebounceMs > 0) {
|
|
5556
|
+
if (debounceTimerRef.current)
|
|
5557
|
+
window.clearTimeout(debounceTimerRef.current);
|
|
5558
|
+
debounceTimerRef.current = window.setTimeout(() => {
|
|
5559
|
+
actuallyEmit();
|
|
5560
|
+
}, changeDebounceMs);
|
|
5561
|
+
return () => {
|
|
5562
|
+
if (debounceTimerRef.current)
|
|
5563
|
+
window.clearTimeout(debounceTimerRef.current);
|
|
5564
|
+
};
|
|
5565
|
+
}
|
|
5566
|
+
else {
|
|
5567
|
+
actuallyEmit();
|
|
5568
|
+
}
|
|
5569
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5570
|
+
}, [hex, a, format]); // h/s/v → hex로 수렴됨. a/format도 외부로 나갈 수 있으니 포함.
|
|
5571
|
+
/** ===== SV 영역 ===== */
|
|
5512
5572
|
const updateFromArea = (clientX, clientY) => {
|
|
5513
5573
|
const el = areaRef.current;
|
|
5514
5574
|
if (!el)
|
|
@@ -5516,28 +5576,71 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5516
5576
|
const rect = el.getBoundingClientRect();
|
|
5517
5577
|
const x = clamp(clientX - rect.left, 0, rect.width);
|
|
5518
5578
|
const y = clamp(clientY - rect.top, 0, rect.height);
|
|
5519
|
-
const newS =
|
|
5520
|
-
const newV =
|
|
5579
|
+
const newS = (x / rect.width) * 100;
|
|
5580
|
+
const newV = 100 - (y / rect.height) * 100;
|
|
5581
|
+
// 소수점 그대로 보관 → 색 떨림 감소
|
|
5521
5582
|
setS(newS);
|
|
5522
5583
|
setV(newV);
|
|
5523
5584
|
};
|
|
5524
|
-
const onAreaMouseDown = (e) => {
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
const
|
|
5531
|
-
|
|
5532
|
-
|
|
5585
|
+
const onAreaMouseDown = (e) => {
|
|
5586
|
+
if (disabled || readonly)
|
|
5587
|
+
return;
|
|
5588
|
+
dragging.current = true;
|
|
5589
|
+
updateFromArea(e.clientX, e.clientY);
|
|
5590
|
+
};
|
|
5591
|
+
const onAreaMouseMove = (e) => {
|
|
5592
|
+
if (!dragging.current || disabled || readonly)
|
|
5593
|
+
return;
|
|
5594
|
+
updateFromArea(e.clientX, e.clientY);
|
|
5595
|
+
};
|
|
5596
|
+
const finishDrag = () => {
|
|
5597
|
+
if (!dragging.current)
|
|
5598
|
+
return;
|
|
5599
|
+
dragging.current = false;
|
|
5600
|
+
// 드래그 종료 시 한 번만 발사
|
|
5601
|
+
if (!emitDuringDrag) {
|
|
5602
|
+
if (changeDebounceMs > 0) {
|
|
5603
|
+
if (debounceTimerRef.current)
|
|
5604
|
+
window.clearTimeout(debounceTimerRef.current);
|
|
5605
|
+
debounceTimerRef.current = window.setTimeout(() => {
|
|
5606
|
+
actuallyEmit();
|
|
5607
|
+
}, changeDebounceMs);
|
|
5608
|
+
}
|
|
5609
|
+
else {
|
|
5610
|
+
actuallyEmit();
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
};
|
|
5614
|
+
const onAreaMouseUp = finishDrag;
|
|
5615
|
+
const onAreaLeave = finishDrag;
|
|
5616
|
+
const onAreaTouchStart = (e) => {
|
|
5617
|
+
if (disabled || readonly)
|
|
5618
|
+
return;
|
|
5619
|
+
dragging.current = true;
|
|
5620
|
+
const t = e.touches[0];
|
|
5621
|
+
updateFromArea(t.clientX, t.clientY);
|
|
5622
|
+
};
|
|
5623
|
+
const onAreaTouchMove = (e) => {
|
|
5624
|
+
if (!dragging.current || disabled || readonly)
|
|
5625
|
+
return;
|
|
5626
|
+
const t = e.touches[0];
|
|
5627
|
+
updateFromArea(t.clientX, t.clientY);
|
|
5628
|
+
};
|
|
5629
|
+
const onAreaTouchEnd = finishDrag;
|
|
5533
5630
|
/** 슬라이더 */
|
|
5534
|
-
const onHueChange = useCallback((e) =>
|
|
5631
|
+
const onHueChange = useCallback((e) => {
|
|
5632
|
+
if (disabled || readonly)
|
|
5633
|
+
return;
|
|
5634
|
+
setH(parseFloat(e.target.value));
|
|
5635
|
+
}, [disabled, readonly]);
|
|
5535
5636
|
const onAlphaChange = useCallback((e) => {
|
|
5637
|
+
if (disabled || readonly)
|
|
5638
|
+
return;
|
|
5536
5639
|
const newAlpha = parseInt(e.target.value, 10);
|
|
5537
5640
|
setA(newAlpha);
|
|
5538
5641
|
setAlphaInput(String(newAlpha));
|
|
5539
|
-
}, []);
|
|
5540
|
-
/**
|
|
5642
|
+
}, [disabled, readonly]);
|
|
5643
|
+
/** 입력 확정 */
|
|
5541
5644
|
const commitColorInput = useCallback(() => {
|
|
5542
5645
|
const str = colorInput.trim();
|
|
5543
5646
|
if (/^#([0-9A-Fa-f]{6})$/.test(str)) {
|
|
@@ -5546,7 +5649,7 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5546
5649
|
setH(hsv.h);
|
|
5547
5650
|
setS(hsv.s);
|
|
5548
5651
|
setV(hsv.v);
|
|
5549
|
-
setColorInput(str
|
|
5652
|
+
setColorInput(normalizeHex(str));
|
|
5550
5653
|
return;
|
|
5551
5654
|
}
|
|
5552
5655
|
let m = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.exec(str);
|
|
@@ -5558,7 +5661,7 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5558
5661
|
setH(hsv.h);
|
|
5559
5662
|
setS(hsv.s);
|
|
5560
5663
|
setV(hsv.v);
|
|
5561
|
-
setColorInput(`rgb(${r}, ${g}, ${b})
|
|
5664
|
+
setColorInput(`rgb(${r}, ${g}, ${b})`);
|
|
5562
5665
|
return;
|
|
5563
5666
|
}
|
|
5564
5667
|
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);
|
|
@@ -5573,42 +5676,49 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5573
5676
|
setV(hsv.v);
|
|
5574
5677
|
setA(Math.round(alpha * 100));
|
|
5575
5678
|
setAlphaInput(String(Math.round(alpha * 100)));
|
|
5576
|
-
setColorInput(`rgba(${r}, ${g}, ${b}, ${alpha})
|
|
5577
|
-
return;
|
|
5578
|
-
}
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5679
|
+
setColorInput(`rgba(${r}, ${g}, ${b}, ${alpha})`);
|
|
5680
|
+
return;
|
|
5681
|
+
}
|
|
5682
|
+
// 인식 실패 → 표시값으로 원복
|
|
5683
|
+
const next = format === 'hex' ? normalizeHex(uiFormatted) : uiFormatted;
|
|
5684
|
+
if (colorInput !== next)
|
|
5685
|
+
setColorInput(next);
|
|
5686
|
+
}, [colorInput, uiFormatted, format]);
|
|
5687
|
+
const onColorKeyDown = (e) => { if (e.key === 'Enter')
|
|
5688
|
+
commitColorInput(); };
|
|
5689
|
+
const onColorBlur = () => {
|
|
5690
|
+
const next = format === 'hex' ? normalizeHex(uiFormatted) : uiFormatted;
|
|
5691
|
+
if (colorInput !== next)
|
|
5692
|
+
setColorInput(next);
|
|
5693
|
+
};
|
|
5694
|
+
/** 알파 입력 확정 */
|
|
5587
5695
|
const commitAlphaInput = useCallback(() => {
|
|
5588
5696
|
const n = Number(alphaInput.trim());
|
|
5589
5697
|
if (!Number.isNaN(n) && n >= 0 && n <= 100) {
|
|
5590
|
-
|
|
5591
|
-
|
|
5698
|
+
const rounded = Math.round(n);
|
|
5699
|
+
if (a !== rounded)
|
|
5700
|
+
setA(rounded);
|
|
5701
|
+
if (alphaInput !== String(rounded))
|
|
5702
|
+
setAlphaInput(String(rounded));
|
|
5592
5703
|
return;
|
|
5593
5704
|
}
|
|
5594
5705
|
setAlphaInput(String(a));
|
|
5595
5706
|
}, [alphaInput, a]);
|
|
5596
|
-
const onAlphaInputKeyDown = (e) => {
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
const onAlphaInputBlur = () => setAlphaInput(String(a));
|
|
5707
|
+
const onAlphaInputKeyDown = (e) => { if (e.key === 'Enter')
|
|
5708
|
+
commitAlphaInput(); };
|
|
5709
|
+
const onAlphaInputBlur = () => { if (alphaInput !== String(a))
|
|
5710
|
+
setAlphaInput(String(a)); };
|
|
5601
5711
|
/** 복사 */
|
|
5602
5712
|
const onCopy = useCallback(async () => {
|
|
5603
5713
|
try {
|
|
5604
|
-
await navigator.clipboard.writeText(
|
|
5714
|
+
await navigator.clipboard.writeText(uiFormatted);
|
|
5605
5715
|
setIsCopied(true);
|
|
5606
5716
|
setTimeout(() => setIsCopied(false), 1600);
|
|
5607
5717
|
}
|
|
5608
5718
|
catch (e) {
|
|
5609
5719
|
console.error(e);
|
|
5610
5720
|
}
|
|
5611
|
-
}, [
|
|
5721
|
+
}, [uiFormatted]);
|
|
5612
5722
|
/** EyeDropper */
|
|
5613
5723
|
const onEyedrop = useCallback(async () => {
|
|
5614
5724
|
try {
|
|
@@ -5626,7 +5736,8 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5626
5736
|
console.error(e);
|
|
5627
5737
|
}
|
|
5628
5738
|
}, []);
|
|
5629
|
-
|
|
5739
|
+
const isEyedropperAvailable = typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
5740
|
+
/** 모달 */
|
|
5630
5741
|
const handleModalOpen = useCallback(() => {
|
|
5631
5742
|
if (type === 'modal')
|
|
5632
5743
|
setTempColor(hex);
|
|
@@ -5634,10 +5745,11 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5634
5745
|
}, [type, hex]);
|
|
5635
5746
|
const handleModalApply = useCallback(() => {
|
|
5636
5747
|
if (type === 'modal') {
|
|
5637
|
-
|
|
5748
|
+
const out = onChangeFormat === 'hex' ? hex : uiFormatted;
|
|
5749
|
+
onApply?.(out);
|
|
5638
5750
|
setIsOpen(false);
|
|
5639
5751
|
}
|
|
5640
|
-
}, [type,
|
|
5752
|
+
}, [type, hex, uiFormatted, onApply, onChangeFormat]);
|
|
5641
5753
|
const handleModalCancel = useCallback(() => {
|
|
5642
5754
|
if (type === 'modal') {
|
|
5643
5755
|
const rgb = hexToRgb(tempColor);
|
|
@@ -5654,28 +5766,28 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5654
5766
|
/** 스타일 */
|
|
5655
5767
|
const hueTrackStyle = useMemo(() => ({
|
|
5656
5768
|
background: `linear-gradient(to right,
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5769
|
+
hsl(0,100%,50%),
|
|
5770
|
+
hsl(60,100%,50%),
|
|
5771
|
+
hsl(120,100%,50%),
|
|
5772
|
+
hsl(180,100%,50%),
|
|
5773
|
+
hsl(240,100%,50%),
|
|
5774
|
+
hsl(300,100%,50%),
|
|
5775
|
+
hsl(360,100%,50%))`,
|
|
5664
5776
|
}), []);
|
|
5665
5777
|
const areaBackground = useMemo(() => ({
|
|
5666
5778
|
backgroundImage: `
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5779
|
+
linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)),
|
|
5780
|
+
linear-gradient(to right, #ffffff, hsl(${h}, 100%, 50%))
|
|
5781
|
+
`,
|
|
5670
5782
|
}), [h]);
|
|
5671
5783
|
const alphaTrackStyle = useMemo(() => ({
|
|
5672
5784
|
backgroundImage: `
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5785
|
+
linear-gradient(45deg, var(--db-border-base) 25%, transparent 25%),
|
|
5786
|
+
linear-gradient(-45deg, var(--db-border-base) 25%, transparent 25%),
|
|
5787
|
+
linear-gradient(45deg, transparent 75%, var(--db-border-base) 75%),
|
|
5788
|
+
linear-gradient(-45deg, transparent 75%, var(--db-border-base) 75%),
|
|
5789
|
+
linear-gradient(to right, rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0), rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1))
|
|
5790
|
+
`,
|
|
5679
5791
|
backgroundSize: '8px 8px,8px 8px,8px 8px,8px 8px,100% 100%',
|
|
5680
5792
|
backgroundPosition: '0 0,0 4px,4px -4px,-4px 0,0 0',
|
|
5681
5793
|
backgroundColor: 'var(--db-surface-base)',
|
|
@@ -5687,11 +5799,9 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5687
5799
|
'designbase-color-picker--no-input': !showInput,
|
|
5688
5800
|
}, className);
|
|
5689
5801
|
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: {
|
|
5690
|
-
backgroundColor: showAlpha
|
|
5691
|
-
? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a / 100})`
|
|
5692
|
-
: hex
|
|
5802
|
+
backgroundColor: showAlpha ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a / 100})` : hex
|
|
5693
5803
|
} }) }), 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 }) })] }));
|
|
5694
|
-
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: [
|
|
5804
|
+
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: [showEyedropper && isEyedropperAvailable && (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: [
|
|
5695
5805
|
{ label: 'HEX', value: 'hex' },
|
|
5696
5806
|
{ label: 'RGB', value: 'rgb' },
|
|
5697
5807
|
{ label: 'RGBA', value: 'rgba' },
|