@classytic/fluid 0.4.2 → 0.5.0

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 (51) hide show
  1. package/README.md +21 -1
  2. package/dist/client/calendar.d.mts +1 -2
  3. package/dist/client/calendar.mjs +4 -4
  4. package/dist/client/color-picker.d.mts +41 -25
  5. package/dist/client/color-picker.mjs +121 -73
  6. package/dist/client/core.d.mts +243 -557
  7. package/dist/client/core.mjs +351 -1462
  8. package/dist/client/error.d.mts +41 -41
  9. package/dist/client/error.mjs +35 -35
  10. package/dist/client/gallery.d.mts +33 -33
  11. package/dist/client/gallery.mjs +128 -127
  12. package/dist/client/hooks.d.mts +57 -39
  13. package/dist/client/hooks.mjs +29 -7
  14. package/dist/client/spreadsheet.d.mts +28 -28
  15. package/dist/client/spreadsheet.mjs +77 -77
  16. package/dist/client/table.d.mts +66 -33
  17. package/dist/client/table.mjs +87 -54
  18. package/dist/client/theme.mjs +1 -1
  19. package/dist/command.d.mts +6 -4
  20. package/dist/command.mjs +3 -3
  21. package/dist/compact.d.mts +97 -95
  22. package/dist/compact.mjs +336 -322
  23. package/dist/dashboard.d.mts +614 -422
  24. package/dist/dashboard.mjs +1051 -762
  25. package/dist/{dropdown-wrapper-B86u9Fri.mjs → dropdown-wrapper-B9nRDUlz.mjs} +25 -35
  26. package/dist/forms.d.mts +1037 -972
  27. package/dist/forms.mjs +2849 -2721
  28. package/dist/index.d.mts +218 -152
  29. package/dist/index.mjs +357 -264
  30. package/dist/layouts.d.mts +94 -94
  31. package/dist/layouts.mjs +115 -110
  32. package/dist/phone-input-B9_XPNvv.mjs +429 -0
  33. package/dist/phone-input-CLH_UjQZ.d.mts +31 -0
  34. package/dist/{search-context-DR7DBs7S.mjs → search-context-1g3ZmOvx.mjs} +1 -1
  35. package/dist/search.d.mts +168 -164
  36. package/dist/search.mjs +305 -301
  37. package/dist/{sheet-wrapper-C13Y-Q6w.mjs → sheet-wrapper-B2uxookb.mjs} +1 -1
  38. package/dist/timeline-Bgu1mIe9.d.mts +373 -0
  39. package/dist/timeline-HJtWf4Op.mjs +804 -0
  40. package/dist/{use-base-search-BGgWnWaF.d.mts → use-base-search-DFC4QKYU.d.mts} +1 -1
  41. package/dist/{use-media-query-BnVNIKT4.mjs → use-media-query-ChLfFChU.mjs} +6 -7
  42. package/package.json +2 -2
  43. /package/dist/{api-pagination-CJ0vR_w6.d.mts → api-pagination-C30ser2L.d.mts} +0 -0
  44. /package/dist/{filter-utils-DqMmy_v-.mjs → filter-utils-BGIvtq1R.mjs} +0 -0
  45. /package/dist/{filter-utils-IZ0GtuPo.d.mts → filter-utils-DOFTBWm1.d.mts} +0 -0
  46. /package/dist/{use-debounce-xmZucz5e.mjs → use-debounce-BNoNiEon.mjs} +0 -0
  47. /package/dist/{use-keyboard-shortcut-Bl6YM5Q7.mjs → use-keyboard-shortcut-C_Vk-36P.mjs} +0 -0
  48. /package/dist/{use-keyboard-shortcut-_mRCh3QO.d.mts → use-keyboard-shortcut-Q4CSPzSI.d.mts} +0 -0
  49. /package/dist/{use-mobile-BX3SQVo2.mjs → use-mobile-CnEmFiQx.mjs} +0 -0
  50. /package/dist/{use-scroll-detection-CsgsQYvy.mjs → use-scroll-detection-BKfqkmEC.mjs} +0 -0
  51. /package/dist/{utils-CDue7cEt.d.mts → utils-rqvYP1by.d.mts} +0 -0
package/README.md CHANGED
@@ -75,11 +75,15 @@ import { CommandSearch, useKeyboardShortcut } from "@classytic/fluid/command";
75
75
  |----------|-----------|
76
76
  | **Layout** | Section, Container |
77
77
  | **Display** | DisplayHeading, SocialIcons (Facebook, Google, TwitterX, Instagram, WhatsApp) |
78
+ | **Presentation** | CardWrapper, DataCard, LoadingCard, DetailView, DetailItem, Timeline, Pill (+Avatar, Button, Status, Indicator, Delta), InfoRow, AccordionSection, FaqAccordion |
78
79
  | **States** | EmptyState, EmptyStateNoResults, EmptyStateNoData, EmptyStateNotFound |
80
+ | **Status** | StatusBadge, DetailField, StatsGrid |
79
81
  | **Loading** | LoadingState, LoadingOverlay |
80
82
  | **Skeletons** | SkeletonTable, SkeletonList, SkeletonCard, SkeletonGrid |
81
83
  | **Utilities** | cn, buildFilterParams, buildSearchParams, getApiParams |
82
84
 
85
+ > **v0.5.0:** CardWrapper, DetailView, Timeline, Pill, InfoRow, AccordionSection and more are now server-safe — import from the root entry for PPR/donut patterns in Next.js 16.
86
+
83
87
  ### `@classytic/fluid/client/core`
84
88
 
85
89
  | Category | Components |
@@ -119,7 +123,8 @@ EventCalendar, CalendarWithDetail, CalendarDayDetail
119
123
  | **Toggle** | CheckboxInput, RadioInput, SwitchInput |
120
124
  | **Date/Time** | DateInput, DateRangeInput, DateTimeInput, DateRangeFilter |
121
125
  | **Tags** | TagInput, TagChoiceInput |
122
- | **Special** | SlugField, OTPInput, FileUploadInput, PhoneInput (in client/core) |
126
+ | **Phone** | FormPhoneInput |
127
+ | **Special** | SlugField, OTPInput, FileUploadInput |
123
128
  | **Layout** | FormSection, FormGrid, FormFieldArray, FormFieldArrayItem |
124
129
  | **Feedback** | FormErrorSummary |
125
130
 
@@ -149,6 +154,7 @@ CommandSearch (+ Input, List, Group, Item, Empty, Separator), useCommandSearch,
149
154
  | `useDebouncedCallback` | client/hooks | Debounce a function callback |
150
155
  | `useCopyToClipboard` | client/hooks | Copy text with feedback state |
151
156
  | `useBaseSearch` | client/hooks | Full search state with URL sync |
157
+ | `createSearchHook` | client/hooks | Factory to pre-configure useBaseSearch |
152
158
  | `useScrollDetection` | client/hooks | Detect scroll position/direction |
153
159
  | `useLocalStorage` | client/hooks | Typed localStorage hook |
154
160
  | `useKeyboardShortcut` | client/hooks | Register keyboard shortcuts |
@@ -180,6 +186,19 @@ import "@classytic/fluid/styles.css";
180
186
  | `@tanstack/react-table` | DataTable component |
181
187
  | `date-fns` | DateInput, DateRangeInput, EventCalendar |
182
188
 
189
+ ## Key Features (v0.5.0)
190
+
191
+ - **Server-safe by default** — Presentation components (cards, timelines, pills, detail views) work in React Server Components for PPR/donut patterns
192
+ - **React 19** — ref-as-prop (no forwardRef), modern hooks
193
+ - **shadcn/ui + Base UI** — Uses `render` prop pattern (not Radix `asChild`), `@/components/ui/*` resolved at consumer runtime
194
+ - **Form integration** — Every form component works with or without react-hook-form via conditional Controller
195
+ - **`clearable` selects** — `SelectInput` and `CompactSelect` support `clearable` prop for auto-generated clear options
196
+ - **`createSearchHook` factory** — Pre-configure search with typed filter fields and URL sync
197
+ - **5 dashboard presets** — InsetSidebar, DualSidebar, FloatingSidebar, MiniSidebar, TopbarRail
198
+ - **Composable search** — `Search.Root/Input/TypeInput/Filters/ActiveFilters/Actions`
199
+ - **757 tests** across 50 test files
200
+ - **Biome** for linting/formatting, **Knip** for dead code detection
201
+
183
202
  ## Dev
184
203
 
185
204
  ```bash
@@ -187,6 +206,7 @@ npm run build # Build with tsdown
187
206
  npm run dev # Watch mode
188
207
  npm run typecheck # Type check
189
208
  npm run clean # Remove dist
209
+ npm test # Run vitest
190
210
  ```
191
211
 
192
212
  ## License
@@ -1,11 +1,10 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
1
  import { ReactNode } from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/components/event-calendar.d.ts
5
5
  interface CalendarEvent {
6
6
  id: string;
7
7
  date: Date | string;
8
- [key: string]: any;
9
8
  }
10
9
  interface EventCalendarProps<T extends CalendarEvent> {
11
10
  /** Events to display on the calendar */
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { t as cn } from "../utils-DQ5SCVoW.mjs";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
4
  import { useCallback, useMemo, useState } from "react";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { ChevronLeft, ChevronRight } from "lucide-react";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { addMonths, eachDayOfInterval, endOfMonth, format, getDay, isSameDay, isSameMonth, startOfMonth, subMonths } from "date-fns";
@@ -131,7 +131,7 @@ function EventCalendar({ events = [], selectedDate: controlledSelectedDate, onDa
131
131
  className: cn("bg-card p-2 cursor-pointer transition-colors hover:bg-muted/50", isSelected && "ring-2 ring-primary ring-inset bg-primary/5", !isCurrentMonth && "opacity-50", isLoading && "pointer-events-none opacity-50"),
132
132
  style: { minHeight: minCellHeight },
133
133
  onClick: () => handleDateClick(day),
134
- children: renderDayContent ? renderDayContent(day, dayEvents, isSelected) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
134
+ children: renderDayContent ? renderDayContent(day, dayEvents, isSelected) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
135
135
  className: cn("flex h-7 w-7 items-center justify-center rounded-full text-sm", isToday && "bg-primary text-primary-foreground font-semibold"),
136
136
  children: format(day, "d")
137
137
  }), renderIndicators(dayEvents)] })
@@ -188,10 +188,10 @@ function CalendarWithDetail({ renderDetail, detailTitle, detailEmptyMessage, lay
188
188
  });
189
189
  return /* @__PURE__ */ jsx("div", {
190
190
  className: cn("gap-4", isHorizontal ? "flex" : "flex flex-col", isHorizontal && "items-start"),
191
- children: detailFirst ? /* @__PURE__ */ jsxs(Fragment, { children: [detailElement, /* @__PURE__ */ jsx("div", {
191
+ children: detailFirst ? /* @__PURE__ */ jsxs(Fragment$1, { children: [detailElement, /* @__PURE__ */ jsx("div", {
192
192
  className: "flex-1",
193
193
  children: calendarElement
194
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
194
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
195
195
  className: isHorizontal ? "flex-1" : "",
196
196
  children: calendarElement
197
197
  }), detailElement] })
@@ -1,8 +1,8 @@
1
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
1
  import * as react from "react";
3
2
  import { ReactNode } from "react";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
5
- //#region src/components/color-picker.d.ts
5
+ //#region src/components/color-picker/types.d.ts
6
6
  type RGBA = [number, number, number, number];
7
7
  interface ColorPickerContextValue {
8
8
  hue: number;
@@ -16,8 +16,6 @@ interface ColorPickerContextValue {
16
16
  getCurrentColor: () => RGBA;
17
17
  onChange?: (color: RGBA) => void;
18
18
  }
19
- declare function useColorPicker(): ColorPickerContextValue;
20
- declare function useColorPickerHex(): string;
21
19
  interface ColorPickerProps {
22
20
  /** Current color value (hex string, e.g. "#ff0000") */
23
21
  value?: string;
@@ -28,51 +26,69 @@ interface ColorPickerProps {
28
26
  className?: string;
29
27
  children?: ReactNode;
30
28
  }
31
- declare function ColorPicker({
32
- value,
33
- defaultValue,
34
- onChange,
35
- className,
36
- children
37
- }: ColorPickerProps): react_jsx_runtime0.JSX.Element;
38
29
  interface ColorPickerSelectionProps {
39
30
  className?: string;
40
31
  }
41
- declare const ColorPickerSelection: react.NamedExoticComponent<ColorPickerSelectionProps>;
42
32
  interface ColorPickerHueProps {
43
33
  className?: string;
44
34
  }
45
- declare function ColorPickerHue({
46
- className
47
- }: ColorPickerHueProps): react_jsx_runtime0.JSX.Element;
48
35
  interface ColorPickerAlphaProps {
49
36
  className?: string;
50
37
  }
51
- declare function ColorPickerAlpha({
52
- className
53
- }: ColorPickerAlphaProps): react_jsx_runtime0.JSX.Element;
54
38
  interface ColorPickerEyeDropperProps {
55
39
  className?: string;
56
40
  children?: ReactNode;
57
41
  }
58
- declare function ColorPickerEyeDropper({
59
- className,
60
- children
61
- }: ColorPickerEyeDropperProps): react_jsx_runtime0.JSX.Element;
62
42
  interface ColorPickerInputProps {
63
43
  className?: string;
64
44
  /** Show alpha percentage alongside hex */
65
45
  showAlpha?: boolean;
66
46
  }
47
+ interface ColorPickerPreviewProps {
48
+ className?: string;
49
+ }
50
+ //#endregion
51
+ //#region src/components/color-picker/color-picker.d.ts
52
+ declare function ColorPicker({
53
+ value,
54
+ defaultValue,
55
+ onChange,
56
+ className,
57
+ children
58
+ }: ColorPickerProps): react_jsx_runtime0.JSX.Element;
59
+ //#endregion
60
+ //#region src/components/color-picker/color-picker-alpha.d.ts
61
+ declare function ColorPickerAlpha({
62
+ className
63
+ }: ColorPickerAlphaProps): react_jsx_runtime0.JSX.Element;
64
+ //#endregion
65
+ //#region src/components/color-picker/color-picker-eye-dropper.d.ts
66
+ declare function ColorPickerEyeDropper({
67
+ className,
68
+ children
69
+ }: ColorPickerEyeDropperProps): react_jsx_runtime0.JSX.Element;
70
+ //#endregion
71
+ //#region src/components/color-picker/color-picker-hue.d.ts
72
+ declare function ColorPickerHue({
73
+ className
74
+ }: ColorPickerHueProps): react_jsx_runtime0.JSX.Element;
75
+ //#endregion
76
+ //#region src/components/color-picker/color-picker-input.d.ts
67
77
  declare function ColorPickerInput({
68
78
  className,
69
79
  showAlpha
70
80
  }: ColorPickerInputProps): react_jsx_runtime0.JSX.Element;
71
- interface ColorPickerPreviewProps {
72
- className?: string;
73
- }
81
+ //#endregion
82
+ //#region src/components/color-picker/color-picker-preview.d.ts
74
83
  declare function ColorPickerPreview({
75
84
  className
76
85
  }: ColorPickerPreviewProps): react_jsx_runtime0.JSX.Element;
77
86
  //#endregion
87
+ //#region src/components/color-picker/color-picker-selection.d.ts
88
+ declare const ColorPickerSelection: react.NamedExoticComponent<ColorPickerSelectionProps>;
89
+ //#endregion
90
+ //#region src/components/color-picker/context.d.ts
91
+ declare function useColorPicker(): ColorPickerContextValue;
92
+ declare function useColorPickerHex(): string;
93
+ //#endregion
78
94
  export { ColorPicker, ColorPickerAlpha, type ColorPickerAlphaProps, ColorPickerEyeDropper, type ColorPickerEyeDropperProps, ColorPickerHue, type ColorPickerHueProps, ColorPickerInput, type ColorPickerInputProps, ColorPickerPreview, type ColorPickerPreviewProps, type ColorPickerProps, ColorPickerSelection, type ColorPickerSelectionProps, type RGBA, useColorPicker, useColorPickerHex };
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { t as cn } from "../utils-DQ5SCVoW.mjs";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
4
  import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Pipette } from "lucide-react";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { Input } from "@/components/ui/input";
9
9
 
10
- //#region src/components/color-picker.tsx
10
+ //#region src/components/color-picker/utils.ts
11
11
  function hexToRgb(hex) {
12
12
  const h = hex.replace("#", "");
13
13
  const expanded = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
@@ -89,6 +89,9 @@ function hslToHexa(h, s, l, a) {
89
89
  function hexToHsl(hex) {
90
90
  return rgbToHsl(...hexToRgb(hex));
91
91
  }
92
+
93
+ //#endregion
94
+ //#region src/components/color-picker/context.tsx
92
95
  const ColorPickerContext = createContext(void 0);
93
96
  function useColorPicker() {
94
97
  const context = useContext(ColorPickerContext);
@@ -99,6 +102,9 @@ function useColorPickerHex() {
99
102
  const { hue, saturation, lightness } = useColorPicker();
100
103
  return hslToHex(hue, saturation, lightness);
101
104
  }
105
+
106
+ //#endregion
107
+ //#region src/components/color-picker/color-picker.tsx
102
108
  function ColorPicker({ value, defaultValue = "#000000", onChange, className, children }) {
103
109
  const [ih, is, il] = hexToHsl(value || defaultValue);
104
110
  const [hue, setHue] = useState(ih || 0);
@@ -165,73 +171,41 @@ function ColorPicker({ value, defaultValue = "#000000", onChange, className, chi
165
171
  lightness,
166
172
  alpha
167
173
  ]);
174
+ const contextValue = useMemo(() => ({
175
+ hue,
176
+ saturation,
177
+ lightness,
178
+ alpha,
179
+ setHue: setHueNotify,
180
+ setSaturation: setSaturationNotify,
181
+ setLightness: setLightnessNotify,
182
+ setAlpha: setAlphaNotify,
183
+ getCurrentColor,
184
+ onChange
185
+ }), [
186
+ hue,
187
+ saturation,
188
+ lightness,
189
+ alpha,
190
+ setHueNotify,
191
+ setSaturationNotify,
192
+ setLightnessNotify,
193
+ setAlphaNotify,
194
+ getCurrentColor,
195
+ onChange
196
+ ]);
168
197
  return /* @__PURE__ */ jsx(ColorPickerContext.Provider, {
169
- value: {
170
- hue,
171
- saturation,
172
- lightness,
173
- alpha,
174
- setHue: setHueNotify,
175
- setSaturation: setSaturationNotify,
176
- setLightness: setLightnessNotify,
177
- setAlpha: setAlphaNotify,
178
- getCurrentColor,
179
- onChange
180
- },
198
+ value: contextValue,
181
199
  children: /* @__PURE__ */ jsx("div", {
182
200
  className: cn("flex size-full flex-col gap-4", className),
183
201
  children
184
202
  })
185
203
  });
186
204
  }
187
- const ColorPickerSelection = memo(function ColorPickerSelection({ className }) {
188
- const containerRef = useRef(null);
189
- const [isDragging, setIsDragging] = useState(false);
190
- const { hue, saturation, lightness, setSaturation, setLightness } = useColorPicker();
191
- const backgroundGradient = useMemo(() => `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)), linear-gradient(90deg, rgba(255,255,255,1), rgba(255,255,255,0)), hsl(${hue}, 100%, 50%)`, [hue]);
192
- const positionX = saturation / 100;
193
- const topLightness = positionX < .01 ? 100 : 50 + 50 * (1 - positionX);
194
- const positionY = topLightness > 0 ? Math.max(0, Math.min(1, 1 - lightness / topLightness)) : 0;
195
- const updateColor = useCallback((clientX, clientY) => {
196
- if (!containerRef.current) return;
197
- const rect = containerRef.current.getBoundingClientRect();
198
- const x = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
199
- const y = Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
200
- setSaturation(x * 100);
201
- setLightness((x < .01 ? 100 : 50 + 50 * (1 - x)) * (1 - y));
202
- }, [setSaturation, setLightness]);
203
- const handlePointerDown = useCallback((e) => {
204
- e.preventDefault();
205
- setIsDragging(true);
206
- updateColor(e.clientX, e.clientY);
207
- }, [updateColor]);
208
- useEffect(() => {
209
- if (!isDragging) return;
210
- const onMove = (e) => updateColor(e.clientX, e.clientY);
211
- const onUp = () => setIsDragging(false);
212
- window.addEventListener("pointermove", onMove);
213
- window.addEventListener("pointerup", onUp);
214
- return () => {
215
- window.removeEventListener("pointermove", onMove);
216
- window.removeEventListener("pointerup", onUp);
217
- };
218
- }, [isDragging, updateColor]);
219
- return /* @__PURE__ */ jsx("div", {
220
- className: cn("relative size-full cursor-crosshair rounded", className),
221
- onPointerDown: handlePointerDown,
222
- ref: containerRef,
223
- style: { background: backgroundGradient },
224
- children: /* @__PURE__ */ jsx("div", {
225
- className: "-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute h-4 w-4 rounded-full border-2 border-white",
226
- style: {
227
- left: `${positionX * 100}%`,
228
- top: `${positionY * 100}%`,
229
- boxShadow: "0 0 0 1px rgba(0,0,0,0.5)"
230
- }
231
- })
232
- });
233
- });
234
- function ColorSlider({ value, max, onChange, trackClassName, trackStyle, children, className }) {
205
+
206
+ //#endregion
207
+ //#region src/components/color-picker/color-picker-slider.tsx
208
+ function ColorSlider({ value, max, onChange, trackClassName, trackStyle, children, className, ariaLabel }) {
235
209
  const ref = useRef(null);
236
210
  const [dragging, setDragging] = useState(false);
237
211
  const update = useCallback((clientX) => {
@@ -260,6 +234,11 @@ function ColorSlider({ value, max, onChange, trackClassName, trackStyle, childre
260
234
  ref,
261
235
  className: cn("relative flex h-4 w-full touch-none items-center", className),
262
236
  onPointerDown: handlePointerDown,
237
+ role: "slider",
238
+ "aria-label": ariaLabel,
239
+ "aria-valuemin": 0,
240
+ "aria-valuemax": max,
241
+ "aria-valuenow": value,
263
242
  children: [/* @__PURE__ */ jsx("div", {
264
243
  className: cn("relative h-3 w-full rounded-full", trackClassName),
265
244
  style: trackStyle,
@@ -270,17 +249,9 @@ function ColorSlider({ value, max, onChange, trackClassName, trackStyle, childre
270
249
  })]
271
250
  });
272
251
  }
273
- const HUE_GRADIENT = "linear-gradient(90deg, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)";
274
- function ColorPickerHue({ className }) {
275
- const { hue, setHue } = useColorPicker();
276
- return /* @__PURE__ */ jsx(ColorSlider, {
277
- value: hue,
278
- max: 360,
279
- onChange: setHue,
280
- trackStyle: { background: HUE_GRADIENT },
281
- className
282
- });
283
- }
252
+
253
+ //#endregion
254
+ //#region src/components/color-picker/color-picker-alpha.tsx
284
255
  const CHECKER_BG = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==')";
285
256
  function ColorPickerAlpha({ className }) {
286
257
  const { alpha, setAlpha } = useColorPicker();
@@ -293,9 +264,13 @@ function ColorPickerAlpha({ className }) {
293
264
  backgroundSize: "contain"
294
265
  },
295
266
  className,
267
+ ariaLabel: "Opacity",
296
268
  children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full bg-gradient-to-r from-transparent to-black/50 dark:to-white/50" })
297
269
  });
298
270
  }
271
+
272
+ //#endregion
273
+ //#region src/components/color-picker/color-picker-eye-dropper.tsx
299
274
  function ColorPickerEyeDropper({ className, children }) {
300
275
  const { setHue, setSaturation, setLightness, setAlpha } = useColorPicker();
301
276
  const handleEyeDropper = async () => {
@@ -316,6 +291,24 @@ function ColorPickerEyeDropper({ className, children }) {
316
291
  children: children || /* @__PURE__ */ jsx(Pipette, { className: "h-4 w-4" })
317
292
  });
318
293
  }
294
+
295
+ //#endregion
296
+ //#region src/components/color-picker/color-picker-hue.tsx
297
+ const HUE_GRADIENT = "linear-gradient(90deg, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)";
298
+ function ColorPickerHue({ className }) {
299
+ const { hue, setHue } = useColorPicker();
300
+ return /* @__PURE__ */ jsx(ColorSlider, {
301
+ value: hue,
302
+ max: 360,
303
+ onChange: setHue,
304
+ trackStyle: { background: HUE_GRADIENT },
305
+ className,
306
+ ariaLabel: "Hue"
307
+ });
308
+ }
309
+
310
+ //#endregion
311
+ //#region src/components/color-picker/color-picker-input.tsx
319
312
  function ColorPickerInput({ className, showAlpha = false }) {
320
313
  const { hue, saturation, lightness, alpha } = useColorPicker();
321
314
  const hex = hslToHex(hue, saturation, lightness);
@@ -331,6 +324,9 @@ function ColorPickerInput({ className, showAlpha = false }) {
331
324
  })]
332
325
  });
333
326
  }
327
+
328
+ //#endregion
329
+ //#region src/components/color-picker/color-picker-preview.tsx
334
330
  function ColorPickerPreview({ className }) {
335
331
  const { hue, saturation, lightness, alpha } = useColorPicker();
336
332
  const color = hslToHexa(hue, saturation, lightness, alpha / 100);
@@ -340,5 +336,57 @@ function ColorPickerPreview({ className }) {
340
336
  });
341
337
  }
342
338
 
339
+ //#endregion
340
+ //#region src/components/color-picker/color-picker-selection.tsx
341
+ const ColorPickerSelection = memo(function ColorPickerSelection({ className }) {
342
+ const containerRef = useRef(null);
343
+ const [isDragging, setIsDragging] = useState(false);
344
+ const { hue, saturation, lightness, setSaturation, setLightness } = useColorPicker();
345
+ const backgroundGradient = useMemo(() => `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)), linear-gradient(90deg, rgba(255,255,255,1), rgba(255,255,255,0)), hsl(${hue}, 100%, 50%)`, [hue]);
346
+ const positionX = saturation / 100;
347
+ const topLightness = positionX < .01 ? 100 : 50 + 50 * (1 - positionX);
348
+ const positionY = topLightness > 0 ? Math.max(0, Math.min(1, 1 - lightness / topLightness)) : 0;
349
+ const updateColor = useCallback((clientX, clientY) => {
350
+ if (!containerRef.current) return;
351
+ const rect = containerRef.current.getBoundingClientRect();
352
+ const x = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
353
+ const y = Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
354
+ setSaturation(x * 100);
355
+ setLightness((x < .01 ? 100 : 50 + 50 * (1 - x)) * (1 - y));
356
+ }, [setSaturation, setLightness]);
357
+ const handlePointerDown = useCallback((e) => {
358
+ e.preventDefault();
359
+ setIsDragging(true);
360
+ updateColor(e.clientX, e.clientY);
361
+ }, [updateColor]);
362
+ useEffect(() => {
363
+ if (!isDragging) return;
364
+ const onMove = (e) => updateColor(e.clientX, e.clientY);
365
+ const onUp = () => setIsDragging(false);
366
+ window.addEventListener("pointermove", onMove);
367
+ window.addEventListener("pointerup", onUp);
368
+ return () => {
369
+ window.removeEventListener("pointermove", onMove);
370
+ window.removeEventListener("pointerup", onUp);
371
+ };
372
+ }, [isDragging, updateColor]);
373
+ return /* @__PURE__ */ jsx("div", {
374
+ className: cn("relative size-full cursor-crosshair rounded", className),
375
+ onPointerDown: handlePointerDown,
376
+ ref: containerRef,
377
+ style: { background: backgroundGradient },
378
+ role: "application",
379
+ "aria-label": "Color saturation and lightness",
380
+ children: /* @__PURE__ */ jsx("div", {
381
+ className: "-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute h-4 w-4 rounded-full border-2 border-white",
382
+ style: {
383
+ left: `${positionX * 100}%`,
384
+ top: `${positionY * 100}%`,
385
+ boxShadow: "0 0 0 1px rgba(0,0,0,0.5)"
386
+ }
387
+ })
388
+ });
389
+ });
390
+
343
391
  //#endregion
344
392
  export { ColorPicker, ColorPickerAlpha, ColorPickerEyeDropper, ColorPickerHue, ColorPickerInput, ColorPickerPreview, ColorPickerSelection, useColorPicker, useColorPickerHex };