sh-ui-cli 0.52.0 → 0.52.2

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.
Files changed (89) hide show
  1. package/data/changelog/versions.json +25 -0
  2. package/data/registry/react/components/_smoke/vanilla-extract.test.ts +33 -0
  3. package/data/registry/react/components/input/styles.css.ts +6 -6
  4. package/data/registry/react/registry.json +35 -852
  5. package/package.json +2 -2
  6. package/src/api.d.ts +3 -4
  7. package/src/constants.js +9 -5
  8. package/src/create/plugins/pluginSchema.js +5 -3
  9. package/src/mcp.mjs +4 -3
  10. package/data/registry/react/components/accordion/index.vanilla-extract.tsx +0 -97
  11. package/data/registry/react/components/accordion/styles.css.ts +0 -131
  12. package/data/registry/react/components/avatar/index.vanilla-extract.tsx +0 -73
  13. package/data/registry/react/components/avatar/styles.css.ts +0 -68
  14. package/data/registry/react/components/badge/index.vanilla-extract.tsx +0 -40
  15. package/data/registry/react/components/badge/styles.css.ts +0 -71
  16. package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +0 -152
  17. package/data/registry/react/components/breadcrumb/styles.css.ts +0 -95
  18. package/data/registry/react/components/calendar/index.vanilla-extract.tsx +0 -806
  19. package/data/registry/react/components/calendar/styles.css.ts +0 -250
  20. package/data/registry/react/components/carousel/index.vanilla-extract.tsx +0 -430
  21. package/data/registry/react/components/carousel/styles.css.ts +0 -169
  22. package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +0 -96
  23. package/data/registry/react/components/checkbox/styles.css.ts +0 -74
  24. package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +0 -230
  25. package/data/registry/react/components/code-editor/styles.css.ts +0 -97
  26. package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +0 -191
  27. package/data/registry/react/components/code-panel/styles.css.ts +0 -151
  28. package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +0 -467
  29. package/data/registry/react/components/color-picker/styles.css.ts +0 -169
  30. package/data/registry/react/components/combobox/index.vanilla-extract.tsx +0 -165
  31. package/data/registry/react/components/combobox/styles.css.ts +0 -174
  32. package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +0 -251
  33. package/data/registry/react/components/context-menu/styles.css.ts +0 -167
  34. package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +0 -520
  35. package/data/registry/react/components/date-picker/styles.css.ts +0 -111
  36. package/data/registry/react/components/dialog/index.vanilla-extract.tsx +0 -95
  37. package/data/registry/react/components/dialog/styles.css.ts +0 -140
  38. package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +0 -255
  39. package/data/registry/react/components/dropdown-menu/styles.css.ts +0 -175
  40. package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +0 -487
  41. package/data/registry/react/components/file-upload/styles.css.ts +0 -193
  42. package/data/registry/react/components/form/index.vanilla-extract.tsx +0 -61
  43. package/data/registry/react/components/form/styles.css.ts +0 -56
  44. package/data/registry/react/components/header/index.vanilla-extract.tsx +0 -805
  45. package/data/registry/react/components/header/styles.css.ts +0 -413
  46. package/data/registry/react/components/label/index.vanilla-extract.tsx +0 -52
  47. package/data/registry/react/components/label/styles.css.ts +0 -141
  48. package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +0 -119
  49. package/data/registry/react/components/markdown-editor/styles.css.ts +0 -231
  50. package/data/registry/react/components/menubar/index.vanilla-extract.tsx +0 -32
  51. package/data/registry/react/components/menubar/styles.css.ts +0 -53
  52. package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +0 -148
  53. package/data/registry/react/components/numeric-input/styles.css.ts +0 -65
  54. package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +0 -174
  55. package/data/registry/react/components/page-toc/styles.css.ts +0 -97
  56. package/data/registry/react/components/pagination/index.vanilla-extract.tsx +0 -269
  57. package/data/registry/react/components/pagination/styles.css.ts +0 -113
  58. package/data/registry/react/components/popover/index.vanilla-extract.tsx +0 -113
  59. package/data/registry/react/components/popover/styles.css.ts +0 -78
  60. package/data/registry/react/components/progress/index.vanilla-extract.tsx +0 -54
  61. package/data/registry/react/components/progress/styles.css.ts +0 -53
  62. package/data/registry/react/components/radio/index.vanilla-extract.tsx +0 -65
  63. package/data/registry/react/components/radio/styles.css.ts +0 -79
  64. package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +0 -348
  65. package/data/registry/react/components/rich-text-editor/styles.css.ts +0 -243
  66. package/data/registry/react/components/select/index.vanilla-extract.tsx +0 -234
  67. package/data/registry/react/components/select/styles.css.ts +0 -225
  68. package/data/registry/react/components/separator/index.vanilla-extract.tsx +0 -46
  69. package/data/registry/react/components/separator/styles.css.ts +0 -24
  70. package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +0 -1067
  71. package/data/registry/react/components/sidebar/styles.css.ts +0 -578
  72. package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +0 -22
  73. package/data/registry/react/components/skeleton/styles.css.ts +0 -30
  74. package/data/registry/react/components/slider/index.vanilla-extract.tsx +0 -298
  75. package/data/registry/react/components/slider/styles.css.ts +0 -75
  76. package/data/registry/react/components/spinner/index.vanilla-extract.tsx +0 -38
  77. package/data/registry/react/components/spinner/styles.css.ts +0 -60
  78. package/data/registry/react/components/switch/index.vanilla-extract.tsx +0 -39
  79. package/data/registry/react/components/switch/styles.css.ts +0 -87
  80. package/data/registry/react/components/tabs/index.vanilla-extract.tsx +0 -91
  81. package/data/registry/react/components/tabs/styles.css.ts +0 -145
  82. package/data/registry/react/components/textarea/index.vanilla-extract.tsx +0 -23
  83. package/data/registry/react/components/textarea/styles.css.ts +0 -55
  84. package/data/registry/react/components/toast/index.vanilla-extract.tsx +0 -258
  85. package/data/registry/react/components/toast/styles.css.ts +0 -307
  86. package/data/registry/react/components/toggle/index.vanilla-extract.tsx +0 -131
  87. package/data/registry/react/components/toggle/styles.css.ts +0 -109
  88. package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +0 -83
  89. package/data/registry/react/components/tooltip/styles.css.ts +0 -59
@@ -1,467 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { cn } from "@SH_UI_UTILS@";
5
- import { byKey, colorPicker, colorPickerSv, colorPickerSvSaturation, colorPickerSvValue, colorPickerSvThumb, colorPickerHue, colorPickerHueThumb, colorPickerAlpha, colorPickerAlphaTrack, colorPickerRow, colorPickerSwatch, colorPickerHex, colorPickerSwatches, colorPickerSwatchBtn } from "./styles.css";
6
-
7
- /* ───────────── types ───────────── */
8
-
9
- interface HSV {
10
- h: number; // 0~360
11
- s: number; // 0~1
12
- v: number; // 0~1
13
- }
14
-
15
- interface HSVA extends HSV {
16
- a: number; // 0~1
17
- }
18
-
19
- export interface ColorPickerProps
20
- extends Omit<
21
- React.HTMLAttributes<HTMLDivElement>,
22
- "onChange" | "defaultValue" | "children"
23
- > {
24
- /** 제어 모드 색상값 (hex, 예: `"#FF8800"`). 6자리 / 3자리 / `#` 생략 모두 허용. */
25
- value?: string;
26
- /** 색상 변경 콜백. 항상 6자리 대문자 hex(`"#RRGGBB"`)로 통일되어 전달된다. */
27
- onChange?: (hex: string) => void;
28
- /**
29
- * 비제어 모드 초기값.
30
- * @default "#000000"
31
- */
32
- defaultValue?: string;
33
- /**
34
- * compound 모드. 미지정 시 기본 레이아웃(Saturation + Hue + Hex)이 자동 렌더된다.
35
- * 직접 조립하려면 `ColorPickerSaturation`/`Hue`/`Alpha`/`Hex`/`Swatches`를 자식으로 넘긴다.
36
- */
37
- children?: React.ReactNode;
38
- }
39
-
40
- /* ───────────── color math ───────────── */
41
-
42
- function clamp(n: number, min: number, max: number) {
43
- return Math.min(max, Math.max(min, n));
44
- }
45
-
46
- function hexToRgb(hex: string): [number, number, number] {
47
- const m = hex.replace("#", "");
48
- const full = m.length === 3 ? m.split("").map((c) => c + c).join("") : m;
49
- const n = parseInt(full, 16);
50
- return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
51
- }
52
-
53
- function rgbToHex(r: number, g: number, b: number): string {
54
- const toHex = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, "0");
55
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
56
- }
57
-
58
- function rgbToHsv(r: number, g: number, b: number): HSV {
59
- const rn = r / 255, gn = g / 255, bn = b / 255;
60
- const max = Math.max(rn, gn, bn);
61
- const min = Math.min(rn, gn, bn);
62
- const d = max - min;
63
- let h = 0;
64
- if (d !== 0) {
65
- if (max === rn) h = ((gn - bn) / d) % 6;
66
- else if (max === gn) h = (bn - rn) / d + 2;
67
- else h = (rn - gn) / d + 4;
68
- h *= 60;
69
- if (h < 0) h += 360;
70
- }
71
- const s = max === 0 ? 0 : d / max;
72
- const v = max;
73
- return { h, s, v };
74
- }
75
-
76
- function hsvToRgb({ h, s, v }: HSV): [number, number, number] {
77
- const c = v * s;
78
- const hh = h / 60;
79
- const x = c * (1 - Math.abs((hh % 2) - 1));
80
- let r = 0, g = 0, b = 0;
81
- if (hh >= 0 && hh < 1) [r, g, b] = [c, x, 0];
82
- else if (hh < 2) [r, g, b] = [x, c, 0];
83
- else if (hh < 3) [r, g, b] = [0, c, x];
84
- else if (hh < 4) [r, g, b] = [0, x, c];
85
- else if (hh < 5) [r, g, b] = [x, 0, c];
86
- else [r, g, b] = [c, 0, x];
87
- const m = v - c;
88
- return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
89
- }
90
-
91
- function hexToHsv(hex: string): HSV {
92
- const [r, g, b] = hexToRgb(hex);
93
- return rgbToHsv(r, g, b);
94
- }
95
-
96
- function hsvToHex(hsv: HSV): string {
97
- const [r, g, b] = hsvToRgb(hsv);
98
- return rgbToHex(r, g, b);
99
- }
100
-
101
- const HEX_RE = /^#?[0-9a-f]{6}$/i;
102
-
103
- /* ───────────── drag hook ───────────── */
104
-
105
- function useDrag(onMove: (e: PointerEvent, el: HTMLElement) => void) {
106
- const ref = React.useRef<HTMLDivElement>(null);
107
-
108
- const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
109
- const el = ref.current;
110
- if (!el) return;
111
- el.setPointerCapture(e.pointerId);
112
- onMove(e.nativeEvent, el);
113
-
114
- const onPointerMove = (ev: PointerEvent) => onMove(ev, el);
115
- const onPointerUp = (ev: PointerEvent) => {
116
- el.releasePointerCapture(ev.pointerId);
117
- el.removeEventListener("pointermove", onPointerMove);
118
- el.removeEventListener("pointerup", onPointerUp);
119
- };
120
- el.addEventListener("pointermove", onPointerMove);
121
- el.addEventListener("pointerup", onPointerUp);
122
- };
123
-
124
- return { ref, onPointerDown };
125
- }
126
-
127
- /* ───────────── context ───────────── */
128
-
129
- interface ColorPickerContextValue {
130
- hsva: HSVA;
131
- hex: string;
132
- /** 현재 hue에 해당하는 순색(pure) hex. SV 배경용. */
133
- pureHueHex: string;
134
- setHsv: (next: Partial<HSV>) => void;
135
- setAlpha: (a: number) => void;
136
- commitHex: (raw: string) => boolean;
137
- }
138
-
139
- const ColorPickerContext = React.createContext<ColorPickerContextValue | null>(null);
140
-
141
- function useColorPicker() {
142
- const ctx = React.useContext(ColorPickerContext);
143
- if (!ctx) {
144
- throw new Error(
145
- "ColorPicker 하위 컴포넌트는 <ColorPicker> 내부에서만 사용할 수 있습니다.",
146
- );
147
- }
148
- return ctx;
149
- }
150
-
151
- /* ───────────── root ───────────── */
152
-
153
- /**
154
- * HSV 모델 기반 색상 선택기. children을 생략하면 기본 레이아웃(SV + Hue + Hex)이 자동 렌더되고,
155
- * 직접 조립하려면 ColorPickerSaturation/Hue/Alpha/Hex/Swatches를 자식으로 넘긴다.
156
- * 외부 노출값은 항상 6자리 대문자 hex(`#RRGGBB`).
157
- */
158
- export function ColorPicker({
159
- value: valueProp,
160
- onChange,
161
- defaultValue = "#000000",
162
- className,
163
- children,
164
- ...rest
165
- }: ColorPickerProps) {
166
- const isControlled = valueProp !== undefined;
167
- const [internal, setInternal] = React.useState(defaultValue);
168
- const value = isControlled ? valueProp! : internal;
169
-
170
- const [hsva, setHsva] = React.useState<HSVA>(() => ({ ...hexToHsv(value), a: 1 }));
171
-
172
- /* 외부 value 변경 시 hsv 동기화 (우리가 내놓은 hex는 무시 — 무한 루프 방지) */
173
- const lastEmittedRef = React.useRef(value);
174
- React.useEffect(() => {
175
- if (value === lastEmittedRef.current) return;
176
- setHsva((prev) => ({ ...hexToHsv(value), a: prev.a }));
177
- }, [value]);
178
-
179
- const emit = React.useCallback(
180
- (next: HSVA) => {
181
- const hex = hsvToHex(next);
182
- lastEmittedRef.current = hex;
183
- setHsva(next);
184
- if (!isControlled) setInternal(hex);
185
- onChange?.(hex);
186
- },
187
- [isControlled, onChange],
188
- );
189
-
190
- const setHsv = React.useCallback(
191
- (partial: Partial<HSV>) => {
192
- const next: HSVA = { ...hsva, ...partial };
193
- const hex = hsvToHex(next);
194
- lastEmittedRef.current = hex;
195
- setHsva(next);
196
- if (!isControlled) setInternal(hex);
197
- onChange?.(hex);
198
- },
199
- [hsva, isControlled, onChange],
200
- );
201
-
202
- const setAlpha = React.useCallback((a: number) => {
203
- setHsva((prev) => ({ ...prev, a: clamp(a, 0, 1) }));
204
- }, []);
205
-
206
- const commitHex = React.useCallback(
207
- (raw: string) => {
208
- const v = raw.trim();
209
- if (!HEX_RE.test(v)) return false;
210
- const normalized = (v.startsWith("#") ? v : `#${v}`).toUpperCase();
211
- const nextHsv = hexToHsv(normalized);
212
- emit({ ...nextHsv, a: hsva.a });
213
- return true;
214
- },
215
- [emit, hsva.a],
216
- );
217
-
218
- const pureHueHex = React.useMemo(
219
- () => hsvToHex({ h: hsva.h, s: 1, v: 1 }),
220
- [hsva.h],
221
- );
222
-
223
- const ctx = React.useMemo<ColorPickerContextValue>(
224
- () => ({
225
- hsva,
226
- hex: value,
227
- pureHueHex,
228
- setHsv,
229
- setAlpha,
230
- commitHex,
231
- }),
232
- [hsva, value, pureHueHex, setHsv, setAlpha, commitHex],
233
- );
234
-
235
- return (
236
- <ColorPickerContext.Provider value={ctx}>
237
- <div
238
- className={cn(colorPicker, className)}
239
- {...rest}
240
- >
241
- {children ?? (
242
- <>
243
- <ColorPickerSaturation />
244
- <ColorPickerHue />
245
- <ColorPickerHex />
246
- </>
247
- )}
248
- </div>
249
- </ColorPickerContext.Provider>
250
- );
251
- }
252
-
253
- /* ───────────── parts ───────────── */
254
-
255
- export interface ColorPickerSaturationProps
256
- extends Omit<React.HTMLAttributes<HTMLDivElement>, "onPointerDown"> {}
257
-
258
- /** 채도(S)와 명도(V)를 동시에 조절하는 2D 박스. 포인터 드래그로 조작. */
259
- export function ColorPickerSaturation({
260
- className,
261
- style,
262
- ...rest
263
- }: ColorPickerSaturationProps) {
264
- const { hsva, hex, pureHueHex, setHsv } = useColorPicker();
265
- const drag = useDrag((e, el) => {
266
- const r = el.getBoundingClientRect();
267
- const x = clamp((e.clientX - r.left) / r.width, 0, 1);
268
- const y = clamp((e.clientY - r.top) / r.height, 0, 1);
269
- setHsv({ s: x, v: 1 - y });
270
- });
271
- return (
272
- <div
273
- ref={drag.ref}
274
- onPointerDown={drag.onPointerDown}
275
- className={cn(colorPickerSv, className)}
276
- style={{ background: pureHueHex, ...style }}
277
- role="slider"
278
- aria-label="채도/명도"
279
- aria-valuemin={0}
280
- aria-valuemax={100}
281
- aria-valuenow={Math.round(hsva.s * 100)}
282
- {...rest}
283
- >
284
- <div className={colorPickerSvSaturation} />
285
- <div className={colorPickerSvValue} />
286
- <div
287
- className={colorPickerSvThumb}
288
- style={{
289
- left: `${hsva.s * 100}%`,
290
- top: `${(1 - hsva.v) * 100}%`,
291
- background: hex,
292
- }}
293
- />
294
- </div>
295
- );
296
- }
297
-
298
- export interface ColorPickerHueProps
299
- extends Omit<React.HTMLAttributes<HTMLDivElement>, "onPointerDown"> {}
300
-
301
- /** 색상(H, 0~360°) 슬라이더. 무지개 그라데이션 위에 thumb이 위치. */
302
- export function ColorPickerHue({ className, ...rest }: ColorPickerHueProps) {
303
- const { hsva, setHsv } = useColorPicker();
304
- const drag = useDrag((e, el) => {
305
- const r = el.getBoundingClientRect();
306
- const x = clamp((e.clientX - r.left) / r.width, 0, 1);
307
- setHsv({ h: x * 360 });
308
- });
309
- return (
310
- <div
311
- ref={drag.ref}
312
- onPointerDown={drag.onPointerDown}
313
- className={cn(colorPickerHue, className)}
314
- role="slider"
315
- aria-label="색조"
316
- aria-valuemin={0}
317
- aria-valuemax={360}
318
- aria-valuenow={Math.round(hsva.h)}
319
- {...rest}
320
- >
321
- <div
322
- className={colorPickerHueThumb}
323
- style={{ left: `${(hsva.h / 360) * 100}%` }}
324
- />
325
- </div>
326
- );
327
- }
328
-
329
- export interface ColorPickerAlphaProps
330
- extends Omit<React.HTMLAttributes<HTMLDivElement>, "onPointerDown"> {}
331
-
332
- /** 투명도(A, 0~100%) 슬라이더. 외부에 알파를 노출하지 않는 hex 모드와는 시각 표시용. */
333
- export function ColorPickerAlpha({ className, style, ...rest }: ColorPickerAlphaProps) {
334
- const { hsva, hex, setAlpha } = useColorPicker();
335
- const drag = useDrag((e, el) => {
336
- const r = el.getBoundingClientRect();
337
- const x = clamp((e.clientX - r.left) / r.width, 0, 1);
338
- setAlpha(x);
339
- });
340
- const gradient = `linear-gradient(to right, rgba(0,0,0,0) 0%, ${hex} 100%)`;
341
- return (
342
- <div
343
- ref={drag.ref}
344
- onPointerDown={drag.onPointerDown}
345
- className={cn(colorPickerAlpha, className)}
346
- role="slider"
347
- aria-label="투명도"
348
- aria-valuemin={0}
349
- aria-valuemax={100}
350
- aria-valuenow={Math.round(hsva.a * 100)}
351
- style={style}
352
- {...rest}
353
- >
354
- <div
355
- className={colorPickerAlphaTrack}
356
- style={{ backgroundImage: gradient }}
357
- />
358
- <div
359
- className={colorPickerHueThumb}
360
- style={{ left: `${hsva.a * 100}%` }}
361
- />
362
- </div>
363
- );
364
- }
365
-
366
- export interface ColorPickerHexProps
367
- extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
368
- /**
369
- * input 좌측에 현재 색상 미리보기 swatch 표시 여부.
370
- * @default true
371
- */
372
- showSwatch?: boolean;
373
- }
374
-
375
- /** Hex 직접 입력 + 좌측 swatch. blur·Enter 시 검증·커밋되며 잘못된 값은 이전 값으로 되돌린다. */
376
- export function ColorPickerHex({
377
- className,
378
- showSwatch = true,
379
- ...rest
380
- }: ColorPickerHexProps) {
381
- const { hex, commitHex } = useColorPicker();
382
- const [draft, setDraft] = React.useState(hex);
383
-
384
- // 외부 hex 변경 시 draft 동기화 (단, 포커스 중이 아닐 때만)
385
- const inputRef = React.useRef<HTMLInputElement>(null);
386
- React.useEffect(() => {
387
- if (document.activeElement !== inputRef.current) setDraft(hex);
388
- }, [hex]);
389
-
390
- const onCommit = () => {
391
- if (!commitHex(draft)) setDraft(hex);
392
- };
393
-
394
- return (
395
- <div
396
- className={cn(colorPickerRow, className)}
397
- {...rest}
398
- >
399
- {showSwatch && (
400
- <div
401
- className={colorPickerSwatch}
402
- style={{ background: hex }}
403
- aria-hidden
404
- />
405
- )}
406
- <input
407
- ref={inputRef}
408
- type="text"
409
- className={colorPickerHex}
410
- value={draft}
411
- onChange={(e) => setDraft(e.target.value)}
412
- onBlur={onCommit}
413
- onKeyDown={(e) => {
414
- if (e.key === "Enter") {
415
- e.preventDefault();
416
- (e.target as HTMLInputElement).blur();
417
- }
418
- }}
419
- spellCheck={false}
420
- aria-label="Hex"
421
- />
422
- </div>
423
- );
424
- }
425
-
426
- export interface ColorPickerSwatchesProps
427
- extends React.HTMLAttributes<HTMLDivElement> {
428
- /**
429
- * 표시할 hex 색상 목록. 항목 클릭 시 해당 색상으로 즉시 commit된다.
430
- * 형식은 `"#RRGGBB"` 권장 (입력 시 대소문자는 자동 정규화).
431
- */
432
- colors: string[];
433
- }
434
-
435
- /** 미리 정의된 색상 팔레트 그리드. 각 항목 클릭 시 그 색상으로 즉시 커밋한다. */
436
- export function ColorPickerSwatches({
437
- className,
438
- colors,
439
- ...rest
440
- }: ColorPickerSwatchesProps) {
441
- const { hex, commitHex } = useColorPicker();
442
- return (
443
- <div
444
- role="group"
445
- aria-label="미리 준비된 색상"
446
- className={cn(colorPickerSwatches, className)}
447
- {...rest}
448
- >
449
- {colors.map((c) => {
450
- const normalized = c.toUpperCase();
451
- const selected = normalized === hex.toUpperCase();
452
- return (
453
- <button
454
- key={c}
455
- type="button"
456
- className={colorPickerSwatchBtn}
457
- aria-label={c}
458
- aria-pressed={selected}
459
- data-selected={selected || undefined}
460
- style={{ background: c }}
461
- onClick={() => commitHex(c)}
462
- />
463
- );
464
- })}
465
- </div>
466
- );
467
- }
@@ -1,169 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const colorPicker = style({
4
- display: "flex",
5
- flexDirection: "column",
6
- gap: "0.625rem",
7
- width: "100%",
8
- userSelect: "none",
9
- WebkitUserSelect: "none",
10
- });
11
-
12
- export const colorPickerSv = style({
13
- position: "relative",
14
- width: "100%",
15
- aspectRatio: "4 / 3",
16
- borderRadius: "var(--radius)",
17
- cursor: "crosshair",
18
- overflow: "hidden",
19
- touchAction: "none",
20
- });
21
-
22
- export const colorPickerSvSaturation = style({
23
- position: "absolute",
24
- inset: 0,
25
- background: "linear-gradient(to right, #fff, transparent)",
26
- });
27
-
28
- export const colorPickerSvValue = style({
29
- position: "absolute",
30
- inset: 0,
31
- background: "linear-gradient(to top, #000, transparent)",
32
- });
33
-
34
- export const colorPickerSvThumb = style({
35
- position: "absolute",
36
- width: "0.875rem",
37
- height: "0.875rem",
38
- marginLeft: "-0.4375rem",
39
- marginTop: "-0.4375rem",
40
- border: "2px solid #fff",
41
- borderRadius: "50%",
42
- boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.4)",
43
- pointerEvents: "none",
44
- });
45
-
46
- export const colorPickerHue = style({
47
- position: "relative",
48
- width: "100%",
49
- height: "0.875rem",
50
- borderRadius: "999px",
51
- cursor: "pointer",
52
- touchAction: "none",
53
- background: "linear-gradient(\n to right,\n #f00 0%,\n #ff0 16.66%,\n #0f0 33.33%,\n #0ff 50%,\n #00f 66.66%,\n #f0f 83.33%,\n #f00 100%\n )",
54
- });
55
-
56
- export const colorPickerHueThumb = style({
57
- position: "absolute",
58
- top: "50%",
59
- width: "0.875rem",
60
- height: "0.875rem",
61
- marginLeft: "-0.4375rem",
62
- transform: "translateY(-50%)",
63
- background: "#fff",
64
- borderRadius: "50%",
65
- boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.4)",
66
- pointerEvents: "none",
67
- });
68
-
69
- export const colorPickerAlpha = style({
70
- position: "relative",
71
- width: "100%",
72
- height: "0.875rem",
73
- borderRadius: "999px",
74
- cursor: "pointer",
75
- touchAction: "none",
76
- backgroundColor: "#fff",
77
- backgroundImage: "linear-gradient(45deg, #ccc 25%, transparent 25%),\n linear-gradient(-45deg, #ccc 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #ccc 75%),\n linear-gradient(-45deg, transparent 75%, #ccc 75%)",
78
- backgroundSize: "8px 8px",
79
- backgroundPosition: "0 0, 0 4px, 4px -4px, -4px 0",
80
- overflow: "hidden",
81
- });
82
-
83
- export const colorPickerAlphaTrack = style({
84
- position: "absolute",
85
- inset: 0,
86
- borderRadius: "inherit",
87
- pointerEvents: "none",
88
- });
89
-
90
- export const colorPickerRow = style({
91
- display: "flex",
92
- alignItems: "center",
93
- gap: "var(--space-2)",
94
- });
95
-
96
- export const colorPickerSwatch = style({
97
- width: "1.75rem",
98
- height: "1.75rem",
99
- borderRadius: "calc(var(--radius) - 2px)",
100
- border: "1px solid var(--border)",
101
- flexShrink: 0,
102
- });
103
-
104
- export const colorPickerHex = style({
105
- flex: 1,
106
- minWidth: 0,
107
- height: "1.75rem",
108
- padding: "0 var(--space-2)",
109
- border: "1px solid var(--border)",
110
- borderRadius: "calc(var(--radius) - 2px)",
111
- background: "var(--background)",
112
- color: "var(--foreground)",
113
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
114
- fontSize: "0.8125rem",
115
- textTransform: "uppercase",
116
- selectors: {
117
- "&:focus": {
118
- outline: "none",
119
- borderColor: "var(--foreground)",
120
- boxShadow: "0 0 0 1px var(--foreground)",
121
- },
122
- },
123
- });
124
-
125
- export const colorPickerSwatches = style({
126
- display: "flex",
127
- flexWrap: "wrap",
128
- gap: "0.375rem",
129
- });
130
-
131
- export const colorPickerSwatchBtn = style({
132
- width: "1.25rem",
133
- height: "1.25rem",
134
- padding: 0,
135
- border: "1px solid var(--border)",
136
- borderRadius: "calc(var(--radius) - 4px)",
137
- cursor: "pointer",
138
- transition: "transform var(--duration-fast), box-shadow var(--duration-fast)",
139
- selectors: {
140
- "&:hover": {
141
- transform: "scale(1.08)",
142
- },
143
- "&:focus-visible": {
144
- outline: "var(--border-width-strong) solid var(--foreground)",
145
- outlineOffset: "2px",
146
- },
147
- "&[data-selected]": {
148
- boxShadow: "0 0 0 2px var(--background), 0 0 0 3.5px var(--foreground)",
149
- },
150
- },
151
- });
152
-
153
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
154
- export const byKey: Record<string, string> = {
155
- "color-picker": colorPicker,
156
- "color-picker__sv": colorPickerSv,
157
- "color-picker__sv-saturation": colorPickerSvSaturation,
158
- "color-picker__sv-value": colorPickerSvValue,
159
- "color-picker__sv-thumb": colorPickerSvThumb,
160
- "color-picker__hue": colorPickerHue,
161
- "color-picker__hue-thumb": colorPickerHueThumb,
162
- "color-picker__alpha": colorPickerAlpha,
163
- "color-picker__alpha-track": colorPickerAlphaTrack,
164
- "color-picker__row": colorPickerRow,
165
- "color-picker__swatch": colorPickerSwatch,
166
- "color-picker__hex": colorPickerHex,
167
- "color-picker__swatches": colorPickerSwatches,
168
- "color-picker__swatch-btn": colorPickerSwatchBtn,
169
- };