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