@codecanon/next-presets 0.0.1

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.js ADDED
@@ -0,0 +1,936 @@
1
+ import { ThemeProvider as ThemeProvider$1, useTheme as useTheme$1 } from "next-themes";
2
+ import * as React from "react";
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, MonitorCog, Moon, Palette, Sun, XIcon } from "lucide-react";
6
+ import { clsx } from "clsx";
7
+ import { twMerge } from "tailwind-merge";
8
+ import { cva } from "class-variance-authority";
9
+ import { Dialog, ToggleGroup } from "radix-ui";
10
+ //#region src/hooks/use-local-storage.ts
11
+ function useLocalStorage(key, initialValue) {
12
+ const [storedValue, setStoredValue] = useState(() => {
13
+ try {
14
+ const item = window.localStorage.getItem(key);
15
+ return item ? JSON.parse(item) : initialValue;
16
+ } catch {
17
+ return initialValue;
18
+ }
19
+ });
20
+ const setValue = useCallback((value) => {
21
+ try {
22
+ setStoredValue((prev) => {
23
+ const next = value instanceof Function ? value(prev) : value;
24
+ window.localStorage.setItem(key, JSON.stringify(next));
25
+ return next;
26
+ });
27
+ } catch (error) {
28
+ console.error(`useLocalStorage: failed to set "${key}"`, error);
29
+ }
30
+ }, [key]);
31
+ const removeValue = useCallback(() => {
32
+ try {
33
+ window.localStorage.removeItem(key);
34
+ setStoredValue(initialValue);
35
+ } catch (error) {
36
+ console.error(`useLocalStorage: failed to remove "${key}"`, error);
37
+ }
38
+ }, [key]);
39
+ useEffect(() => {
40
+ const handler = (e) => {
41
+ if (e.key !== key) return;
42
+ try {
43
+ setStoredValue(e.newValue ? JSON.parse(e.newValue) : initialValue);
44
+ } catch {}
45
+ };
46
+ window.addEventListener("storage", handler);
47
+ return () => window.removeEventListener("storage", handler);
48
+ }, [key]);
49
+ return [
50
+ storedValue,
51
+ setValue,
52
+ removeValue
53
+ ];
54
+ }
55
+ //#endregion
56
+ //#region src/presets/index.ts
57
+ const PRESETS = [
58
+ ["nuteral", "Nuteral"],
59
+ ["nuteral-accent", "Nuteral (Accent)"],
60
+ ["new", "Anew"],
61
+ ["red", "Red"],
62
+ ["rose", "Rose"],
63
+ ["orange", "Orange"],
64
+ ["green", "Green"],
65
+ ["blue", "Blue"],
66
+ ["yellow", "Yellow"],
67
+ ["violet", "Violet"],
68
+ ["modern-minimal", "Modern Minimal"],
69
+ ["violet-bloom", "Violet Bloom"],
70
+ ["t3-chat", "T3 Chat"],
71
+ ["twitter", "Twitter"],
72
+ ["mocha-mousse", "Mocha Mousse"],
73
+ ["bubblegum", "Bubblegum"],
74
+ ["amethyst-haze", "Amethyst Haze"],
75
+ ["notebook", "Notebook"],
76
+ ["doom-64", "Doom 64"],
77
+ ["catppuccin", "Catppuccin"],
78
+ ["graphite", "Graphite"],
79
+ ["perpetuity", "Perpetuity"],
80
+ ["kodama-grove", "Kodama Grove"],
81
+ ["cosmic-night", "Cosmic Night"],
82
+ ["tangerine", "Tangerine"],
83
+ ["quantum-rose", "Quantum Rose"],
84
+ ["nature", "Nature"],
85
+ ["bold-tech", "Bold Tech"],
86
+ ["elegant-luxury", "Elegant Luxury"],
87
+ ["amber-minimal", "Amber Minimal"],
88
+ ["supabase", "Supabase"],
89
+ ["neo-brutalism", "Neo Brutalism"],
90
+ ["solar-dusk", "Solar Dusk"],
91
+ ["claymorphism", "Claymorphism"],
92
+ ["cyberpunk", "Cyberpunk"],
93
+ ["pastel-dreams", "Pastel Dreams"],
94
+ ["clean-slate", "Clean Slate"],
95
+ ["caffeine", "Caffeine"],
96
+ ["ocean-breeze", "Ocean Breeze"],
97
+ ["retro-arcade", "Retro Arcade"],
98
+ ["midnight-bloom", "Midnight Bloom"],
99
+ ["candyland", "Candyland"],
100
+ ["northern-lights", "Northern Lights"],
101
+ ["vintage-paper", "Vintage Paper"],
102
+ ["sunset-horizon", "Sunset Horizon"],
103
+ ["starry-night", "Starry Night"],
104
+ ["claude", "Claude"],
105
+ ["vercel", "Vercel"],
106
+ ["darkmatter", "Darkmatter"],
107
+ ["mono", "Mono"],
108
+ ["soft-pop", "Soft Pop"],
109
+ ["sage-garden", "Sage Garden"]
110
+ ];
111
+ const DEFAULT_PRESET = "nuteral";
112
+ //#endregion
113
+ //#region src/providers.tsx
114
+ const DEFAULT_COLOR_SCHEME = "dark";
115
+ const PresetContext = createContext({
116
+ setPreset: () => null,
117
+ resetPreset: () => null
118
+ });
119
+ function PresetProvider({ children, presetKey = "preset" }) {
120
+ const [preset, setPreset, resetPreset] = useLocalStorage(presetKey, DEFAULT_PRESET);
121
+ useEffect(() => {
122
+ const root = document.documentElement;
123
+ if (!root) return;
124
+ if (preset) root.setAttribute("data-preset", preset);
125
+ else root.removeAttribute("data-preset");
126
+ }, [preset]);
127
+ const presetContext = {
128
+ preset,
129
+ resetPreset,
130
+ setPreset
131
+ };
132
+ return /* @__PURE__ */ jsx(PresetContext.Provider, {
133
+ value: presetContext,
134
+ children
135
+ });
136
+ }
137
+ function usePreset() {
138
+ const context = useContext(PresetContext);
139
+ if (context === void 0) throw new Error("usePreset must be used within a PresetProvider");
140
+ return context;
141
+ }
142
+ function ThemeProvider({ children, defaultTheme = DEFAULT_COLOR_SCHEME, themeKey = "theme", ...nextThemeProps }) {
143
+ return /* @__PURE__ */ jsx(ThemeProvider$1, {
144
+ enableSystem: true,
145
+ enableColorScheme: true,
146
+ attribute: "class",
147
+ storageKey: themeKey,
148
+ defaultTheme,
149
+ ...nextThemeProps,
150
+ children
151
+ });
152
+ }
153
+ function useTheme() {
154
+ const themeContext = useTheme$1();
155
+ const colorScheme = themeContext.resolvedTheme || DEFAULT_COLOR_SCHEME;
156
+ const isDarkTheme = colorScheme === "dark";
157
+ return {
158
+ ...themeContext,
159
+ colorScheme,
160
+ isDarkTheme
161
+ };
162
+ }
163
+ //#endregion
164
+ //#region src/hooks/use-boolean.ts
165
+ function useBoolean(initialValue = false) {
166
+ const [value, setValue] = useState(initialValue);
167
+ const setTrue = useCallback(() => setValue(true), []);
168
+ const setFalse = useCallback(() => setValue(false), []);
169
+ return [
170
+ value,
171
+ useCallback((next) => setValue((v) => next ?? !v), []),
172
+ setTrue,
173
+ setFalse
174
+ ];
175
+ }
176
+ //#endregion
177
+ //#region src/lib/utils.ts
178
+ function cn(...inputs) {
179
+ return twMerge(clsx(inputs));
180
+ }
181
+ //#endregion
182
+ //#region src/components/ui/input.tsx
183
+ function Input({ className, type, ...props }) {
184
+ return /* @__PURE__ */ jsx("input", {
185
+ type,
186
+ "data-slot": "input",
187
+ className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
188
+ ...props
189
+ });
190
+ }
191
+ //#endregion
192
+ //#region node_modules/@radix-ui/react-compose-refs/dist/index.mjs
193
+ function setRef$1(ref, value) {
194
+ if (typeof ref === "function") return ref(value);
195
+ else if (ref !== null && ref !== void 0) ref.current = value;
196
+ }
197
+ function composeRefs$1(...refs) {
198
+ return (node) => {
199
+ let hasCleanup = false;
200
+ const cleanups = refs.map((ref) => {
201
+ const cleanup = setRef$1(ref, node);
202
+ if (!hasCleanup && typeof cleanup == "function") hasCleanup = true;
203
+ return cleanup;
204
+ });
205
+ if (hasCleanup) return () => {
206
+ for (let i = 0; i < cleanups.length; i++) {
207
+ const cleanup = cleanups[i];
208
+ if (typeof cleanup == "function") cleanup();
209
+ else setRef$1(refs[i], null);
210
+ }
211
+ };
212
+ };
213
+ }
214
+ //#endregion
215
+ //#region node_modules/@radix-ui/react-slot/dist/index.mjs
216
+ /* @__NO_SIDE_EFFECTS__ */
217
+ function createSlot(ownerName) {
218
+ const SlotClone = /* @__PURE__ */ createSlotClone(ownerName);
219
+ const Slot2 = React.forwardRef((props, forwardedRef) => {
220
+ const { children, ...slotProps } = props;
221
+ const childrenArray = React.Children.toArray(children);
222
+ const slottable = childrenArray.find(isSlottable);
223
+ if (slottable) {
224
+ const newElement = slottable.props.children;
225
+ const newChildren = childrenArray.map((child) => {
226
+ if (child === slottable) {
227
+ if (React.Children.count(newElement) > 1) return React.Children.only(null);
228
+ return React.isValidElement(newElement) ? newElement.props.children : null;
229
+ } else return child;
230
+ });
231
+ return /* @__PURE__ */ jsx(SlotClone, {
232
+ ...slotProps,
233
+ ref: forwardedRef,
234
+ children: React.isValidElement(newElement) ? React.cloneElement(newElement, void 0, newChildren) : null
235
+ });
236
+ }
237
+ return /* @__PURE__ */ jsx(SlotClone, {
238
+ ...slotProps,
239
+ ref: forwardedRef,
240
+ children
241
+ });
242
+ });
243
+ Slot2.displayName = `${ownerName}.Slot`;
244
+ return Slot2;
245
+ }
246
+ var Slot = /* @__PURE__ */ createSlot("Slot");
247
+ /* @__NO_SIDE_EFFECTS__ */
248
+ function createSlotClone(ownerName) {
249
+ const SlotClone = React.forwardRef((props, forwardedRef) => {
250
+ const { children, ...slotProps } = props;
251
+ if (React.isValidElement(children)) {
252
+ const childrenRef = getElementRef(children);
253
+ const props2 = mergeProps(slotProps, children.props);
254
+ if (children.type !== React.Fragment) props2.ref = forwardedRef ? composeRefs$1(forwardedRef, childrenRef) : childrenRef;
255
+ return React.cloneElement(children, props2);
256
+ }
257
+ return React.Children.count(children) > 1 ? React.Children.only(null) : null;
258
+ });
259
+ SlotClone.displayName = `${ownerName}.SlotClone`;
260
+ return SlotClone;
261
+ }
262
+ var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
263
+ function isSlottable(child) {
264
+ return React.isValidElement(child) && typeof child.type === "function" && "__radixId" in child.type && child.type.__radixId === SLOTTABLE_IDENTIFIER;
265
+ }
266
+ function mergeProps(slotProps, childProps) {
267
+ const overrideProps = { ...childProps };
268
+ for (const propName in childProps) {
269
+ const slotPropValue = slotProps[propName];
270
+ const childPropValue = childProps[propName];
271
+ if (/^on[A-Z]/.test(propName)) {
272
+ if (slotPropValue && childPropValue) overrideProps[propName] = (...args) => {
273
+ const result = childPropValue(...args);
274
+ slotPropValue(...args);
275
+ return result;
276
+ };
277
+ else if (slotPropValue) overrideProps[propName] = slotPropValue;
278
+ } else if (propName === "style") overrideProps[propName] = {
279
+ ...slotPropValue,
280
+ ...childPropValue
281
+ };
282
+ else if (propName === "className") overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
283
+ }
284
+ return {
285
+ ...slotProps,
286
+ ...overrideProps
287
+ };
288
+ }
289
+ function getElementRef(element) {
290
+ let getter = Object.getOwnPropertyDescriptor(element.props, "ref")?.get;
291
+ let mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
292
+ if (mayWarn) return element.ref;
293
+ getter = Object.getOwnPropertyDescriptor(element, "ref")?.get;
294
+ mayWarn = getter && "isReactWarning" in getter && getter.isReactWarning;
295
+ if (mayWarn) return element.props.ref;
296
+ return element.props.ref || element.ref;
297
+ }
298
+ //#endregion
299
+ //#region src/lib/compose-refs.ts
300
+ /**
301
+ * Set a given ref to a given value
302
+ * This utility takes care of different types of refs: callback refs and RefObject(s)
303
+ */
304
+ function setRef(ref, value) {
305
+ if (typeof ref === "function") return ref(value);
306
+ if (ref !== null && ref !== void 0) ref.current = value;
307
+ }
308
+ /**
309
+ * A utility to compose multiple refs together
310
+ * Accepts callback refs and RefObject(s)
311
+ */
312
+ function composeRefs(...refs) {
313
+ return (node) => {
314
+ let hasCleanup = false;
315
+ const cleanups = refs.map((ref) => {
316
+ const cleanup = setRef(ref, node);
317
+ if (!hasCleanup && typeof cleanup === "function") hasCleanup = true;
318
+ return cleanup;
319
+ });
320
+ if (hasCleanup) return () => {
321
+ for (let i = 0; i < cleanups.length; i++) {
322
+ const cleanup = cleanups[i];
323
+ if (typeof cleanup === "function") cleanup();
324
+ else setRef(refs[i], null);
325
+ }
326
+ };
327
+ };
328
+ }
329
+ /**
330
+ * A custom hook that composes multiple refs
331
+ * Accepts callback refs and RefObject(s)
332
+ */
333
+ function useComposedRefs(...refs) {
334
+ return React.useCallback(composeRefs(...refs), refs);
335
+ }
336
+ //#endregion
337
+ //#region src/components/ui/scroller.tsx
338
+ const DATA_TOP_SCROLL = "data-top-scroll";
339
+ const DATA_BOTTOM_SCROLL = "data-bottom-scroll";
340
+ const DATA_LEFT_SCROLL = "data-left-scroll";
341
+ const DATA_RIGHT_SCROLL = "data-right-scroll";
342
+ const DATA_TOP_BOTTOM_SCROLL = "data-top-bottom-scroll";
343
+ const DATA_LEFT_RIGHT_SCROLL = "data-left-right-scroll";
344
+ const scrollerVariants = cva("", {
345
+ variants: {
346
+ orientation: {
347
+ vertical: [
348
+ "overflow-y-auto",
349
+ "data-[top-scroll=true]:[mask-image:linear-gradient(0deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]",
350
+ "data-[bottom-scroll=true]:[mask-image:linear-gradient(180deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]",
351
+ "data-[top-bottom-scroll=true]:[mask-image:linear-gradient(#000,#000,transparent_0,#000_var(--scroll-shadow-size),#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]"
352
+ ],
353
+ horizontal: [
354
+ "overflow-x-auto",
355
+ "data-[left-scroll=true]:[mask-image:linear-gradient(270deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]",
356
+ "data-[right-scroll=true]:[mask-image:linear-gradient(90deg,#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]",
357
+ "data-[left-right-scroll=true]:[mask-image:linear-gradient(to_right,#000,#000,transparent_0,#000_var(--scroll-shadow-size),#000_calc(100%_-_var(--scroll-shadow-size)),transparent)]"
358
+ ]
359
+ },
360
+ hideScrollbar: {
361
+ true: "[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
362
+ false: ""
363
+ }
364
+ },
365
+ defaultVariants: {
366
+ orientation: "vertical",
367
+ hideScrollbar: false
368
+ }
369
+ });
370
+ function Scroller(props) {
371
+ const { orientation = "vertical", hideScrollbar, className, size = 40, offset = 0, scrollStep = 40, style, asChild, withNavigation = false, scrollTriggerMode = "press", ref, ...scrollerProps } = props;
372
+ const containerRef = React.useRef(null);
373
+ const composedRef = useComposedRefs(ref, containerRef);
374
+ const [scrollVisibility, setScrollVisibility] = React.useState({
375
+ up: false,
376
+ down: false,
377
+ left: false,
378
+ right: false
379
+ });
380
+ const onScrollBy = React.useCallback((direction) => {
381
+ const container = containerRef.current;
382
+ if (!container) return;
383
+ ({
384
+ up: () => container.scrollTop -= scrollStep,
385
+ down: () => container.scrollTop += scrollStep,
386
+ left: () => container.scrollLeft -= scrollStep,
387
+ right: () => container.scrollLeft += scrollStep
388
+ })[direction]();
389
+ }, [scrollStep]);
390
+ const scrollHandlers = React.useMemo(() => ({
391
+ up: () => onScrollBy("up"),
392
+ down: () => onScrollBy("down"),
393
+ left: () => onScrollBy("left"),
394
+ right: () => onScrollBy("right")
395
+ }), [onScrollBy]);
396
+ React.useLayoutEffect(() => {
397
+ const container = containerRef.current;
398
+ if (!container) return;
399
+ function onScroll() {
400
+ if (!container) return;
401
+ if (orientation === "vertical") {
402
+ const scrollTop = container.scrollTop;
403
+ const clientHeight = container.clientHeight;
404
+ const scrollHeight = container.scrollHeight;
405
+ if (withNavigation) setScrollVisibility((prev) => {
406
+ const newUp = scrollTop > offset;
407
+ const newDown = scrollTop + clientHeight < scrollHeight;
408
+ if (prev.up !== newUp || prev.down !== newDown) return {
409
+ ...prev,
410
+ up: newUp,
411
+ down: newDown
412
+ };
413
+ return prev;
414
+ });
415
+ const hasTopScroll = scrollTop > offset;
416
+ const hasBottomScroll = scrollTop + clientHeight + offset < scrollHeight;
417
+ const isVerticallyScrollable = scrollHeight > clientHeight;
418
+ if (hasTopScroll && hasBottomScroll && isVerticallyScrollable) {
419
+ container.setAttribute(DATA_TOP_BOTTOM_SCROLL, "true");
420
+ container.removeAttribute(DATA_TOP_SCROLL);
421
+ container.removeAttribute(DATA_BOTTOM_SCROLL);
422
+ } else {
423
+ container.removeAttribute(DATA_TOP_BOTTOM_SCROLL);
424
+ if (hasTopScroll) container.setAttribute(DATA_TOP_SCROLL, "true");
425
+ else container.removeAttribute(DATA_TOP_SCROLL);
426
+ if (hasBottomScroll && isVerticallyScrollable) container.setAttribute(DATA_BOTTOM_SCROLL, "true");
427
+ else container.removeAttribute(DATA_BOTTOM_SCROLL);
428
+ }
429
+ }
430
+ const scrollLeft = container.scrollLeft;
431
+ const clientWidth = container.clientWidth;
432
+ const scrollWidth = container.scrollWidth;
433
+ if (withNavigation) setScrollVisibility((prev) => {
434
+ const newLeft = scrollLeft > offset;
435
+ const newRight = scrollLeft + clientWidth < scrollWidth;
436
+ if (prev.left !== newLeft || prev.right !== newRight) return {
437
+ ...prev,
438
+ left: newLeft,
439
+ right: newRight
440
+ };
441
+ return prev;
442
+ });
443
+ const hasLeftScroll = scrollLeft > offset;
444
+ const hasRightScroll = scrollLeft + clientWidth + offset < scrollWidth;
445
+ const isHorizontallyScrollable = scrollWidth > clientWidth;
446
+ if (hasLeftScroll && hasRightScroll && isHorizontallyScrollable) {
447
+ container.setAttribute(DATA_LEFT_RIGHT_SCROLL, "true");
448
+ container.removeAttribute(DATA_LEFT_SCROLL);
449
+ container.removeAttribute(DATA_RIGHT_SCROLL);
450
+ } else {
451
+ container.removeAttribute(DATA_LEFT_RIGHT_SCROLL);
452
+ if (hasLeftScroll) container.setAttribute(DATA_LEFT_SCROLL, "true");
453
+ else container.removeAttribute(DATA_LEFT_SCROLL);
454
+ if (hasRightScroll && isHorizontallyScrollable) container.setAttribute(DATA_RIGHT_SCROLL, "true");
455
+ else container.removeAttribute(DATA_RIGHT_SCROLL);
456
+ }
457
+ }
458
+ onScroll();
459
+ container.addEventListener("scroll", onScroll);
460
+ window.addEventListener("resize", onScroll);
461
+ return () => {
462
+ container.removeEventListener("scroll", onScroll);
463
+ window.removeEventListener("resize", onScroll);
464
+ };
465
+ }, [
466
+ orientation,
467
+ offset,
468
+ withNavigation
469
+ ]);
470
+ const composedStyle = React.useMemo(() => ({
471
+ "--scroll-shadow-size": `${size}px`,
472
+ ...style
473
+ }), [size, style]);
474
+ const activeDirections = React.useMemo(() => {
475
+ if (!withNavigation) return [];
476
+ return orientation === "vertical" ? ["up", "down"] : ["left", "right"];
477
+ }, [orientation, withNavigation]);
478
+ const ScrollerImpl = /* @__PURE__ */ jsx(asChild ? Slot : "div", {
479
+ "data-slot": "scroller",
480
+ ...scrollerProps,
481
+ ref: composedRef,
482
+ style: composedStyle,
483
+ className: cn(scrollerVariants({
484
+ orientation,
485
+ hideScrollbar,
486
+ className
487
+ }))
488
+ });
489
+ const navigationButtons = React.useMemo(() => {
490
+ if (!withNavigation) return null;
491
+ return activeDirections.filter((direction) => scrollVisibility[direction]).map((direction) => /* @__PURE__ */ jsx(ScrollButton, {
492
+ "data-slot": "scroll-button",
493
+ direction,
494
+ onClick: scrollHandlers[direction],
495
+ triggerMode: scrollTriggerMode
496
+ }, direction));
497
+ }, [
498
+ activeDirections,
499
+ scrollVisibility,
500
+ scrollHandlers,
501
+ scrollTriggerMode,
502
+ withNavigation
503
+ ]);
504
+ if (withNavigation) return /* @__PURE__ */ jsxs("div", {
505
+ className: "relative w-full",
506
+ children: [navigationButtons, ScrollerImpl]
507
+ });
508
+ return ScrollerImpl;
509
+ }
510
+ const scrollButtonVariants = cva("absolute z-10 transition-opacity [&>svg]:size-4 [&>svg]:opacity-80 hover:[&>svg]:opacity-100", {
511
+ variants: { direction: {
512
+ up: "-translate-x-1/2 top-2 left-1/2",
513
+ down: "-translate-x-1/2 bottom-2 left-1/2",
514
+ left: "-translate-y-1/2 top-1/2 left-2",
515
+ right: "-translate-y-1/2 top-1/2 right-2"
516
+ } },
517
+ defaultVariants: { direction: "up" }
518
+ });
519
+ const directionToIcon = {
520
+ up: ChevronUp,
521
+ down: ChevronDown,
522
+ left: ChevronLeft,
523
+ right: ChevronRight
524
+ };
525
+ function ScrollButton(props) {
526
+ const { direction, className, triggerMode = "press", onClick, ref, ...buttonProps } = props;
527
+ const [autoScrollTimer, setAutoScrollTimer] = React.useState(null);
528
+ const onAutoScrollStart = React.useCallback((event) => {
529
+ if (autoScrollTimer !== null) return;
530
+ if (triggerMode === "press") setAutoScrollTimer(window.setInterval(onClick ?? (() => {}), 50));
531
+ else if (triggerMode === "hover") setAutoScrollTimer(window.setInterval(() => {
532
+ if (event) onClick?.(event);
533
+ }, 50));
534
+ }, [
535
+ autoScrollTimer,
536
+ onClick,
537
+ triggerMode
538
+ ]);
539
+ const onAutoScrollStop = React.useCallback(() => {
540
+ if (autoScrollTimer === null) return;
541
+ window.clearInterval(autoScrollTimer);
542
+ setAutoScrollTimer(null);
543
+ }, [autoScrollTimer]);
544
+ const eventHandlers = React.useMemo(() => {
545
+ return {
546
+ press: {
547
+ onPointerDown: onAutoScrollStart,
548
+ onPointerUp: onAutoScrollStop,
549
+ onPointerLeave: onAutoScrollStop,
550
+ onClick: () => {}
551
+ },
552
+ hover: {
553
+ onPointerEnter: onAutoScrollStart,
554
+ onPointerLeave: onAutoScrollStop,
555
+ onClick: () => {}
556
+ },
557
+ click: { onClick }
558
+ }[triggerMode] ?? {};
559
+ }, [
560
+ triggerMode,
561
+ onAutoScrollStart,
562
+ onAutoScrollStop,
563
+ onClick
564
+ ]);
565
+ React.useEffect(() => {
566
+ return () => onAutoScrollStop();
567
+ }, [onAutoScrollStop]);
568
+ const Icon = directionToIcon[direction];
569
+ return /* @__PURE__ */ jsx("button", {
570
+ type: "button",
571
+ ...buttonProps,
572
+ ...eventHandlers,
573
+ ref,
574
+ className: cn(scrollButtonVariants({
575
+ direction,
576
+ className
577
+ })),
578
+ children: /* @__PURE__ */ jsx(Icon, {})
579
+ });
580
+ }
581
+ //#endregion
582
+ //#region src/components/ui/sheet.tsx
583
+ function Sheet({ ...props }) {
584
+ return /* @__PURE__ */ jsx(Dialog.Root, {
585
+ "data-slot": "sheet",
586
+ ...props
587
+ });
588
+ }
589
+ function SheetPortal({ ...props }) {
590
+ return /* @__PURE__ */ jsx(Dialog.Portal, {
591
+ "data-slot": "sheet-portal",
592
+ ...props
593
+ });
594
+ }
595
+ function SheetOverlay({ className, ...props }) {
596
+ return /* @__PURE__ */ jsx(Dialog.Overlay, {
597
+ "data-slot": "sheet-overlay",
598
+ className: cn("data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs", className),
599
+ ...props
600
+ });
601
+ }
602
+ function SheetContent({ className, children, side = "right", ...props }) {
603
+ return /* @__PURE__ */ jsxs(SheetPortal, { children: [/* @__PURE__ */ jsx(SheetOverlay, {}), /* @__PURE__ */ jsx(Dialog.Content, {
604
+ "data-slot": "sheet-content",
605
+ "data-side": side,
606
+ className: cn("bg-popover text-popover-foreground data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10 fixed z-50 flex flex-col gap-4 bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm", className),
607
+ ...props,
608
+ children
609
+ })] });
610
+ }
611
+ function SheetContentClose() {
612
+ return /* @__PURE__ */ jsxs(Dialog.Close, {
613
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none",
614
+ children: [/* @__PURE__ */ jsx(XIcon, { className: "size-4" }), /* @__PURE__ */ jsx("span", {
615
+ className: "sr-only",
616
+ children: "Close"
617
+ })]
618
+ });
619
+ }
620
+ function SheetHeader({ className, ...props }) {
621
+ return /* @__PURE__ */ jsx("div", {
622
+ "data-slot": "sheet-header",
623
+ className: cn("flex flex-col gap-0.5 p-4", className),
624
+ ...props
625
+ });
626
+ }
627
+ function SheetTitle({ className, ...props }) {
628
+ return /* @__PURE__ */ jsx(Dialog.Title, {
629
+ "data-slot": "sheet-title",
630
+ className: cn("text-foreground text-base font-medium", className),
631
+ ...props
632
+ });
633
+ }
634
+ //#endregion
635
+ //#region src/components/ui/toggle.tsx
636
+ const toggleVariants = cva("group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
637
+ variants: {
638
+ variant: {
639
+ default: "bg-transparent",
640
+ outline: "border border-input bg-transparent hover:bg-muted"
641
+ },
642
+ size: {
643
+ default: "h-8 min-w-8 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
644
+ sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
645
+ lg: "h-9 min-w-9 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2"
646
+ }
647
+ },
648
+ defaultVariants: {
649
+ variant: "default",
650
+ size: "default"
651
+ }
652
+ });
653
+ //#endregion
654
+ //#region src/components/ui/toggle-group.tsx
655
+ const ToggleGroupContext = React.createContext({
656
+ size: "default",
657
+ variant: "default",
658
+ spacing: 0,
659
+ orientation: "horizontal"
660
+ });
661
+ function ToggleGroup$1({ className, variant, size, spacing = 0, orientation = "horizontal", children, ...props }) {
662
+ return /* @__PURE__ */ jsx(ToggleGroup.Root, {
663
+ "data-slot": "toggle-group",
664
+ "data-variant": variant,
665
+ "data-size": size,
666
+ "data-spacing": spacing,
667
+ "data-orientation": orientation,
668
+ style: { "--gap": spacing },
669
+ className: cn("group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-lg data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-vertical:flex-col data-vertical:items-stretch", className),
670
+ ...props,
671
+ children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, {
672
+ value: {
673
+ variant,
674
+ size,
675
+ spacing,
676
+ orientation
677
+ },
678
+ children
679
+ })
680
+ });
681
+ }
682
+ function ToggleGroupItem({ className, children, variant = "default", size = "default", ...props }) {
683
+ const context = React.useContext(ToggleGroupContext);
684
+ return /* @__PURE__ */ jsx(ToggleGroup.Item, {
685
+ "data-slot": "toggle-group-item",
686
+ "data-variant": context.variant || variant,
687
+ "data-size": context.size || size,
688
+ "data-spacing": context.spacing,
689
+ className: cn("shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-lg group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-lg group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t", toggleVariants({
690
+ variant: context.variant || variant,
691
+ size: context.size || size
692
+ }), className),
693
+ ...props,
694
+ children
695
+ });
696
+ }
697
+ //#endregion
698
+ //#region src/lib/format.ts
699
+ function titleCase(str) {
700
+ if (!str) return "";
701
+ let s = str.replace(/[_-]/g, " ");
702
+ s = s.replace(/([a-z])([A-Z])/g, "$1 $2");
703
+ s = s.replace(/\s+/g, " ").trim();
704
+ return s.split(" ").map((word) => word.length > 0 ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() : "").join(" ");
705
+ }
706
+ typeof navigator !== "undefined" && /Mac/i.test(navigator.platform);
707
+ //#endregion
708
+ //#region src/components/default-app-preview-card.tsx
709
+ function DefaultAppPreviewCard({ presetKey, label = titleCase(presetKey || ""), active, showDock, highlighted, className, ref, ...props }) {
710
+ const { preset: activePresetKey } = usePreset();
711
+ const { colorScheme = "light" } = useTheme();
712
+ const themeKey = presetKey || activePresetKey;
713
+ return /* @__PURE__ */ jsxs("div", {
714
+ ref,
715
+ ...props,
716
+ className: cn("hover:bg-accent hover:text-accent-foreground group/theme-preset-card m-2 cursor-pointer overflow-hidden rounded-md border transition-all hover:shadow-xl", highlighted && "bg-accent text-accent-foreground shadow-xl", active && "ring-primary ring-3", highlighted && !active && "ring-accent-foreground ring-.5 ring-offset-1", "h-49 w-67 max-w-11/12", "flex flex-col", className),
717
+ children: [/* @__PURE__ */ jsxs("div", {
718
+ className: "flex justify-center gap-2 border-b p-3 text-center",
719
+ children: [/* @__PURE__ */ jsx("span", {
720
+ className: "text-sm font-medium",
721
+ children: label
722
+ }), active && /* @__PURE__ */ jsxs(Fragment, { children: [
723
+ /* @__PURE__ */ jsx("span", {
724
+ className: "text-sm font-bold",
725
+ children: "—"
726
+ }),
727
+ " ",
728
+ /* @__PURE__ */ jsx("span", {
729
+ className: "text-sm font-bold",
730
+ children: "active"
731
+ })
732
+ ] })]
733
+ }), /* @__PURE__ */ jsx("div", {
734
+ "data-preset": themeKey,
735
+ className: cn("bg-background text-foreground flex-1 p-3", colorScheme === "dark" && "dark"),
736
+ children: /* @__PURE__ */ jsxs("div", {
737
+ className: cn("flex h-full gap-2", showDock && "flex-col-reverse"),
738
+ children: [/* @__PURE__ */ jsx("div", {
739
+ className: cn("bg-sidebar text-sidebar-foreground border-sidebar-border flex w-16 flex-col gap-1 rounded p-2", showDock && "mx-auto flex-row"),
740
+ children: [
741
+ 1,
742
+ 2,
743
+ 3,
744
+ 4
745
+ ].map((i) => /* @__PURE__ */ jsx("div", { className: cn("h-2 rounded", showDock ? "w-2" : "h-2", i === 1 ? "bg-primary" : "bg-sidebar-accent opacity-50") }, i))
746
+ }), /* @__PURE__ */ jsxs("div", {
747
+ className: "flex flex-1 flex-col gap-2",
748
+ children: [
749
+ /* @__PURE__ */ jsxs("div", {
750
+ className: "flex items-center justify-between gap-2",
751
+ children: [/* @__PURE__ */ jsx("div", { className: "bg-muted h-2 flex-1 rounded" }), /* @__PURE__ */ jsx("div", { className: "bg-primary h-2 w-8 rounded" })]
752
+ }),
753
+ /* @__PURE__ */ jsx("div", {
754
+ className: "grid flex-1 grid-cols-1 gap-2 md:grid-cols-2",
755
+ children: [
756
+ 1,
757
+ 2,
758
+ 3,
759
+ 4
760
+ ].map((i) => /* @__PURE__ */ jsxs("div", {
761
+ className: cn("bg-card border-border rounded border p-2", i > 2 && "hidden md:block"),
762
+ children: [/* @__PURE__ */ jsx("div", { className: "bg-card-foreground mb-1 h-1.5 w-3/4 rounded opacity-70" }), /* @__PURE__ */ jsx("div", { className: "bg-muted h-1 w-1/2 rounded" })]
763
+ }, i))
764
+ }),
765
+ /* @__PURE__ */ jsxs("div", {
766
+ className: "flex gap-2",
767
+ children: [/* @__PURE__ */ jsx("div", { className: "bg-secondary h-1.5 flex-1 rounded" }), /* @__PURE__ */ jsx("div", { className: "bg-accent h-1.5 w-12 rounded" })]
768
+ })
769
+ ]
770
+ })]
771
+ })
772
+ })]
773
+ });
774
+ }
775
+ //#endregion
776
+ //#region src/components/preset-picker.tsx
777
+ const PresetsPickerContext = createContext({
778
+ open: false,
779
+ setOpen: () => {},
780
+ toggleOpen: () => {}
781
+ });
782
+ function PresetPicker({ children }) {
783
+ const [open, setOpen] = useState(false);
784
+ const toggleOpen = () => setOpen((open) => !open);
785
+ const context = useMemo(() => ({
786
+ open,
787
+ setOpen,
788
+ toggleOpen
789
+ }), [open]);
790
+ return /* @__PURE__ */ jsx(PresetsPickerContext.Provider, {
791
+ value: context,
792
+ children
793
+ });
794
+ }
795
+ function usePresetPicker(caller = "usePresetPicker") {
796
+ const context = useContext(PresetsPickerContext);
797
+ if (!context) throw new Error(`${caller} must be used within a PresetPicker`);
798
+ return context;
799
+ }
800
+ function PresetPickerThemeToggleGroup({ className, ...props }) {
801
+ const { theme, setTheme } = useTheme();
802
+ return /* @__PURE__ */ jsxs(ToggleGroup$1, {
803
+ ...props,
804
+ className: cn("w-full border", className),
805
+ type: "single",
806
+ value: theme ?? "",
807
+ onValueChange: (value) => value && setTheme(value),
808
+ children: [
809
+ /* @__PURE__ */ jsxs(ToggleGroupItem, {
810
+ className: "flex-1",
811
+ value: "light",
812
+ children: [/* @__PURE__ */ jsx(Sun, {}), "Light"]
813
+ }),
814
+ /* @__PURE__ */ jsxs(ToggleGroupItem, {
815
+ className: "flex-1",
816
+ value: "dark",
817
+ children: [/* @__PURE__ */ jsx(Moon, {}), "Dark"]
818
+ }),
819
+ /* @__PURE__ */ jsxs(ToggleGroupItem, {
820
+ className: "flex-1",
821
+ value: "system",
822
+ children: [/* @__PURE__ */ jsx(MonitorCog, {}), "System"]
823
+ })
824
+ ]
825
+ });
826
+ }
827
+ function PresetPickerContent({ showDock, previewCard: AppPreviewCard = DefaultAppPreviewCard, className, ...props }) {
828
+ const { open } = usePresetPicker("PresetPickerContent");
829
+ const { preset, setPreset } = usePreset();
830
+ const [mounted, setMounted] = useBoolean(false);
831
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
832
+ const [query, setQuery] = useState("");
833
+ const queryLower = query.trim().toLowerCase();
834
+ const scrollerRef = useRef(null);
835
+ const itemRefs = useRef(/* @__PURE__ */ new Map());
836
+ const filteredPresets = useMemo(() => PRESETS.filter(([, t]) => t?.toLowerCase().includes(queryLower)), [queryLower]);
837
+ useEffect(() => {
838
+ if (queryLower.trim()) setHighlightedIndex(-1);
839
+ else setHighlightedIndex(filteredPresets.findIndex(([id]) => id === preset));
840
+ }, [queryLower]);
841
+ useEffect(() => {
842
+ if (!open) setQuery("");
843
+ }, [open]);
844
+ useEffect(() => {
845
+ const highlightedElement = itemRefs.current.get(highlightedIndex);
846
+ if (highlightedElement && scrollerRef.current) {
847
+ const parent = scrollerRef.current;
848
+ const parentRect = parent.getBoundingClientRect();
849
+ const elementRect = highlightedElement.getBoundingClientRect();
850
+ if (elementRect.top < parentRect.top + 20) parent.scrollTop -= parentRect.top + 20 - elementRect.top;
851
+ else if (elementRect.bottom > parentRect.bottom - 20) parent.scrollTop += elementRect.bottom - (parentRect.bottom - 20);
852
+ }
853
+ }, [highlightedIndex]);
854
+ const handleKeyDown = (e) => {
855
+ if (filteredPresets.length === 0) return;
856
+ if (e.key === "ArrowDown") {
857
+ e.preventDefault();
858
+ setHighlightedIndex((prev) => prev < filteredPresets.length - 1 ? prev + 1 : prev);
859
+ } else if (e.key === "ArrowUp") {
860
+ e.preventDefault();
861
+ setHighlightedIndex((prev) => prev > 0 ? prev - 1 : prev);
862
+ } else if (e.key === "Enter") {
863
+ e.preventDefault();
864
+ const id = filteredPresets[highlightedIndex]?.[0];
865
+ setPreset(id);
866
+ }
867
+ };
868
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Input, {
869
+ type: "text",
870
+ placeholder: "Search themes...",
871
+ value: query,
872
+ onChange: (e) => setQuery(e.target.value),
873
+ onKeyDown: handleKeyDown,
874
+ autoFocus: true
875
+ }), /* @__PURE__ */ jsx(Scroller, {
876
+ className: "min-h-0 flex-1",
877
+ ref: scrollerRef,
878
+ children: /* @__PURE__ */ jsx("div", {
879
+ className: cn("flex flex-col gap-1 overflow-y-auto", className),
880
+ ...props,
881
+ children: filteredPresets.length > 0 ? filteredPresets.map(([id, label], index) => /* @__PURE__ */ jsx(AppPreviewCard, {
882
+ showDock: showDock ?? false,
883
+ ref: (el) => {
884
+ if (!mounted && preset === id) {
885
+ setMounted(true);
886
+ el?.scrollIntoView({
887
+ block: "center",
888
+ behavior: "instant"
889
+ });
890
+ }
891
+ if (el) itemRefs.current.set(index, el);
892
+ else itemRefs.current.delete(index);
893
+ },
894
+ active: preset === id,
895
+ highlighted: index === highlightedIndex,
896
+ label,
897
+ presetKey: id,
898
+ onClick: () => {
899
+ setPreset(id);
900
+ setHighlightedIndex(index);
901
+ }
902
+ }, id)) : /* @__PURE__ */ jsx("div", {
903
+ className: "text-muted-foreground py-8 text-center text-sm",
904
+ children: "No themes found"
905
+ })
906
+ })
907
+ })] });
908
+ }
909
+ function PresetPickerSheet({ children, ...props }) {
910
+ const { open, setOpen } = usePresetPicker("PresetPickerSheet");
911
+ return /* @__PURE__ */ jsx(Sheet, {
912
+ open,
913
+ onOpenChange: setOpen,
914
+ modal: false,
915
+ ...props,
916
+ children: /* @__PURE__ */ jsxs(SheetContent, {
917
+ side: "left",
918
+ className: cn("pointer-events-auto max-w-screen data-[side=left]:w-80 data-[side=right]:w-80 sm:max-w-md", "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left"),
919
+ onPointerDownOutside: (e) => e.preventDefault(),
920
+ onInteractOutside: (e) => e.preventDefault(),
921
+ children: [
922
+ /* @__PURE__ */ jsx(SheetContentClose, {}),
923
+ /* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsxs(SheetTitle, {
924
+ className: "flex gap-2",
925
+ children: [/* @__PURE__ */ jsx(Palette, {}), "Preset Picker"]
926
+ }) }),
927
+ /* @__PURE__ */ jsx("div", {
928
+ className: "flex min-h-0 flex-1 flex-col gap-4 px-4",
929
+ children
930
+ })
931
+ ]
932
+ })
933
+ });
934
+ }
935
+ //#endregion
936
+ export { DEFAULT_PRESET, DefaultAppPreviewCard, PRESETS, PresetPicker, PresetPickerContent, PresetPickerSheet, PresetPickerThemeToggleGroup, PresetProvider, ThemeProvider, usePreset, usePresetPicker, useTheme };