@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/README.md +40 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +936 -0
- package/dist/styles.css +6418 -0
- package/package.json +87 -0
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 };
|