@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.
- package/README.md +21 -1
- package/dist/client/calendar.d.mts +1 -2
- package/dist/client/calendar.mjs +4 -4
- package/dist/client/color-picker.d.mts +41 -25
- package/dist/client/color-picker.mjs +121 -73
- package/dist/client/core.d.mts +243 -557
- package/dist/client/core.mjs +351 -1462
- package/dist/client/error.d.mts +41 -41
- package/dist/client/error.mjs +35 -35
- package/dist/client/gallery.d.mts +33 -33
- package/dist/client/gallery.mjs +128 -127
- package/dist/client/hooks.d.mts +57 -39
- package/dist/client/hooks.mjs +29 -7
- package/dist/client/spreadsheet.d.mts +28 -28
- package/dist/client/spreadsheet.mjs +77 -77
- package/dist/client/table.d.mts +66 -33
- package/dist/client/table.mjs +87 -54
- package/dist/client/theme.mjs +1 -1
- package/dist/command.d.mts +6 -4
- package/dist/command.mjs +3 -3
- package/dist/compact.d.mts +97 -95
- package/dist/compact.mjs +336 -322
- package/dist/dashboard.d.mts +614 -422
- package/dist/dashboard.mjs +1051 -762
- package/dist/{dropdown-wrapper-B86u9Fri.mjs → dropdown-wrapper-B9nRDUlz.mjs} +25 -35
- package/dist/forms.d.mts +1037 -972
- package/dist/forms.mjs +2849 -2721
- package/dist/index.d.mts +218 -152
- package/dist/index.mjs +357 -264
- package/dist/layouts.d.mts +94 -94
- package/dist/layouts.mjs +115 -110
- package/dist/phone-input-B9_XPNvv.mjs +429 -0
- package/dist/phone-input-CLH_UjQZ.d.mts +31 -0
- package/dist/{search-context-DR7DBs7S.mjs → search-context-1g3ZmOvx.mjs} +1 -1
- package/dist/search.d.mts +168 -164
- package/dist/search.mjs +305 -301
- package/dist/{sheet-wrapper-C13Y-Q6w.mjs → sheet-wrapper-B2uxookb.mjs} +1 -1
- package/dist/timeline-Bgu1mIe9.d.mts +373 -0
- package/dist/timeline-HJtWf4Op.mjs +804 -0
- package/dist/{use-base-search-BGgWnWaF.d.mts → use-base-search-DFC4QKYU.d.mts} +1 -1
- package/dist/{use-media-query-BnVNIKT4.mjs → use-media-query-ChLfFChU.mjs} +6 -7
- package/package.json +2 -2
- /package/dist/{api-pagination-CJ0vR_w6.d.mts → api-pagination-C30ser2L.d.mts} +0 -0
- /package/dist/{filter-utils-DqMmy_v-.mjs → filter-utils-BGIvtq1R.mjs} +0 -0
- /package/dist/{filter-utils-IZ0GtuPo.d.mts → filter-utils-DOFTBWm1.d.mts} +0 -0
- /package/dist/{use-debounce-xmZucz5e.mjs → use-debounce-BNoNiEon.mjs} +0 -0
- /package/dist/{use-keyboard-shortcut-Bl6YM5Q7.mjs → use-keyboard-shortcut-C_Vk-36P.mjs} +0 -0
- /package/dist/{use-keyboard-shortcut-_mRCh3QO.d.mts → use-keyboard-shortcut-Q4CSPzSI.d.mts} +0 -0
- /package/dist/{use-mobile-BX3SQVo2.mjs → use-mobile-CnEmFiQx.mjs} +0 -0
- /package/dist/{use-scroll-detection-CsgsQYvy.mjs → use-scroll-detection-BKfqkmEC.mjs} +0 -0
- /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
|
-
| **
|
|
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 */
|
package/dist/client/calendar.mjs
CHANGED
|
@@ -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
|
-
|
|
72
|
-
|
|
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.
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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 };
|