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