@designbasekorea/ui 0.2.23 → 0.2.26
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 +2 -20
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.css.map +1 -1
- package/dist/index.esm.js +348 -276
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +348 -276
- 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 +348 -276
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5279,247 +5279,366 @@ const Chip = ({ label, size = 'm', variant = 'default', color = 'primary', delet
|
|
|
5279
5279
|
};
|
|
5280
5280
|
Chip.displayName = 'Chip';
|
|
5281
5281
|
|
|
5282
|
-
|
|
5283
|
-
|
|
5282
|
+
/** 유틸 */
|
|
5283
|
+
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
5284
|
+
const hexToRgb = (hex) => {
|
|
5285
|
+
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
|
|
5286
|
+
if (!m)
|
|
5287
|
+
return null;
|
|
5288
|
+
return { r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16) };
|
|
5289
|
+
};
|
|
5290
|
+
const rgbToHex = (r, g, b) => {
|
|
5291
|
+
const toHex = (n) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');
|
|
5292
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
|
|
5293
|
+
};
|
|
5294
|
+
const rgbToHsv = (r, g, b) => {
|
|
5295
|
+
r /= 255;
|
|
5296
|
+
g /= 255;
|
|
5297
|
+
b /= 255;
|
|
5298
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
5299
|
+
const d = max - min;
|
|
5300
|
+
let h = 0;
|
|
5301
|
+
if (d !== 0) {
|
|
5302
|
+
switch (max) {
|
|
5303
|
+
case r:
|
|
5304
|
+
h = ((g - b) / d + (g < b ? 6 : 0));
|
|
5305
|
+
break;
|
|
5306
|
+
case g:
|
|
5307
|
+
h = ((b - r) / d + 2);
|
|
5308
|
+
break;
|
|
5309
|
+
case b:
|
|
5310
|
+
h = ((r - g) / d + 4);
|
|
5311
|
+
break;
|
|
5312
|
+
}
|
|
5313
|
+
h *= 60;
|
|
5314
|
+
}
|
|
5315
|
+
const s = max === 0 ? 0 : d / max;
|
|
5316
|
+
const v = max;
|
|
5317
|
+
return { h: Math.round(h * 100) / 100, s: Math.round(s * 10000) / 100, v: Math.round(v * 10000) / 100 };
|
|
5318
|
+
};
|
|
5319
|
+
const hsvToRgb = (h, s, v) => {
|
|
5320
|
+
s /= 100;
|
|
5321
|
+
v /= 100;
|
|
5322
|
+
const c = v * s;
|
|
5323
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
5324
|
+
const m = v - c;
|
|
5325
|
+
let rp = 0, gp = 0, bp = 0;
|
|
5326
|
+
if (h >= 0 && h < 60) {
|
|
5327
|
+
rp = c;
|
|
5328
|
+
gp = x;
|
|
5329
|
+
bp = 0;
|
|
5330
|
+
}
|
|
5331
|
+
else if (h < 120) {
|
|
5332
|
+
rp = x;
|
|
5333
|
+
gp = c;
|
|
5334
|
+
bp = 0;
|
|
5335
|
+
}
|
|
5336
|
+
else if (h < 180) {
|
|
5337
|
+
rp = 0;
|
|
5338
|
+
gp = c;
|
|
5339
|
+
bp = x;
|
|
5340
|
+
}
|
|
5341
|
+
else if (h < 240) {
|
|
5342
|
+
rp = 0;
|
|
5343
|
+
gp = x;
|
|
5344
|
+
bp = c;
|
|
5345
|
+
}
|
|
5346
|
+
else if (h < 300) {
|
|
5347
|
+
rp = x;
|
|
5348
|
+
gp = 0;
|
|
5349
|
+
bp = c;
|
|
5350
|
+
}
|
|
5351
|
+
else {
|
|
5352
|
+
rp = c;
|
|
5353
|
+
gp = 0;
|
|
5354
|
+
bp = x;
|
|
5355
|
+
}
|
|
5356
|
+
return { r: Math.round((rp + m) * 255), g: Math.round((gp + m) * 255), b: Math.round((bp + m) * 255) };
|
|
5357
|
+
};
|
|
5358
|
+
const rgbToHsl = (r, g, b) => {
|
|
5359
|
+
r /= 255;
|
|
5360
|
+
g /= 255;
|
|
5361
|
+
b /= 255;
|
|
5362
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
5363
|
+
let h = 0, s = 0;
|
|
5364
|
+
const l = (max + min) / 2;
|
|
5365
|
+
if (max !== min) {
|
|
5366
|
+
const d = max - min;
|
|
5367
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
5368
|
+
switch (max) {
|
|
5369
|
+
case r:
|
|
5370
|
+
h = ((g - b) / d + (g < b ? 6 : 0));
|
|
5371
|
+
break;
|
|
5372
|
+
case g:
|
|
5373
|
+
h = ((b - r) / d + 2);
|
|
5374
|
+
break;
|
|
5375
|
+
case b:
|
|
5376
|
+
h = ((r - g) / d + 4);
|
|
5377
|
+
break;
|
|
5378
|
+
}
|
|
5379
|
+
h *= 60;
|
|
5380
|
+
}
|
|
5381
|
+
return { h: Math.round(h * 100) / 100, s: Math.round(s * 10000) / 100, l: Math.round(l * 10000) / 100 };
|
|
5382
|
+
};
|
|
5383
|
+
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, }) => {
|
|
5384
|
+
/** 내부 상태 (HSV + alpha%) */
|
|
5385
|
+
const [h, setH] = React.useState(211);
|
|
5386
|
+
const [s, setS] = React.useState(100);
|
|
5387
|
+
const [v, setV] = React.useState(50);
|
|
5388
|
+
const [a, setA] = React.useState(100);
|
|
5284
5389
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
5285
|
-
const [
|
|
5286
|
-
const [saturation, setSaturation] = React.useState(100);
|
|
5287
|
-
const [lightness, setLightness] = React.useState(50);
|
|
5288
|
-
const [alpha, setAlpha] = React.useState(100);
|
|
5289
|
-
const [colorFormat, setColorFormat] = React.useState('hex');
|
|
5390
|
+
const [format, setFormat] = React.useState('hex');
|
|
5290
5391
|
const [isCopied, setIsCopied] = React.useState(false);
|
|
5291
|
-
|
|
5392
|
+
// 하단 컬러 인풋(문자열), 알파 인풋(문자열) – 엔터로만 확정
|
|
5393
|
+
const [colorInput, setColorInput] = React.useState('');
|
|
5394
|
+
const [alphaInput, setAlphaInput] = React.useState('100');
|
|
5395
|
+
// 모달용 임시 상태 (적용/취소 버튼용)
|
|
5396
|
+
const [tempColor, setTempColor] = React.useState('');
|
|
5292
5397
|
const pickerRef = React.useRef(null);
|
|
5293
|
-
|
|
5398
|
+
const areaRef = React.useRef(null);
|
|
5399
|
+
const dragging = React.useRef(false);
|
|
5400
|
+
/** 초기화 */
|
|
5294
5401
|
React.useEffect(() => {
|
|
5295
|
-
const
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5402
|
+
const initial = (value || defaultValue).toUpperCase();
|
|
5403
|
+
const rgb = hexToRgb(initial);
|
|
5404
|
+
if (rgb) {
|
|
5405
|
+
const { h, s, v } = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5406
|
+
setH(h);
|
|
5407
|
+
setS(s);
|
|
5408
|
+
setV(v);
|
|
5409
|
+
setColorInput(initial);
|
|
5410
|
+
setAlphaInput('100');
|
|
5299
5411
|
}
|
|
5300
|
-
|
|
5301
|
-
|
|
5412
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5413
|
+
}, []);
|
|
5414
|
+
/** 외부 value 변화 */
|
|
5302
5415
|
React.useEffect(() => {
|
|
5303
|
-
if (value)
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
}
|
|
5307
|
-
}, [value]);
|
|
5308
|
-
// HEX를 HSL로 변환
|
|
5309
|
-
const updateHSLFromHex = (hex) => {
|
|
5310
|
-
const rgb = hexToRgb(hex);
|
|
5416
|
+
if (!value)
|
|
5417
|
+
return;
|
|
5418
|
+
const rgb = hexToRgb(value);
|
|
5311
5419
|
if (rgb) {
|
|
5312
|
-
const
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
}
|
|
5317
|
-
};
|
|
5318
|
-
// HEX를 RGB로 변환
|
|
5319
|
-
const hexToRgb = (hex) => {
|
|
5320
|
-
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
5321
|
-
return result ? {
|
|
5322
|
-
r: parseInt(result[1], 16),
|
|
5323
|
-
g: parseInt(result[2], 16),
|
|
5324
|
-
b: parseInt(result[3], 16)
|
|
5325
|
-
} : null;
|
|
5326
|
-
};
|
|
5327
|
-
// RGB를 HSL로 변환 (정밀도 개선)
|
|
5328
|
-
const rgbToHsl = (r, g, b) => {
|
|
5329
|
-
r /= 255;
|
|
5330
|
-
g /= 255;
|
|
5331
|
-
b /= 255;
|
|
5332
|
-
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
5333
|
-
let h = 0, s = 0, l = (max + min) / 2;
|
|
5334
|
-
if (max !== min) {
|
|
5335
|
-
const d = max - min;
|
|
5336
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
5337
|
-
switch (max) {
|
|
5338
|
-
case r:
|
|
5339
|
-
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
5340
|
-
break;
|
|
5341
|
-
case g:
|
|
5342
|
-
h = ((b - r) / d + 2) / 6;
|
|
5343
|
-
break;
|
|
5344
|
-
case b:
|
|
5345
|
-
h = ((r - g) / d + 4) / 6;
|
|
5346
|
-
break;
|
|
5347
|
-
}
|
|
5420
|
+
const { h, s, v } = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5421
|
+
setH(h);
|
|
5422
|
+
setS(s);
|
|
5423
|
+
setV(v);
|
|
5348
5424
|
}
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
return
|
|
5364
|
-
}
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5425
|
+
}, [value]);
|
|
5426
|
+
/** 파생값 */
|
|
5427
|
+
const rgb = React.useMemo(() => hsvToRgb(h, s, v), [h, s, v]);
|
|
5428
|
+
const hex = React.useMemo(() => rgbToHex(rgb.r, rgb.g, rgb.b), [rgb]);
|
|
5429
|
+
const hsl = React.useMemo(() => rgbToHsl(rgb.r, rgb.g, rgb.b), [rgb]);
|
|
5430
|
+
/** 표시 문자열 */
|
|
5431
|
+
const formatted = React.useMemo(() => {
|
|
5432
|
+
const alpha = a / 100;
|
|
5433
|
+
switch (format) {
|
|
5434
|
+
case 'hex': return hex;
|
|
5435
|
+
case 'rgb': return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
|
|
5436
|
+
case 'rgba': return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha.toFixed(2)})`;
|
|
5437
|
+
case 'hsl': return `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
|
|
5438
|
+
case 'hsla': return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${alpha.toFixed(2)})`;
|
|
5439
|
+
default: return hex;
|
|
5440
|
+
}
|
|
5441
|
+
}, [format, hex, rgb, hsl, a]);
|
|
5442
|
+
/** 외부 onChange 및 인풋 동기화 */
|
|
5443
|
+
React.useEffect(() => {
|
|
5444
|
+
onChange?.(formatted);
|
|
5445
|
+
setColorInput(formatted.toUpperCase());
|
|
5446
|
+
setAlphaInput(String(a));
|
|
5447
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5448
|
+
}, [formatted]);
|
|
5449
|
+
/** 드롭다운 외부 클릭 닫기 */
|
|
5450
|
+
React.useEffect(() => {
|
|
5451
|
+
const handler = (e) => {
|
|
5452
|
+
if (pickerRef.current && !pickerRef.current.contains(e.target))
|
|
5453
|
+
setIsOpen(false);
|
|
5374
5454
|
};
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
}
|
|
5395
|
-
};
|
|
5396
|
-
|
|
5397
|
-
const
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
};
|
|
5401
|
-
|
|
5402
|
-
const
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
};
|
|
5408
|
-
|
|
5409
|
-
const
|
|
5410
|
-
const
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
}
|
|
5422
|
-
|
|
5423
|
-
|
|
5455
|
+
if (isOpen && type === 'dropdown')
|
|
5456
|
+
document.addEventListener('mousedown', handler);
|
|
5457
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
5458
|
+
}, [isOpen, type]);
|
|
5459
|
+
/** 컬러 필드 핸들링 */
|
|
5460
|
+
const updateFromArea = (clientX, clientY) => {
|
|
5461
|
+
const el = areaRef.current;
|
|
5462
|
+
if (!el)
|
|
5463
|
+
return;
|
|
5464
|
+
const rect = el.getBoundingClientRect();
|
|
5465
|
+
const x = clamp(clientX - rect.left, 0, rect.width);
|
|
5466
|
+
const y = clamp(clientY - rect.top, 0, rect.height);
|
|
5467
|
+
const newS = Math.round((x / rect.width) * 100);
|
|
5468
|
+
const newV = Math.round(100 - (y / rect.height) * 100);
|
|
5469
|
+
setS(newS);
|
|
5470
|
+
setV(newV);
|
|
5471
|
+
};
|
|
5472
|
+
const onAreaMouseDown = (e) => { dragging.current = true; updateFromArea(e.clientX, e.clientY); };
|
|
5473
|
+
const onAreaMouseMove = (e) => { if (dragging.current)
|
|
5474
|
+
updateFromArea(e.clientX, e.clientY); };
|
|
5475
|
+
const onAreaMouseUp = () => { dragging.current = false; };
|
|
5476
|
+
const onAreaLeave = () => { dragging.current = false; };
|
|
5477
|
+
const onAreaTouchStart = (e) => { dragging.current = true; const t = e.touches[0]; updateFromArea(t.clientX, t.clientY); };
|
|
5478
|
+
const onAreaTouchMove = (e) => { if (!dragging.current)
|
|
5479
|
+
return; const t = e.touches[0]; updateFromArea(t.clientX, t.clientY); };
|
|
5480
|
+
const onAreaTouchEnd = () => { dragging.current = false; };
|
|
5481
|
+
/** 슬라이더 */
|
|
5482
|
+
const onHueChange = (e) => setH(parseInt(e.target.value, 10));
|
|
5483
|
+
const onAlphaChange = (e) => {
|
|
5484
|
+
const newAlpha = parseInt(e.target.value, 10);
|
|
5485
|
+
setA(newAlpha);
|
|
5486
|
+
setAlphaInput(String(newAlpha)); // 실시간 동기화
|
|
5487
|
+
};
|
|
5488
|
+
/** 컬러 인풋: 엔터로만 확정 */
|
|
5489
|
+
const tryCommitColorInput = () => {
|
|
5490
|
+
const str = colorInput.trim();
|
|
5491
|
+
// HEX
|
|
5492
|
+
if (/^#([0-9A-Fa-f]{6})$/.test(str)) {
|
|
5493
|
+
const rgb = hexToRgb(str);
|
|
5494
|
+
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5495
|
+
setH(hsv.h);
|
|
5496
|
+
setS(hsv.s);
|
|
5497
|
+
setV(hsv.v);
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
// rgb()
|
|
5501
|
+
let m = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.exec(str);
|
|
5502
|
+
if (m) {
|
|
5503
|
+
const r = clamp(parseInt(m[1], 10), 0, 255);
|
|
5504
|
+
const g = clamp(parseInt(m[2], 10), 0, 255);
|
|
5505
|
+
const b = clamp(parseInt(m[3], 10), 0, 255);
|
|
5506
|
+
const hsv = rgbToHsv(r, g, b);
|
|
5507
|
+
setH(hsv.h);
|
|
5508
|
+
setS(hsv.s);
|
|
5509
|
+
setV(hsv.v);
|
|
5510
|
+
return;
|
|
5511
|
+
}
|
|
5512
|
+
// rgba()
|
|
5513
|
+
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);
|
|
5514
|
+
if (m) {
|
|
5515
|
+
const r = clamp(parseInt(m[1], 10), 0, 255);
|
|
5516
|
+
const g = clamp(parseInt(m[2], 10), 0, 255);
|
|
5517
|
+
const b = clamp(parseInt(m[3], 10), 0, 255);
|
|
5518
|
+
const alpha = parseFloat(m[4]);
|
|
5519
|
+
const hsv = rgbToHsv(r, g, b);
|
|
5520
|
+
setH(hsv.h);
|
|
5521
|
+
setS(hsv.s);
|
|
5522
|
+
setV(hsv.v);
|
|
5523
|
+
setA(Math.round(alpha * 100));
|
|
5524
|
+
return;
|
|
5525
|
+
}
|
|
5526
|
+
// 실패 → 원래 값으로 복구
|
|
5527
|
+
setColorInput(formatted.toUpperCase());
|
|
5528
|
+
};
|
|
5529
|
+
const onColorKeyDown = (e) => {
|
|
5530
|
+
if (e.key === 'Enter')
|
|
5531
|
+
tryCommitColorInput();
|
|
5532
|
+
};
|
|
5533
|
+
const onColorBlur = () => {
|
|
5534
|
+
// 요구사항: 엔터로만 변경. 블러 시에는 값 복구만.
|
|
5535
|
+
setColorInput(formatted.toUpperCase());
|
|
5536
|
+
};
|
|
5537
|
+
/** 알파 인풋: 엔터로만 확정 (0-100) */
|
|
5538
|
+
const tryCommitAlphaInput = () => {
|
|
5539
|
+
const n = Number(alphaInput.trim());
|
|
5540
|
+
if (!Number.isNaN(n) && n >= 0 && n <= 100) {
|
|
5541
|
+
setA(Math.round(n));
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
// 실패 → 복구
|
|
5545
|
+
setAlphaInput(String(a));
|
|
5546
|
+
};
|
|
5547
|
+
const onAlphaInputKeyDown = (e) => {
|
|
5548
|
+
if (e.key === 'Enter')
|
|
5549
|
+
tryCommitAlphaInput();
|
|
5550
|
+
};
|
|
5551
|
+
const onAlphaInputBlur = () => {
|
|
5552
|
+
// 엔터로만 반영. 블러 시 복원.
|
|
5553
|
+
setAlphaInput(String(a));
|
|
5554
|
+
};
|
|
5555
|
+
/** 복사 */
|
|
5556
|
+
const onCopy = async () => {
|
|
5557
|
+
try {
|
|
5558
|
+
await navigator.clipboard.writeText(formatted);
|
|
5559
|
+
setIsCopied(true);
|
|
5560
|
+
setTimeout(() => setIsCopied(false), 1600);
|
|
5424
5561
|
}
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
// 입력 필드 변경
|
|
5428
|
-
const handleInputChange = (e) => {
|
|
5429
|
-
const newValue = e.target.value.trim();
|
|
5430
|
-
setInputValue(newValue);
|
|
5431
|
-
// HEX 값이 유효하면 실시간으로 색상 업데이트
|
|
5432
|
-
if (/^#[0-9A-F]{6}$/i.test(newValue)) {
|
|
5433
|
-
const normalizedHex = newValue.toUpperCase();
|
|
5434
|
-
handleColorChange(normalizedHex);
|
|
5435
|
-
updateHSLFromHex(normalizedHex);
|
|
5562
|
+
catch (e) {
|
|
5563
|
+
console.error(e);
|
|
5436
5564
|
}
|
|
5437
5565
|
};
|
|
5438
|
-
|
|
5439
|
-
const
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
const match = inputValue.match(/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
|
|
5450
|
-
if (match) {
|
|
5451
|
-
const r = parseInt(match[1]);
|
|
5452
|
-
const g = parseInt(match[2]);
|
|
5453
|
-
const b = parseInt(match[3]);
|
|
5454
|
-
if (r <= 255 && g <= 255 && b <= 255) {
|
|
5455
|
-
const hsl = rgbToHsl(r, g, b);
|
|
5456
|
-
setHue(hsl.h);
|
|
5457
|
-
setSaturation(hsl.s);
|
|
5458
|
-
setLightness(hsl.l);
|
|
5459
|
-
const hex = hslToHex(hsl.h, hsl.s, hsl.l);
|
|
5460
|
-
handleColorChange(hex);
|
|
5461
|
-
isValid = true;
|
|
5462
|
-
}
|
|
5566
|
+
/** EyeDropper */
|
|
5567
|
+
const onEyedrop = async () => {
|
|
5568
|
+
try {
|
|
5569
|
+
if ('EyeDropper' in window) {
|
|
5570
|
+
const eye = new window.EyeDropper();
|
|
5571
|
+
const { sRGBHex } = await eye.open();
|
|
5572
|
+
const rgb = hexToRgb(sRGBHex);
|
|
5573
|
+
const hsv = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5574
|
+
setH(hsv.h);
|
|
5575
|
+
setS(hsv.s);
|
|
5576
|
+
setV(hsv.v);
|
|
5463
5577
|
}
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
else if (/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)$/i.test(inputValue)) {
|
|
5467
|
-
const match = inputValue.match(/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)$/i);
|
|
5468
|
-
if (match) {
|
|
5469
|
-
const r = parseInt(match[1]);
|
|
5470
|
-
const g = parseInt(match[2]);
|
|
5471
|
-
const b = parseInt(match[3]);
|
|
5472
|
-
const a = parseFloat(match[4]);
|
|
5473
|
-
if (r <= 255 && g <= 255 && b <= 255 && a >= 0 && a <= 1) {
|
|
5474
|
-
const hsl = rgbToHsl(r, g, b);
|
|
5475
|
-
setHue(hsl.h);
|
|
5476
|
-
setSaturation(hsl.s);
|
|
5477
|
-
setLightness(hsl.l);
|
|
5478
|
-
setAlpha(Math.round(a * 100));
|
|
5479
|
-
const hex = hslToHex(hsl.h, hsl.s, hsl.l);
|
|
5480
|
-
handleColorChange(hex);
|
|
5481
|
-
isValid = true;
|
|
5482
|
-
}
|
|
5578
|
+
else {
|
|
5579
|
+
console.log('EyeDropper API is not supported');
|
|
5483
5580
|
}
|
|
5484
5581
|
}
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
setInputValue(getFormattedColor());
|
|
5582
|
+
catch (e) {
|
|
5583
|
+
console.error(e);
|
|
5488
5584
|
}
|
|
5489
5585
|
};
|
|
5490
|
-
|
|
5491
|
-
const
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
setIsCopied(true);
|
|
5495
|
-
setTimeout(() => setIsCopied(false), 2000);
|
|
5586
|
+
/** 모달용 핸들러 */
|
|
5587
|
+
const handleModalOpen = () => {
|
|
5588
|
+
if (type === 'modal') {
|
|
5589
|
+
setTempColor(formatted);
|
|
5496
5590
|
}
|
|
5497
|
-
|
|
5498
|
-
|
|
5591
|
+
setIsOpen(true);
|
|
5592
|
+
};
|
|
5593
|
+
const handleModalApply = () => {
|
|
5594
|
+
if (type === 'modal') {
|
|
5595
|
+
onApply?.(formatted);
|
|
5596
|
+
setIsOpen(false);
|
|
5499
5597
|
}
|
|
5500
5598
|
};
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
}
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
5599
|
+
const handleModalCancel = () => {
|
|
5600
|
+
if (type === 'modal') {
|
|
5601
|
+
// 원래 색상으로 복원
|
|
5602
|
+
const rgb = hexToRgb(tempColor);
|
|
5603
|
+
if (rgb) {
|
|
5604
|
+
const { h, s, v } = rgbToHsv(rgb.r, rgb.g, rgb.b);
|
|
5605
|
+
setH(h);
|
|
5606
|
+
setS(s);
|
|
5607
|
+
setV(v);
|
|
5608
|
+
}
|
|
5609
|
+
onCancel?.();
|
|
5610
|
+
setIsOpen(false);
|
|
5514
5611
|
}
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5612
|
+
};
|
|
5613
|
+
/** 배경 스타일 */
|
|
5614
|
+
const hueTrackStyle = {
|
|
5615
|
+
background: `linear-gradient(to right,
|
|
5616
|
+
hsl(0,100%,50%),
|
|
5617
|
+
hsl(60,100%,50%),
|
|
5618
|
+
hsl(120,100%,50%),
|
|
5619
|
+
hsl(180,100%,50%),
|
|
5620
|
+
hsl(240,100%,50%),
|
|
5621
|
+
hsl(300,100%,50%),
|
|
5622
|
+
hsl(360,100%,50%))`,
|
|
5623
|
+
};
|
|
5624
|
+
const areaBackground = {
|
|
5625
|
+
backgroundImage: `
|
|
5626
|
+
linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)),
|
|
5627
|
+
linear-gradient(to right, #ffffff, hsl(${h}, 100%, 50%))
|
|
5628
|
+
`,
|
|
5629
|
+
};
|
|
5630
|
+
/** ✔︎ 알파 트랙: 색상 무관, 고정 흑백 그라디언트 */
|
|
5631
|
+
const alphaTrackStyle = {
|
|
5632
|
+
backgroundImage: `
|
|
5633
|
+
linear-gradient(45deg, var(--db-border-base) 25%, transparent 25%),
|
|
5634
|
+
linear-gradient(-45deg, var(--db-border-base) 25%, transparent 25%),
|
|
5635
|
+
linear-gradient(45deg, transparent 75%, var(--db-border-base) 75%),
|
|
5636
|
+
linear-gradient(-45deg, transparent 75%, var(--db-border-base) 75%),
|
|
5637
|
+
linear-gradient(to right, rgba(0,0,0,0), rgba(0,0,0,1))
|
|
5638
|
+
`,
|
|
5639
|
+
backgroundSize: '8px 8px,8px 8px,8px 8px,8px 8px,100% 100%',
|
|
5640
|
+
backgroundPosition: '0 0,0 4px,4px -4px,-4px 0,0 0',
|
|
5641
|
+
backgroundColor: 'var(--db-surface-base)',
|
|
5523
5642
|
};
|
|
5524
5643
|
const classes = clsx('designbase-color-picker', `designbase-color-picker--${size}`, `designbase-color-picker--${position}`, {
|
|
5525
5644
|
'designbase-color-picker--disabled': disabled,
|
|
@@ -5527,62 +5646,15 @@ const ColorPicker = ({ size = 'm', type = 'dropdown', position = 'bottom-left',
|
|
|
5527
5646
|
'designbase-color-picker--open': isOpen,
|
|
5528
5647
|
'designbase-color-picker--no-input': !showInput,
|
|
5529
5648
|
}, className);
|
|
5530
|
-
|
|
5531
|
-
const
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
setSaturation(Math.max(0, Math.min(100, saturation)));
|
|
5540
|
-
setLightness(Math.max(0, Math.min(100, lightness)));
|
|
5541
|
-
const newColor = hslToHex(hue, saturation, lightness);
|
|
5542
|
-
handleColorChange(newColor);
|
|
5543
|
-
};
|
|
5544
|
-
// 스포이드 기능 (색상 추출)
|
|
5545
|
-
const handleEyedropperClick = async () => {
|
|
5546
|
-
try {
|
|
5547
|
-
// EyeDropper API 지원 확인
|
|
5548
|
-
if ('EyeDropper' in window) {
|
|
5549
|
-
const eyeDropper = new window.EyeDropper();
|
|
5550
|
-
const result = await eyeDropper.open();
|
|
5551
|
-
const hexColor = result.sRGBHex;
|
|
5552
|
-
handleColorChange(hexColor);
|
|
5553
|
-
updateHSLFromHex(hexColor);
|
|
5554
|
-
}
|
|
5555
|
-
else {
|
|
5556
|
-
// EyeDropper API가 지원되지 않는 경우 대체 기능
|
|
5557
|
-
console.log('EyeDropper API is not supported in this browser');
|
|
5558
|
-
// 여기에 대체 기능을 구현할 수 있습니다
|
|
5559
|
-
}
|
|
5560
|
-
}
|
|
5561
|
-
catch (err) {
|
|
5562
|
-
console.error('EyeDropper failed:', err);
|
|
5563
|
-
}
|
|
5564
|
-
};
|
|
5565
|
-
// 컬러 선택 UI
|
|
5566
|
-
const renderColorSelector = () => (jsxRuntime.jsxs("div", { className: "designbase-color-picker__selector", children: [jsxRuntime.jsx("div", { className: "designbase-color-picker__color-area", children: jsxRuntime.jsx("div", { className: "designbase-color-picker__color-field", style: {
|
|
5567
|
-
background: `linear-gradient(to right,
|
|
5568
|
-
hsl(${hue}, 0%, 50%),
|
|
5569
|
-
hsl(${hue}, 100%, 50%)),
|
|
5570
|
-
linear-gradient(to bottom,
|
|
5571
|
-
hsl(${hue}, 100%, 100%),
|
|
5572
|
-
hsl(${hue}, 100%, 0%))`
|
|
5573
|
-
}, onClick: handleColorAreaClick, children: jsxRuntime.jsx("div", { className: "designbase-color-picker__color-pointer", style: {
|
|
5574
|
-
left: `${saturation}%`,
|
|
5575
|
-
top: `${100 - lightness}%`,
|
|
5576
|
-
backgroundColor: `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
|
5577
|
-
} }) }) }), jsxRuntime.jsxs("div", { className: "designbase-color-picker__controls", children: [jsxRuntime.jsx("button", { type: "button", className: "designbase-color-picker__eyedropper", onClick: handleEyedropperClick, "aria-label": "Eyedropper tool", children: jsxRuntime.jsx(icons.EyedropperIcon, { size: 16 }) }), jsxRuntime.jsx("div", { className: "designbase-color-picker__hue-slider", children: jsxRuntime.jsx("input", { type: "range", min: "0", max: "360", value: hue, onChange: handleHueChange, className: "designbase-color-picker__slider designbase-color-picker__slider--hue" }) }), showAlpha && (jsxRuntime.jsx("div", { className: "designbase-color-picker__alpha-slider", children: jsxRuntime.jsx("input", { type: "range", min: "0", max: "100", value: alpha, onChange: handleAlphaChange, className: "designbase-color-picker__slider designbase-color-picker__slider--alpha" }) }))] }), jsxRuntime.jsxs("div", { className: "designbase-color-picker__value-display", children: [showFormatSelector && (jsxRuntime.jsxs("select", { className: "designbase-color-picker__format-select", value: colorFormat, onChange: (e) => setColorFormat(e.target.value), children: [jsxRuntime.jsx("option", { value: "hex", children: "HEX" }), jsxRuntime.jsx("option", { value: "rgb", children: "RGB" }), jsxRuntime.jsx("option", { value: "rgba", children: "RGBA" }), jsxRuntime.jsx("option", { value: "hsl", children: "HSL" }), jsxRuntime.jsx("option", { value: "hsla", children: "HSLA" })] })), jsxRuntime.jsx("input", { type: "text", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, className: "designbase-color-picker__value-input", placeholder: "#000000" }), showAlpha && (jsxRuntime.jsxs("span", { className: "designbase-color-picker__alpha-percent", children: [alpha, "%"] })), showCopyButton && (jsxRuntime.jsx("button", { type: "button", className: "designbase-color-picker__copy-button", onClick: handleCopy, "aria-label": "Copy color value", children: isCopied ? jsxRuntime.jsx(icons.DoneIcon, { size: 14 }) : jsxRuntime.jsx(icons.CopyIcon, { size: 14 }) }))] })] }));
|
|
5578
|
-
return (jsxRuntime.jsxs("div", { ref: pickerRef, className: classes, children: [jsxRuntime.jsxs("div", { className: "designbase-color-picker__trigger", children: [jsxRuntime.jsx("div", { className: "designbase-color-picker__color-display", onClick: togglePicker, children: jsxRuntime.jsx("div", { className: "designbase-color-picker__color-box", style: {
|
|
5579
|
-
backgroundColor: showAlpha
|
|
5580
|
-
? `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha / 100})`
|
|
5581
|
-
: `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
|
5582
|
-
} }) }), showInput && (jsxRuntime.jsx("input", { type: "text", value: inputValue, onChange: handleInputChange, onBlur: handleInputBlur, 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) => {
|
|
5583
|
-
e.stopPropagation();
|
|
5584
|
-
handleCopy();
|
|
5585
|
-
}, 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", onClick: togglePicker, disabled: disabled, "aria-label": "Toggle color picker", children: jsxRuntime.jsx(icons.ChevronDownIcon, { size: 16 }) })] }), type === 'dropdown' && isOpen && (jsxRuntime.jsx("div", { className: "designbase-color-picker__dropdown", children: renderColorSelector() })), type === 'modal' && (jsxRuntime.jsx(Modal, { isOpen: isOpen, onClose: () => setIsOpen(false), title: "\uC0C9\uC0C1 \uC120\uD0DD", size: "s", children: renderColorSelector() }))] }));
|
|
5649
|
+
const Trigger = (jsxRuntime.jsxs("div", { className: "designbase-color-picker__trigger", onClick: () => !disabled && !readonly && handleModalOpen(), 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 }) })] }));
|
|
5650
|
+
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: [
|
|
5651
|
+
{ label: 'HEX', value: 'hex' },
|
|
5652
|
+
{ label: 'RGB', value: 'rgb' },
|
|
5653
|
+
{ label: 'RGBA', value: 'rgba' },
|
|
5654
|
+
{ label: 'HSL', value: 'hsl' },
|
|
5655
|
+
{ label: 'HSLA', value: 'hsla' },
|
|
5656
|
+
], size: "s" })), jsxRuntime.jsx(Input, { type: "text", value: colorInput, onChange: setColorInput, onKeyDown: onColorKeyDown, onBlur: onColorBlur, placeholder: "#000000", size: "s", className: "designbase-color-picker__value-input" }), showAlpha && (jsxRuntime.jsxs("div", { className: "designbase-color-picker__alpha-input-wrap", children: [jsxRuntime.jsx(Input, { type: "text", inputMode: "numeric", value: alphaInput, onChange: (value) => setAlphaInput(value.replace(/[^\d]/g, '').slice(0, 3)), onKeyDown: onAlphaInputKeyDown, onBlur: onAlphaInputBlur, placeholder: "100", size: "s", className: "designbase-color-picker__alpha-input", "aria-label": "Alpha percent" }), jsxRuntime.jsx("span", { className: "designbase-color-picker__alpha-suffix", children: "%" })] })), showCopyButton && (jsxRuntime.jsx(Button, { variant: "tertiary", size: "m", iconOnly: true, onClick: onCopy, "aria-label": "Copy color value", children: isCopied ? jsxRuntime.jsx(icons.DoneIcon, {}) : jsxRuntime.jsx(icons.CopyIcon, {}) }))] })] }));
|
|
5657
|
+
return (jsxRuntime.jsxs("div", { ref: pickerRef, className: classes, children: [Trigger, type === 'dropdown' && isOpen && (jsxRuntime.jsx("div", { className: "designbase-color-picker__dropdown", onClick: (e) => e.stopPropagation(), children: Selector })), type === 'modal' && (jsxRuntime.jsxs(Modal, { isOpen: isOpen, onClose: handleModalCancel, title: "\uC0C9\uC0C1 \uC120\uD0DD", size: "s", children: [Selector, jsxRuntime.jsx(ModalFooter, { children: jsxRuntime.jsxs("div", { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end' }, children: [jsxRuntime.jsx(Button, { variant: "secondary", size: "s", onClick: handleModalCancel, children: "\uCDE8\uC18C" }), jsxRuntime.jsx(Button, { variant: "primary", size: "s", onClick: handleModalApply, children: "\uC801\uC6A9" })] }) })] }))] }));
|
|
5586
5658
|
};
|
|
5587
5659
|
ColorPicker.displayName = 'ColorPicker';
|
|
5588
5660
|
|