@togo-framework/ui-desktop 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1836 @@
1
+ // src/components/desktop/DesktopShell.tsx
2
+ import * as React7 from "react";
3
+ import { wallpaperCss } from "@togo-framework/ui-core";
4
+
5
+ // src/components/desktop/TopBar.tsx
6
+ import * as React2 from "react";
7
+ import { LogOut as LogOut2, User } from "lucide-react";
8
+ import { Avatar, AvatarFallback } from "@togo-framework/ui-core";
9
+ import {
10
+ DropdownMenu as DropdownMenu2,
11
+ DropdownMenuContent as DropdownMenuContent2,
12
+ DropdownMenuItem as DropdownMenuItem2,
13
+ DropdownMenuSeparator as DropdownMenuSeparator2,
14
+ DropdownMenuTrigger as DropdownMenuTrigger2
15
+ } from "@togo-framework/ui-core";
16
+
17
+ // src/components/desktop/TogoMenu.tsx
18
+ import { Layers, Info, Settings, Moon, RotateCcw, Power, LogOut } from "lucide-react";
19
+ import {
20
+ DropdownMenu,
21
+ DropdownMenuContent,
22
+ DropdownMenuItem,
23
+ DropdownMenuSeparator,
24
+ DropdownMenuTrigger
25
+ } from "@togo-framework/ui-core";
26
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
27
+ function TogoMenu({
28
+ logo,
29
+ label = "togo",
30
+ onAbout,
31
+ onSettings,
32
+ onSleep,
33
+ onRestart,
34
+ onShutDown,
35
+ onLogout
36
+ }) {
37
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
38
+ /* @__PURE__ */ jsxs(DropdownMenuTrigger, { className: "flex items-center gap-1.5 rounded-md px-2 py-1 font-semibold outline-none transition hover:bg-white/10", children: [
39
+ /* @__PURE__ */ jsx("span", { className: "text-primary", children: logo ?? /* @__PURE__ */ jsx(Layers, { className: "h-4 w-4" }) }),
40
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: label })
41
+ ] }),
42
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", className: "w-56", children: [
43
+ onAbout && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onAbout, children: [
44
+ /* @__PURE__ */ jsx(Info, { className: "me-2 h-4 w-4" }),
45
+ "About This Desktop"
46
+ ] }),
47
+ onSettings && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onSettings, children: [
48
+ /* @__PURE__ */ jsx(Settings, { className: "me-2 h-4 w-4" }),
49
+ "System Settings\u2026"
50
+ ] }),
51
+ (onAbout || onSettings) && (onSleep || onRestart || onShutDown || onLogout) && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
52
+ onSleep && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onSleep, children: [
53
+ /* @__PURE__ */ jsx(Moon, { className: "me-2 h-4 w-4" }),
54
+ "Sleep"
55
+ ] }),
56
+ onRestart && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onRestart, children: [
57
+ /* @__PURE__ */ jsx(RotateCcw, { className: "me-2 h-4 w-4" }),
58
+ "Restart\u2026"
59
+ ] }),
60
+ onShutDown && /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onShutDown, children: [
61
+ /* @__PURE__ */ jsx(Power, { className: "me-2 h-4 w-4" }),
62
+ "Shut Down\u2026"
63
+ ] }),
64
+ onLogout && /* @__PURE__ */ jsxs(Fragment, { children: [
65
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
66
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { className: "text-destructive", onClick: onLogout, children: [
67
+ /* @__PURE__ */ jsx(LogOut, { className: "me-2 h-4 w-4" }),
68
+ "Log Out"
69
+ ] })
70
+ ] })
71
+ ] })
72
+ ] });
73
+ }
74
+ TogoMenu.displayName = "TogoMenu";
75
+
76
+ // src/components/desktop/WeatherWidget.tsx
77
+ import { Cloud, CloudRain, CloudSnow, CloudLightning, Sun, CloudSun, CloudFog } from "lucide-react";
78
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
79
+ var ICONS = {
80
+ clear: Sun,
81
+ partly: CloudSun,
82
+ cloudy: Cloud,
83
+ rain: CloudRain,
84
+ snow: CloudSnow,
85
+ storm: CloudLightning,
86
+ fog: CloudFog
87
+ };
88
+ function WeatherWidget({ weather, onClick, className }) {
89
+ if (!weather) return null;
90
+ const Icon = ICONS[weather.condition] ?? Cloud;
91
+ return /* @__PURE__ */ jsxs2(
92
+ "button",
93
+ {
94
+ type: "button",
95
+ onClick,
96
+ title: weather.location ? `${weather.location} \u2014 ${weather.condition}` : weather.condition,
97
+ className: ["flex items-center gap-1.5 rounded-md px-2 py-1 transition hover:bg-white/10", className].filter(Boolean).join(" "),
98
+ children: [
99
+ /* @__PURE__ */ jsx2(Icon, { className: "h-4 w-4" }),
100
+ /* @__PURE__ */ jsxs2("span", { className: "tabular-nums", children: [
101
+ Math.round(weather.temp),
102
+ weather.unit ?? "\xB0"
103
+ ] })
104
+ ]
105
+ }
106
+ );
107
+ }
108
+ WeatherWidget.displayName = "WeatherWidget";
109
+
110
+ // src/components/desktop/Spotlight.tsx
111
+ import * as React from "react";
112
+ import { Search } from "lucide-react";
113
+ import {
114
+ Command,
115
+ CommandInput,
116
+ CommandList,
117
+ CommandEmpty,
118
+ CommandGroup,
119
+ CommandItem
120
+ } from "@togo-framework/ui-core";
121
+ import { DynamicIcon } from "@togo-framework/ui-core";
122
+ import { cn } from "@togo-framework/ui-core";
123
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
124
+ function Spotlight({ open, onOpenChange, apps, onLaunch }) {
125
+ const [mounted, setMounted] = React.useState(open);
126
+ const [shown, setShown] = React.useState(false);
127
+ React.useEffect(() => {
128
+ const onKey = (e) => {
129
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
130
+ e.preventDefault();
131
+ onOpenChange(!open);
132
+ } else if (e.key === "Escape" && open) {
133
+ onOpenChange(false);
134
+ }
135
+ };
136
+ document.addEventListener("keydown", onKey);
137
+ return () => document.removeEventListener("keydown", onKey);
138
+ }, [open, onOpenChange]);
139
+ React.useEffect(() => {
140
+ if (open) {
141
+ setMounted(true);
142
+ const t2 = setTimeout(() => setShown(true), 10);
143
+ return () => clearTimeout(t2);
144
+ }
145
+ setShown(false);
146
+ const t = setTimeout(() => setMounted(false), 200);
147
+ return () => clearTimeout(t);
148
+ }, [open]);
149
+ if (!mounted) return null;
150
+ return /* @__PURE__ */ jsx3(
151
+ "div",
152
+ {
153
+ className: cn(
154
+ "fixed inset-0 z-[70] flex items-start justify-center bg-black/30 backdrop-blur-md transition-opacity duration-200",
155
+ shown ? "opacity-100" : "opacity-0"
156
+ ),
157
+ onClick: (e) => {
158
+ if (e.target === e.currentTarget) onOpenChange(false);
159
+ },
160
+ children: /* @__PURE__ */ jsx3(
161
+ "div",
162
+ {
163
+ className: cn(
164
+ "mt-[16vh] w-[92vw] max-w-xl origin-top overflow-hidden rounded-2xl border border-border/60 bg-popover/90 shadow-2xl backdrop-blur-2xl transition-all duration-200 ease-out",
165
+ shown ? "translate-y-0 scale-100 opacity-100" : "-translate-y-3 scale-95 opacity-0"
166
+ ),
167
+ children: /* @__PURE__ */ jsxs3(Command, { className: "bg-transparent [&_[cmdk-input]]:h-12", children: [
168
+ /* @__PURE__ */ jsx3(CommandInput, { autoFocus: true, placeholder: "Spotlight Search", className: "text-base" }),
169
+ /* @__PURE__ */ jsxs3(CommandList, { className: "max-h-[50vh]", children: [
170
+ /* @__PURE__ */ jsx3(CommandEmpty, { children: "No apps found." }),
171
+ /* @__PURE__ */ jsx3(CommandGroup, { heading: "Applications", children: apps.map((app) => /* @__PURE__ */ jsxs3(
172
+ CommandItem,
173
+ {
174
+ value: `${app.name} ${app.slug} ${app.category}`,
175
+ onSelect: () => {
176
+ onLaunch(app.slug);
177
+ onOpenChange(false);
178
+ },
179
+ className: "gap-3",
180
+ children: [
181
+ /* @__PURE__ */ jsx3(
182
+ "span",
183
+ {
184
+ className: "flex h-9 w-9 items-center justify-center rounded-[10px] text-white ring-1 ring-inset ring-white/25",
185
+ style: { backgroundImage: `linear-gradient(160deg, ${app.color || "#64748b"}, ${app.color || "#64748b"}bb)` },
186
+ children: /* @__PURE__ */ jsx3(DynamicIcon, { name: app.icon, size: 18 })
187
+ }
188
+ ),
189
+ /* @__PURE__ */ jsxs3("span", { className: "flex flex-col", children: [
190
+ /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium", children: app.name }),
191
+ /* @__PURE__ */ jsx3("span", { className: "text-xs capitalize text-muted-foreground", children: app.category })
192
+ ] })
193
+ ]
194
+ },
195
+ app.slug
196
+ )) })
197
+ ] })
198
+ ] })
199
+ }
200
+ )
201
+ }
202
+ );
203
+ }
204
+ Spotlight.displayName = "Spotlight";
205
+ function SpotlightTrigger({ onClick, className }) {
206
+ return /* @__PURE__ */ jsx3(
207
+ "button",
208
+ {
209
+ type: "button",
210
+ onClick,
211
+ title: "Search (\u2318K)",
212
+ className,
213
+ "aria-label": "Open Spotlight search",
214
+ children: /* @__PURE__ */ jsx3(Search, { className: "h-4 w-4" })
215
+ }
216
+ );
217
+ }
218
+
219
+ // src/components/desktop/TopBar.tsx
220
+ import { cn as cn2 } from "@togo-framework/ui-core";
221
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
222
+ function useClock() {
223
+ const [now, setNow] = React2.useState(() => /* @__PURE__ */ new Date());
224
+ React2.useEffect(() => {
225
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), 1e3 * 15);
226
+ return () => clearInterval(id);
227
+ }, []);
228
+ return now;
229
+ }
230
+ function TopBar({
231
+ me,
232
+ onLogout,
233
+ onProfile,
234
+ menu,
235
+ onOpenNotifications,
236
+ onOpenSpotlight,
237
+ weather,
238
+ onWeatherClick,
239
+ className
240
+ }) {
241
+ const now = useClock();
242
+ const clock = now.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
243
+ const dateStr = now.toLocaleDateString([], { weekday: "short", month: "short", day: "numeric" });
244
+ const initial = me?.email?.[0]?.toUpperCase() ?? "?";
245
+ return /* @__PURE__ */ jsxs4(
246
+ "div",
247
+ {
248
+ className: cn2(
249
+ "fixed inset-x-0 top-0 z-50 flex h-9 items-center justify-between gap-3 bg-black/25 px-2 text-xs text-white backdrop-blur-lg",
250
+ className
251
+ ),
252
+ children: [
253
+ /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-1", children: menu?.enabled !== false && /* @__PURE__ */ jsx4(TogoMenu, { ...menu, onLogout }) }),
254
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-0.5", children: [
255
+ /* @__PURE__ */ jsx4(WeatherWidget, { weather, onClick: onWeatherClick, className: "text-white/90" }),
256
+ onOpenSpotlight && /* @__PURE__ */ jsx4(
257
+ SpotlightTrigger,
258
+ {
259
+ onClick: onOpenSpotlight,
260
+ className: "flex h-7 w-7 items-center justify-center rounded-md text-white/90 transition hover:bg-white/10"
261
+ }
262
+ ),
263
+ me && /* @__PURE__ */ jsxs4(DropdownMenu2, { children: [
264
+ /* @__PURE__ */ jsx4(DropdownMenuTrigger2, { className: "flex items-center gap-2 rounded-full p-0.5 outline-none transition hover:bg-white/10", children: /* @__PURE__ */ jsx4(Avatar, { className: "h-6 w-6", children: /* @__PURE__ */ jsx4(AvatarFallback, { className: "bg-white/20 text-[10px] text-white", children: initial }) }) }),
265
+ /* @__PURE__ */ jsxs4(DropdownMenuContent2, { align: "end", className: "w-56", children: [
266
+ /* @__PURE__ */ jsx4("div", { className: "truncate px-2 py-1.5 text-xs text-muted-foreground", children: me.email }),
267
+ /* @__PURE__ */ jsx4(DropdownMenuSeparator2, {}),
268
+ onProfile && /* @__PURE__ */ jsxs4(DropdownMenuItem2, { onClick: onProfile, children: [
269
+ /* @__PURE__ */ jsx4(User, { className: "me-2 h-4 w-4" }),
270
+ "Profile"
271
+ ] }),
272
+ /* @__PURE__ */ jsxs4(DropdownMenuItem2, { className: "text-destructive", onClick: onLogout, children: [
273
+ /* @__PURE__ */ jsx4(LogOut2, { className: "me-2 h-4 w-4" }),
274
+ "Sign out"
275
+ ] })
276
+ ] })
277
+ ] }),
278
+ /* @__PURE__ */ jsxs4(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ onClick: onOpenNotifications,
283
+ className: "flex items-center gap-2 rounded-md px-2 py-1 font-medium transition hover:bg-white/10",
284
+ title: "Open Notification Center",
285
+ children: [
286
+ /* @__PURE__ */ jsx4("span", { className: "hidden text-white/80 sm:inline", children: dateStr }),
287
+ /* @__PURE__ */ jsx4("span", { children: clock })
288
+ ]
289
+ }
290
+ )
291
+ ] })
292
+ ]
293
+ }
294
+ );
295
+ }
296
+ TopBar.displayName = "TopBar";
297
+
298
+ // src/components/desktop/Dock.tsx
299
+ import * as React3 from "react";
300
+ import { LayoutGrid, Trash2, SquareArrowOutUpRight, X, PinOff } from "lucide-react";
301
+ import { DynamicIcon as DynamicIcon2 } from "@togo-framework/ui-core";
302
+ import {
303
+ ContextMenu,
304
+ ContextMenuTrigger,
305
+ ContextMenuContent,
306
+ ContextMenuItem
307
+ } from "@togo-framework/ui-core";
308
+ import { cn as cn3 } from "@togo-framework/ui-core";
309
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
310
+ var DockButton = React3.forwardRef(
311
+ ({ label, color, isOpen, children, ...rest }, ref) => {
312
+ const base = color || "#64748b";
313
+ return /* @__PURE__ */ jsxs5(
314
+ "button",
315
+ {
316
+ ref,
317
+ type: "button",
318
+ title: label,
319
+ ...rest,
320
+ className: "group relative flex flex-col items-center transition-transform duration-150 ease-out hover:-translate-y-1.5",
321
+ children: [
322
+ /* @__PURE__ */ jsx5(
323
+ "span",
324
+ {
325
+ className: "flex h-11 w-11 items-center justify-center rounded-[14px] text-white shadow-md ring-1 ring-inset ring-white/25 transition-shadow group-hover:shadow-xl",
326
+ style: { backgroundImage: `linear-gradient(160deg, ${base}, ${base}bb)` },
327
+ children: /* @__PURE__ */ jsx5("span", { className: "drop-shadow-[0_1px_1px_rgba(0,0,0,0.35)]", children })
328
+ }
329
+ ),
330
+ /* @__PURE__ */ jsx5("span", { className: cn3("mt-1 h-1 w-1 rounded-full bg-white/90 transition-opacity", isOpen ? "opacity-100" : "opacity-0") })
331
+ ]
332
+ }
333
+ );
334
+ }
335
+ );
336
+ DockButton.displayName = "DockButton";
337
+ function Dock({ apps, pinned, openSlugs = [], onLaunch, onCloseApp, onUnpin, onLaunchpad, onTrash, className }) {
338
+ const bySlug = React3.useMemo(() => new Map(apps.map((a) => [a.slug, a])), [apps]);
339
+ const slugs = React3.useMemo(() => {
340
+ const seen = /* @__PURE__ */ new Set();
341
+ const ordered = [];
342
+ for (const s of [...pinned, ...openSlugs]) {
343
+ if (!seen.has(s) && bySlug.has(s)) {
344
+ seen.add(s);
345
+ ordered.push(s);
346
+ }
347
+ }
348
+ return ordered;
349
+ }, [pinned, openSlugs, bySlug]);
350
+ return /* @__PURE__ */ jsx5("div", { className: cn3("pointer-events-none fixed inset-x-0 bottom-3 z-40 flex justify-center px-2", className), children: /* @__PURE__ */ jsxs5("div", { className: "no-scrollbar pointer-events-auto flex max-w-full items-end gap-1.5 overflow-x-auto rounded-2xl border border-white/15 bg-black/25 px-2.5 py-2 shadow-2xl backdrop-blur-2xl", children: [
351
+ onLaunchpad && /* @__PURE__ */ jsxs5(Fragment2, { children: [
352
+ /* @__PURE__ */ jsx5(DockButton, { label: "Launchpad", color: "#6366f1", onClick: onLaunchpad, children: /* @__PURE__ */ jsx5(LayoutGrid, { className: "h-5 w-5" }) }),
353
+ (slugs.length > 0 || onTrash) && /* @__PURE__ */ jsx5("span", { className: "mx-1 h-9 w-px self-center bg-white/15" })
354
+ ] }),
355
+ slugs.map((slug) => {
356
+ const app = bySlug.get(slug);
357
+ const running = openSlugs.includes(slug);
358
+ return /* @__PURE__ */ jsxs5(ContextMenu, { children: [
359
+ /* @__PURE__ */ jsx5(ContextMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx5(DockButton, { label: app.name, color: app.color, isOpen: running, onClick: () => onLaunch(slug), children: /* @__PURE__ */ jsx5(DynamicIcon2, { name: app.icon, size: 22 }) }) }),
360
+ /* @__PURE__ */ jsxs5(ContextMenuContent, { className: "w-40", children: [
361
+ /* @__PURE__ */ jsxs5(ContextMenuItem, { onClick: () => onLaunch(slug), children: [
362
+ /* @__PURE__ */ jsx5(SquareArrowOutUpRight, { className: "me-2 h-4 w-4" }),
363
+ running ? "Show" : "Open"
364
+ ] }),
365
+ onUnpin && /* @__PURE__ */ jsxs5(ContextMenuItem, { onClick: () => onUnpin(slug), children: [
366
+ /* @__PURE__ */ jsx5(PinOff, { className: "me-2 h-4 w-4" }),
367
+ "Remove from Dock"
368
+ ] }),
369
+ running && onCloseApp && /* @__PURE__ */ jsxs5(ContextMenuItem, { className: "text-destructive", onClick: () => onCloseApp(slug), children: [
370
+ /* @__PURE__ */ jsx5(X, { className: "me-2 h-4 w-4" }),
371
+ "Quit ",
372
+ app.name
373
+ ] })
374
+ ] })
375
+ ] }, slug);
376
+ }),
377
+ onTrash && /* @__PURE__ */ jsxs5(Fragment2, { children: [
378
+ /* @__PURE__ */ jsx5("span", { className: "mx-1 h-9 w-px self-center bg-white/15" }),
379
+ /* @__PURE__ */ jsx5(DockButton, { label: "Trash", onClick: onTrash, children: /* @__PURE__ */ jsx5(Trash2, { className: "h-5 w-5 text-white/80" }) })
380
+ ] })
381
+ ] }) });
382
+ }
383
+ Dock.displayName = "Dock";
384
+
385
+ // src/components/desktop/DesktopIconGrid.tsx
386
+ import * as React4 from "react";
387
+ import { SquareArrowOutUpRight as SquareArrowOutUpRight2, Trash2 as Trash22 } from "lucide-react";
388
+
389
+ // src/components/desktop/DesktopIcon.tsx
390
+ import { DynamicIcon as DynamicIcon3 } from "@togo-framework/ui-core";
391
+ import { cn as cn4 } from "@togo-framework/ui-core";
392
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
393
+ function DesktopIcon({ app, onOpen, selected, onSelect, tapToOpen, className, style }) {
394
+ return /* @__PURE__ */ jsxs6(
395
+ "button",
396
+ {
397
+ type: "button",
398
+ style,
399
+ className: cn4(
400
+ "flex w-20 select-none flex-col items-center gap-1 rounded-lg p-2 text-center outline-none",
401
+ selected ? "bg-primary/20" : "hover:bg-white/10 focus-visible:bg-white/10",
402
+ className
403
+ ),
404
+ onClick: () => {
405
+ onSelect?.(app.slug);
406
+ if (tapToOpen) onOpen(app.slug);
407
+ },
408
+ onDoubleClick: () => onOpen(app.slug),
409
+ onKeyDown: (e) => {
410
+ if (e.key === "Enter" || e.key === " ") {
411
+ e.preventDefault();
412
+ onOpen(app.slug);
413
+ }
414
+ },
415
+ children: [
416
+ /* @__PURE__ */ jsx6(
417
+ "span",
418
+ {
419
+ className: "flex h-12 w-12 items-center justify-center rounded-[14px] text-white shadow-md ring-1 ring-inset ring-white/25",
420
+ style: { backgroundImage: `linear-gradient(160deg, ${app.color || "#64748b"}, ${app.color || "#64748b"}bb)` },
421
+ children: /* @__PURE__ */ jsx6("span", { className: "drop-shadow-[0_1px_1px_rgba(0,0,0,0.35)]", children: /* @__PURE__ */ jsx6(DynamicIcon3, { name: app.icon, size: 24 }) })
422
+ }
423
+ ),
424
+ /* @__PURE__ */ jsx6("span", { className: "line-clamp-2 text-xs font-medium text-white [text-shadow:0_1px_3px_rgba(0,0,0,0.7)]", children: app.name })
425
+ ]
426
+ }
427
+ );
428
+ }
429
+ DesktopIcon.displayName = "DesktopIcon";
430
+
431
+ // src/components/desktop/DesktopIconGrid.tsx
432
+ import {
433
+ ContextMenu as ContextMenu2,
434
+ ContextMenuTrigger as ContextMenuTrigger2,
435
+ ContextMenuContent as ContextMenuContent2,
436
+ ContextMenuItem as ContextMenuItem2
437
+ } from "@togo-framework/ui-core";
438
+ import { cn as cn5 } from "@togo-framework/ui-core";
439
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
440
+ var CELL_W = 88;
441
+ var CELL_H = 104;
442
+ var TOP = 48;
443
+ var LEFT = 16;
444
+ function clampPos(p) {
445
+ if (typeof window === "undefined") return p;
446
+ const x = Math.min(Math.max(p.x, 8), window.innerWidth - CELL_W);
447
+ const y = Math.min(Math.max(p.y, TOP), window.innerHeight - CELL_H - 72);
448
+ return { x, y };
449
+ }
450
+ function defaultPos(index) {
451
+ const perCol = Math.max(3, Math.floor((typeof window !== "undefined" ? window.innerHeight - TOP - 120 : 600) / CELL_H));
452
+ const col = Math.floor(index / perCol);
453
+ const row = index % perCol;
454
+ return { x: LEFT + col * CELL_W, y: TOP + row * CELL_H };
455
+ }
456
+ function IconMenu({ app, onOpen, onRemove, children }) {
457
+ if (!onRemove) return /* @__PURE__ */ jsx7(Fragment3, { children });
458
+ return /* @__PURE__ */ jsxs7(ContextMenu2, { children: [
459
+ /* @__PURE__ */ jsx7(ContextMenuTrigger2, { asChild: true, children }),
460
+ /* @__PURE__ */ jsxs7(ContextMenuContent2, { className: "w-48", children: [
461
+ /* @__PURE__ */ jsxs7(ContextMenuItem2, { onClick: () => onOpen(app.slug), children: [
462
+ /* @__PURE__ */ jsx7(SquareArrowOutUpRight2, { className: "me-2 h-4 w-4" }),
463
+ "Open"
464
+ ] }),
465
+ /* @__PURE__ */ jsxs7(ContextMenuItem2, { className: "text-destructive", onClick: () => onRemove(app.slug), children: [
466
+ /* @__PURE__ */ jsx7(Trash22, { className: "me-2 h-4 w-4" }),
467
+ "Remove from Desktop"
468
+ ] })
469
+ ] })
470
+ ] });
471
+ }
472
+ function DesktopIconGrid({ apps, onOpen, positions, onMove, hidden = [], onRemove, className }) {
473
+ const [selected, setSelected] = React4.useState(null);
474
+ const hiddenSet = React4.useMemo(() => new Set(hidden), [hidden]);
475
+ const visible = React4.useMemo(
476
+ () => apps.filter((a) => a.enabled && !hiddenSet.has(a.slug)),
477
+ [apps, hiddenSet]
478
+ );
479
+ if (!onMove) {
480
+ return /* @__PURE__ */ jsx7(
481
+ "div",
482
+ {
483
+ className: cn5("grid auto-rows-max grid-cols-[repeat(auto-fill,5rem)] gap-1 p-4", className),
484
+ onClick: (e) => {
485
+ if (e.target === e.currentTarget) setSelected(null);
486
+ },
487
+ children: visible.map((app) => /* @__PURE__ */ jsx7(IconMenu, { app, onOpen, onRemove, children: /* @__PURE__ */ jsx7(DesktopIcon, { app, onOpen, tapToOpen: true, selected: selected === app.slug, onSelect: setSelected }) }, app.slug))
488
+ }
489
+ );
490
+ }
491
+ return /* @__PURE__ */ jsx7(
492
+ "div",
493
+ {
494
+ className: cn5("absolute inset-0", className),
495
+ onPointerDown: (e) => {
496
+ if (e.target === e.currentTarget) setSelected(null);
497
+ },
498
+ children: visible.map((app, i) => /* @__PURE__ */ jsx7(
499
+ PositionedIcon,
500
+ {
501
+ app,
502
+ pos: positions?.[app.slug] ?? defaultPos(i),
503
+ onOpen,
504
+ onMove,
505
+ onRemove,
506
+ selected: selected === app.slug,
507
+ onSelect: setSelected
508
+ },
509
+ app.slug
510
+ ))
511
+ }
512
+ );
513
+ }
514
+ function PositionedIcon({
515
+ app,
516
+ pos,
517
+ onOpen,
518
+ onMove,
519
+ onRemove,
520
+ selected,
521
+ onSelect
522
+ }) {
523
+ const [live, setLive] = React4.useState(pos);
524
+ const dragging = React4.useRef(false);
525
+ React4.useEffect(() => {
526
+ if (!dragging.current) setLive(pos);
527
+ }, [pos]);
528
+ const onPointerDown = (e) => {
529
+ if (e.button !== 0) return;
530
+ onSelect(app.slug);
531
+ const el = e.currentTarget;
532
+ const startX = e.clientX;
533
+ const startY = e.clientY;
534
+ const orig = live;
535
+ let moved = false;
536
+ let captured = false;
537
+ const move = (ev) => {
538
+ const dx = ev.clientX - startX;
539
+ const dy = ev.clientY - startY;
540
+ if (!moved && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {
541
+ moved = true;
542
+ dragging.current = true;
543
+ el.setPointerCapture(e.pointerId);
544
+ captured = true;
545
+ }
546
+ if (dragging.current) setLive(clampPos({ x: orig.x + dx, y: orig.y + dy }));
547
+ };
548
+ const up = () => {
549
+ el.removeEventListener("pointermove", move);
550
+ el.removeEventListener("pointerup", up);
551
+ if (captured) el.releasePointerCapture?.(e.pointerId);
552
+ if (moved) {
553
+ dragging.current = false;
554
+ setLive((p) => {
555
+ onMove(app.slug, p);
556
+ return p;
557
+ });
558
+ } else {
559
+ onOpen(app.slug);
560
+ }
561
+ };
562
+ el.addEventListener("pointermove", move);
563
+ el.addEventListener("pointerup", up);
564
+ };
565
+ return /* @__PURE__ */ jsx7(IconMenu, { app, onOpen, onRemove, children: /* @__PURE__ */ jsx7("div", { className: "absolute touch-none", style: { left: live.x, top: live.y }, onPointerDown, children: /* @__PURE__ */ jsx7(DesktopIcon, { app, onOpen, selected }) }) });
566
+ }
567
+ DesktopIconGrid.displayName = "DesktopIconGrid";
568
+
569
+ // src/components/desktop/DesktopContextMenu.tsx
570
+ import { Image as ImageIcon, Palette, RefreshCw } from "lucide-react";
571
+ import {
572
+ ContextMenu as ContextMenu3,
573
+ ContextMenuContent as ContextMenuContent3,
574
+ ContextMenuItem as ContextMenuItem3,
575
+ ContextMenuSeparator,
576
+ ContextMenuTrigger as ContextMenuTrigger3
577
+ } from "@togo-framework/ui-core";
578
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
579
+ function DesktopContextMenu({ onAction, children, className }) {
580
+ return /* @__PURE__ */ jsxs8(ContextMenu3, { children: [
581
+ /* @__PURE__ */ jsx8(ContextMenuTrigger3, { className, children }),
582
+ /* @__PURE__ */ jsxs8(ContextMenuContent3, { className: "w-52", children: [
583
+ /* @__PURE__ */ jsxs8(ContextMenuItem3, { onClick: () => onAction("change-wallpaper"), children: [
584
+ /* @__PURE__ */ jsx8(ImageIcon, { className: "me-2 h-4 w-4" }),
585
+ "Change wallpaper"
586
+ ] }),
587
+ /* @__PURE__ */ jsxs8(ContextMenuItem3, { onClick: () => onAction("personalize"), children: [
588
+ /* @__PURE__ */ jsx8(Palette, { className: "me-2 h-4 w-4" }),
589
+ "Personalize"
590
+ ] }),
591
+ /* @__PURE__ */ jsx8(ContextMenuSeparator, {}),
592
+ /* @__PURE__ */ jsxs8(ContextMenuItem3, { onClick: () => onAction("refresh"), children: [
593
+ /* @__PURE__ */ jsx8(RefreshCw, { className: "me-2 h-4 w-4" }),
594
+ "Refresh"
595
+ ] })
596
+ ] })
597
+ ] });
598
+ }
599
+ DesktopContextMenu.displayName = "DesktopContextMenu";
600
+
601
+ // src/components/desktop/WindowManager.tsx
602
+ import { createContext, useCallback as useCallback2, useContext, useMemo as useMemo4, useRef as useRef3, useState as useState5 } from "react";
603
+
604
+ // src/components/desktop/Window.tsx
605
+ import * as React5 from "react";
606
+ import { useCallback, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef2, useState as useState4 } from "react";
607
+ import { X as X2, Minus, Plus } from "lucide-react";
608
+ import { DynamicIcon as DynamicIcon4 } from "@togo-framework/ui-core";
609
+ import { cn as cn6 } from "@togo-framework/ui-core";
610
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
611
+ var MIN_W = 320;
612
+ var MIN_H = 220;
613
+ var TOP_INSET = 40;
614
+ var DOCK_INSET = 92;
615
+ function clampWindowRect(r) {
616
+ if (typeof window === "undefined") return r;
617
+ const vw = window.innerWidth;
618
+ const vh = window.innerHeight;
619
+ const w = Math.min(Math.max(r.w, MIN_W), vw - 16);
620
+ const h = Math.min(Math.max(r.h, MIN_H), vh - TOP_INSET - DOCK_INSET);
621
+ const x = Math.min(Math.max(r.x, 8), Math.max(8, vw - w - 8));
622
+ const y = Math.min(Math.max(r.y, TOP_INSET), Math.max(TOP_INSET, vh - h - DOCK_INSET));
623
+ return { x, y, w, h };
624
+ }
625
+ function Window({
626
+ title,
627
+ icon,
628
+ rect,
629
+ onRectChange,
630
+ resizable = true,
631
+ minimized = false,
632
+ zIndex = 10,
633
+ onClose,
634
+ onMinimize,
635
+ onMaximizeToggle,
636
+ maximized = false,
637
+ onFocus,
638
+ className,
639
+ children
640
+ }) {
641
+ const [live, setLive] = useState4(rect);
642
+ const dragging = useRef2(false);
643
+ const [phase, setPhase] = useState4("enter");
644
+ const prevMinimized = useRef2(minimized);
645
+ const [snapHint, setSnapHint] = useState4(null);
646
+ const [isMobile, setIsMobile] = useState4(false);
647
+ useEffect4(() => {
648
+ const mq = window.matchMedia("(max-width: 640px)");
649
+ const on = () => setIsMobile(mq.matches);
650
+ on();
651
+ mq.addEventListener("change", on);
652
+ return () => mq.removeEventListener("change", on);
653
+ }, []);
654
+ useEffect4(() => {
655
+ if (!dragging.current) setLive(rect);
656
+ }, [rect]);
657
+ useEffect4(() => {
658
+ const t = setTimeout(() => setPhase("shown"), 10);
659
+ return () => clearTimeout(t);
660
+ }, []);
661
+ useEffect4(() => {
662
+ const onResize = () => setLive((r) => clampWindowRect(r));
663
+ window.addEventListener("resize", onResize);
664
+ return () => window.removeEventListener("resize", onResize);
665
+ }, []);
666
+ const beginGesture = useCallback(
667
+ (apply) => (e) => {
668
+ if (maximized) return;
669
+ e.preventDefault();
670
+ e.stopPropagation();
671
+ onFocus?.();
672
+ const orig = live;
673
+ const startX = e.clientX;
674
+ const startY = e.clientY;
675
+ const el = e.currentTarget;
676
+ el.setPointerCapture(e.pointerId);
677
+ dragging.current = true;
678
+ const move = (ev) => {
679
+ setLive(clampWindowRect(apply(orig, ev.clientX - startX, ev.clientY - startY)));
680
+ };
681
+ const up = () => {
682
+ dragging.current = false;
683
+ el.removeEventListener("pointermove", move);
684
+ el.removeEventListener("pointerup", up);
685
+ el.removeEventListener("pointercancel", up);
686
+ setLive((r) => {
687
+ onRectChange(r);
688
+ return r;
689
+ });
690
+ };
691
+ el.addEventListener("pointermove", move);
692
+ el.addEventListener("pointerup", up);
693
+ el.addEventListener("pointercancel", up);
694
+ },
695
+ [maximized, live, onFocus, onRectChange]
696
+ );
697
+ const snapRect = useCallback((zone) => {
698
+ const vw = window.innerWidth;
699
+ const vh = window.innerHeight;
700
+ const h = vh - TOP_INSET - DOCK_INSET;
701
+ if (zone === "left") return { x: 8, y: TOP_INSET, w: vw / 2 - 12, h };
702
+ if (zone === "right") return { x: vw / 2 + 4, y: TOP_INSET, w: vw / 2 - 12, h };
703
+ return { x: 8, y: TOP_INSET, w: vw - 16, h };
704
+ }, []);
705
+ const onDragTitle = useCallback(
706
+ (e) => {
707
+ if (maximized || isMobile) return;
708
+ e.preventDefault();
709
+ e.stopPropagation();
710
+ onFocus?.();
711
+ const orig = live;
712
+ const startX = e.clientX;
713
+ const startY = e.clientY;
714
+ const el = e.currentTarget;
715
+ el.setPointerCapture(e.pointerId);
716
+ dragging.current = true;
717
+ const zoneFor = (x, y) => {
718
+ if (y <= 6) return "max";
719
+ if (x <= 6) return "left";
720
+ if (x >= window.innerWidth - 6) return "right";
721
+ return null;
722
+ };
723
+ const move = (ev) => {
724
+ setLive(clampWindowRect({ ...orig, x: orig.x + (ev.clientX - startX), y: orig.y + (ev.clientY - startY) }));
725
+ setSnapHint(zoneFor(ev.clientX, ev.clientY));
726
+ };
727
+ const up = (ev) => {
728
+ dragging.current = false;
729
+ el.removeEventListener("pointermove", move);
730
+ el.removeEventListener("pointerup", up);
731
+ el.removeEventListener("pointercancel", up);
732
+ const zone = zoneFor(ev.clientX, ev.clientY);
733
+ setSnapHint(null);
734
+ if (zone) {
735
+ const r = snapRect(zone);
736
+ setLive(r);
737
+ onRectChange(r);
738
+ } else {
739
+ setLive((r) => {
740
+ onRectChange(r);
741
+ return r;
742
+ });
743
+ }
744
+ };
745
+ el.addEventListener("pointermove", move);
746
+ el.addEventListener("pointerup", up);
747
+ el.addEventListener("pointercancel", up);
748
+ },
749
+ [maximized, isMobile, live, onFocus, onRectChange, snapRect]
750
+ );
751
+ const resizeHandlers = {
752
+ e: (o, dx) => ({ ...o, w: o.w + dx }),
753
+ s: (o, _dx, dy) => ({ ...o, h: o.h + dy }),
754
+ se: (o, dx, dy) => ({ ...o, w: o.w + dx, h: o.h + dy }),
755
+ w: (o, dx) => ({ ...o, x: o.x + dx, w: o.w - dx }),
756
+ n: (o, _dx, dy) => ({ ...o, y: o.y + dy, h: o.h - dy }),
757
+ nw: (o, dx, dy) => ({ ...o, x: o.x + dx, y: o.y + dy, w: o.w - dx, h: o.h - dy }),
758
+ ne: (o, dx, dy) => ({ ...o, y: o.y + dy, w: o.w + dx, h: o.h - dy }),
759
+ sw: (o, dx, dy) => ({ ...o, x: o.x + dx, w: o.w - dx, h: o.h + dy })
760
+ };
761
+ const style = useMemo3(() => {
762
+ if (isMobile) return { left: 0, top: TOP_INSET, right: 0, bottom: DOCK_INSET, width: "auto", height: "auto", zIndex };
763
+ if (maximized) return { left: 8, top: TOP_INSET, right: 8, bottom: DOCK_INSET, width: "auto", height: "auto", zIndex };
764
+ return { left: live.x, top: live.y, width: live.w, height: live.h, zIndex };
765
+ }, [isMobile, maximized, live, zIndex]);
766
+ prevMinimized.current = minimized;
767
+ const animating = phase === "enter";
768
+ const snapPreview = snapHint ? (() => {
769
+ const r = snapRect(snapHint);
770
+ return /* @__PURE__ */ jsx9(
771
+ "div",
772
+ {
773
+ className: "pointer-events-none fixed rounded-xl border-2 border-primary/70 bg-primary/15 backdrop-blur-sm transition-all duration-100",
774
+ style: { left: r.x, top: r.y, width: r.w, height: r.h, zIndex: zIndex - 1 },
775
+ "aria-hidden": "true"
776
+ }
777
+ );
778
+ })() : null;
779
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
780
+ snapPreview,
781
+ /* @__PURE__ */ jsxs9(
782
+ "div",
783
+ {
784
+ className: cn6(
785
+ "fixed flex flex-col overflow-hidden border border-border bg-card/95 shadow-2xl backdrop-blur-xl",
786
+ isMobile ? "rounded-none" : "rounded-xl",
787
+ "origin-bottom transition-[opacity,transform] duration-200 ease-out",
788
+ minimized ? "pointer-events-none translate-y-[45vh] scale-50 opacity-0" : animating ? "scale-95 opacity-0" : "scale-100 opacity-100",
789
+ className
790
+ ),
791
+ style,
792
+ "aria-hidden": minimized,
793
+ onPointerDown: () => onFocus?.(),
794
+ role: "dialog",
795
+ "aria-label": title,
796
+ children: [
797
+ /* @__PURE__ */ jsxs9(
798
+ "div",
799
+ {
800
+ className: "flex shrink-0 cursor-grab select-none items-center gap-2 border-b border-border bg-background/70 px-3 py-2 active:cursor-grabbing",
801
+ onPointerDown: onDragTitle,
802
+ onDoubleClick: onMaximizeToggle,
803
+ children: [
804
+ /* @__PURE__ */ jsxs9("div", { className: "group/lights flex items-center gap-1.5", onPointerDown: (e) => e.stopPropagation(), children: [
805
+ /* @__PURE__ */ jsx9(
806
+ "button",
807
+ {
808
+ type: "button",
809
+ "aria-label": "Close",
810
+ onClick: onClose,
811
+ className: "flex h-3 w-3 items-center justify-center rounded-full bg-[#ff5f57] text-[#7a0d02] transition hover:brightness-95",
812
+ children: /* @__PURE__ */ jsx9(X2, { className: "h-2 w-2 opacity-0 transition-opacity group-hover/lights:opacity-100", strokeWidth: 3 })
813
+ }
814
+ ),
815
+ /* @__PURE__ */ jsx9(
816
+ "button",
817
+ {
818
+ type: "button",
819
+ "aria-label": "Minimize",
820
+ onClick: onMinimize,
821
+ className: "flex h-3 w-3 items-center justify-center rounded-full bg-[#febc2e] text-[#985712] transition hover:brightness-95",
822
+ children: /* @__PURE__ */ jsx9(Minus, { className: "h-2 w-2 opacity-0 transition-opacity group-hover/lights:opacity-100", strokeWidth: 3 })
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsx9(
826
+ "button",
827
+ {
828
+ type: "button",
829
+ "aria-label": "Maximize",
830
+ onClick: onMaximizeToggle,
831
+ className: "flex h-3 w-3 items-center justify-center rounded-full bg-[#28c840] text-[#0b6118] transition hover:brightness-95",
832
+ children: /* @__PURE__ */ jsx9(Plus, { className: "h-2 w-2 opacity-0 transition-opacity group-hover/lights:opacity-100", strokeWidth: 3 })
833
+ }
834
+ )
835
+ ] }),
836
+ icon && /* @__PURE__ */ jsx9(DynamicIcon4, { name: icon, size: 14, className: "ms-1 shrink-0 text-muted-foreground" }),
837
+ /* @__PURE__ */ jsx9("span", { className: "flex-1 truncate text-center text-xs font-medium text-foreground", children: title }),
838
+ /* @__PURE__ */ jsx9("span", { className: "w-12", "aria-hidden": "true" })
839
+ ]
840
+ }
841
+ ),
842
+ /* @__PURE__ */ jsx9("div", { className: "min-h-0 flex-1 overflow-auto", children: /* @__PURE__ */ jsx9(React5.Suspense, { fallback: /* @__PURE__ */ jsx9(WindowSpinner, {}), children }) }),
843
+ resizable && !maximized && !isMobile && /* @__PURE__ */ jsxs9(Fragment4, { children: [
844
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.n), className: "absolute inset-x-2 top-0 h-1.5 cursor-ns-resize touch-none" }),
845
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.s), className: "absolute inset-x-2 bottom-0 h-1.5 cursor-ns-resize touch-none" }),
846
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.w), className: "absolute inset-y-2 left-0 w-1.5 cursor-ew-resize touch-none" }),
847
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.e), className: "absolute inset-y-2 right-0 w-1.5 cursor-ew-resize touch-none" }),
848
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.nw), className: "absolute left-0 top-0 h-3 w-3 cursor-nwse-resize touch-none" }),
849
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.ne), className: "absolute right-0 top-0 h-3 w-3 cursor-nesw-resize touch-none" }),
850
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.sw), className: "absolute bottom-0 left-0 h-3 w-3 cursor-nesw-resize touch-none" }),
851
+ /* @__PURE__ */ jsx9("div", { onPointerDown: beginGesture(resizeHandlers.se), className: "absolute bottom-0 right-0 h-3.5 w-3.5 cursor-nwse-resize touch-none" })
852
+ ] })
853
+ ]
854
+ }
855
+ )
856
+ ] });
857
+ }
858
+ Window.displayName = "Window";
859
+ function WindowSpinner() {
860
+ return /* @__PURE__ */ jsx9("div", { className: "flex h-full min-h-[160px] w-full items-center justify-center", children: /* @__PURE__ */ jsx9("span", { className: "h-7 w-7 animate-spin rounded-full border-2 border-muted border-t-primary", "aria-label": "Loading" }) });
861
+ }
862
+
863
+ // src/components/desktop/WindowManager.tsx
864
+ import { Fragment as Fragment5, jsx as jsx10 } from "react/jsx-runtime";
865
+ var WindowSectionContext = createContext({ slug: "", nonce: 0 });
866
+ function useWindowSection() {
867
+ return useContext(WindowSectionContext);
868
+ }
869
+ var WindowManagerContext = createContext(null);
870
+ var zCounter = 10;
871
+ function defaultRect(width = 640, height = 440) {
872
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1280;
873
+ const vh = typeof window !== "undefined" ? window.innerHeight : 800;
874
+ const x = Math.max(24, (vw - width) / 2 + (Math.random() * 40 - 20));
875
+ const y = Math.max(24, (vh - height) / 2 + (Math.random() * 40 - 20));
876
+ return clampWindowRect({ x, y, w: width, h: height });
877
+ }
878
+ function WindowManagerProvider({ children }) {
879
+ const [windows, setWindows] = useState5([]);
880
+ const slugsRef = useRef3(/* @__PURE__ */ new Set());
881
+ const open = useCallback2((slug, opts) => {
882
+ setWindows((prev) => {
883
+ const existing = prev.find((w) => w.slug === slug);
884
+ zCounter += 1;
885
+ if (existing) {
886
+ return prev.map(
887
+ (w) => w.slug === slug ? { ...w, ...opts, minimized: false, z: zCounter, sectionNonce: w.sectionNonce + 1 } : w
888
+ );
889
+ }
890
+ slugsRef.current.add(slug);
891
+ return [
892
+ ...prev,
893
+ {
894
+ slug,
895
+ ...opts,
896
+ rect: defaultRect(opts.width, opts.height),
897
+ minimized: false,
898
+ maximized: false,
899
+ z: zCounter,
900
+ sectionNonce: 0
901
+ }
902
+ ];
903
+ });
904
+ }, []);
905
+ const close = useCallback2((slug) => {
906
+ slugsRef.current.delete(slug);
907
+ setWindows((prev) => prev.filter((w) => w.slug !== slug));
908
+ }, []);
909
+ const focus = useCallback2((slug) => {
910
+ zCounter += 1;
911
+ const z = zCounter;
912
+ setWindows((prev) => prev.map((w) => w.slug === slug ? { ...w, z } : w));
913
+ }, []);
914
+ const minimize = useCallback2((slug) => {
915
+ setWindows((prev) => prev.map((w) => w.slug === slug ? { ...w, minimized: !w.minimized } : w));
916
+ }, []);
917
+ const restore = useCallback2((slug) => {
918
+ zCounter += 1;
919
+ const z = zCounter;
920
+ setWindows((prev) => prev.map((w) => w.slug === slug ? { ...w, minimized: false, z } : w));
921
+ }, []);
922
+ const toggleMaximize = useCallback2((slug) => {
923
+ setWindows((prev) => prev.map((w) => w.slug === slug ? { ...w, maximized: !w.maximized } : w));
924
+ }, []);
925
+ const isOpen = useCallback2((slug) => slugsRef.current.has(slug), []);
926
+ const isMinimized = useCallback2(
927
+ (slug) => windows.find((w) => w.slug === slug)?.minimized ?? false,
928
+ [windows]
929
+ );
930
+ const setSection = useCallback2((slug, section) => {
931
+ setWindows(
932
+ (prev) => prev.map((w) => w.slug === slug ? { ...w, section, sectionNonce: w.sectionNonce + 1 } : w)
933
+ );
934
+ }, []);
935
+ const updateRect = useCallback2((slug, rect) => {
936
+ setWindows((prev) => prev.map((e) => e.slug === slug ? { ...e, rect } : e));
937
+ }, []);
938
+ const value = useMemo4(
939
+ () => ({ windows, open, close, focus, minimize, restore, toggleMaximize, isOpen, isMinimized, setSection, updateRect }),
940
+ [windows, open, close, focus, minimize, restore, toggleMaximize, isOpen, isMinimized, setSection, updateRect]
941
+ );
942
+ return /* @__PURE__ */ jsx10(WindowManagerContext.Provider, { value, children });
943
+ }
944
+ function useWindowManager() {
945
+ const ctx = useContext(WindowManagerContext);
946
+ if (!ctx) throw new Error("useWindowManager must be used within a <WindowManagerProvider>");
947
+ return ctx;
948
+ }
949
+ function WindowManager() {
950
+ const { windows, close, minimize, toggleMaximize, focus, updateRect } = useWindowManager();
951
+ return /* @__PURE__ */ jsx10(Fragment5, { children: windows.map((w) => /* @__PURE__ */ jsx10(
952
+ Window,
953
+ {
954
+ title: w.title,
955
+ icon: w.icon,
956
+ rect: w.rect,
957
+ resizable: w.resizable ?? true,
958
+ minimized: w.minimized,
959
+ maximized: w.maximized,
960
+ zIndex: w.z,
961
+ onRectChange: (rect) => updateRect(w.slug, rect),
962
+ onClose: () => close(w.slug),
963
+ onMinimize: () => minimize(w.slug),
964
+ onMaximizeToggle: () => toggleMaximize(w.slug),
965
+ onFocus: () => focus(w.slug),
966
+ children: /* @__PURE__ */ jsx10(WindowSectionContext.Provider, { value: { slug: w.slug, section: w.section, nonce: w.sectionNonce }, children: w.content })
967
+ },
968
+ w.slug
969
+ )) });
970
+ }
971
+ WindowManagerProvider.displayName = "WindowManagerProvider";
972
+ WindowManager.displayName = "WindowManager";
973
+
974
+ // src/components/desktop/Launchpad.tsx
975
+ import * as React6 from "react";
976
+ import { Search as Search2, Pin, PinOff as PinOff2, MonitorUp, MonitorX } from "lucide-react";
977
+ import { DynamicIcon as DynamicIcon5 } from "@togo-framework/ui-core";
978
+ import {
979
+ ContextMenu as ContextMenu4,
980
+ ContextMenuTrigger as ContextMenuTrigger4,
981
+ ContextMenuContent as ContextMenuContent4,
982
+ ContextMenuItem as ContextMenuItem4
983
+ } from "@togo-framework/ui-core";
984
+ import { cn as cn7 } from "@togo-framework/ui-core";
985
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
986
+ function Launchpad({
987
+ open,
988
+ onOpenChange,
989
+ apps,
990
+ pinned = [],
991
+ hidden = [],
992
+ onLaunch,
993
+ onPin,
994
+ onUnpin,
995
+ onAddToDesktop,
996
+ onRemoveFromDesktop
997
+ }) {
998
+ const [mounted, setMounted] = React6.useState(open);
999
+ const [shown, setShown] = React6.useState(false);
1000
+ const [query, setQuery] = React6.useState("");
1001
+ const pinnedSet = React6.useMemo(() => new Set(pinned), [pinned]);
1002
+ const hiddenSet = React6.useMemo(() => new Set(hidden), [hidden]);
1003
+ React6.useEffect(() => {
1004
+ if (open) {
1005
+ setMounted(true);
1006
+ const t2 = setTimeout(() => setShown(true), 10);
1007
+ return () => clearTimeout(t2);
1008
+ }
1009
+ setShown(false);
1010
+ const t = setTimeout(() => {
1011
+ setMounted(false);
1012
+ setQuery("");
1013
+ }, 200);
1014
+ return () => clearTimeout(t);
1015
+ }, [open]);
1016
+ React6.useEffect(() => {
1017
+ if (!open) return;
1018
+ const onKey = (e) => {
1019
+ if (e.key === "Escape") onOpenChange(false);
1020
+ };
1021
+ window.addEventListener("keydown", onKey);
1022
+ return () => window.removeEventListener("keydown", onKey);
1023
+ }, [open, onOpenChange]);
1024
+ if (!mounted) return null;
1025
+ const q = query.trim().toLowerCase();
1026
+ const list = apps.filter((a) => a.enabled && (!q || a.name.toLowerCase().includes(q)));
1027
+ const launch = (slug) => {
1028
+ onLaunch(slug);
1029
+ onOpenChange(false);
1030
+ };
1031
+ return /* @__PURE__ */ jsxs10(
1032
+ "div",
1033
+ {
1034
+ className: cn7(
1035
+ "fixed inset-0 z-[60] flex flex-col items-center bg-black/40 backdrop-blur-2xl transition-opacity duration-200",
1036
+ shown ? "opacity-100" : "opacity-0"
1037
+ ),
1038
+ onClick: (e) => {
1039
+ if (e.target === e.currentTarget) onOpenChange(false);
1040
+ },
1041
+ children: [
1042
+ /* @__PURE__ */ jsx11("div", { className: "mt-14 mb-8 w-full max-w-sm px-4", children: /* @__PURE__ */ jsxs10("div", { className: "relative", children: [
1043
+ /* @__PURE__ */ jsx11(Search2, { className: "pointer-events-none absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/70" }),
1044
+ /* @__PURE__ */ jsx11(
1045
+ "input",
1046
+ {
1047
+ autoFocus: true,
1048
+ value: query,
1049
+ onChange: (e) => setQuery(e.target.value),
1050
+ placeholder: "Search",
1051
+ className: "w-full rounded-xl border border-white/20 bg-white/10 py-2 ps-9 pe-3 text-center text-sm text-white placeholder:text-white/60 outline-none backdrop-blur-md focus:border-white/40"
1052
+ }
1053
+ )
1054
+ ] }) }),
1055
+ /* @__PURE__ */ jsxs10(
1056
+ "div",
1057
+ {
1058
+ className: cn7(
1059
+ "grid w-full max-w-5xl grid-cols-3 gap-x-4 gap-y-8 overflow-y-auto px-6 pb-24 transition-transform duration-200 ease-out sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-7",
1060
+ shown ? "scale-100" : "scale-90"
1061
+ ),
1062
+ children: [
1063
+ list.map((app) => {
1064
+ const isPinned = pinnedSet.has(app.slug);
1065
+ const onDesktop = !hiddenSet.has(app.slug);
1066
+ return /* @__PURE__ */ jsxs10(ContextMenu4, { children: [
1067
+ /* @__PURE__ */ jsx11(ContextMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsxs10(
1068
+ "button",
1069
+ {
1070
+ type: "button",
1071
+ onClick: () => launch(app.slug),
1072
+ className: "group flex select-none flex-col items-center gap-2 rounded-2xl p-2 outline-none transition hover:scale-105 focus-visible:scale-105",
1073
+ children: [
1074
+ /* @__PURE__ */ jsx11(
1075
+ "span",
1076
+ {
1077
+ className: "flex h-16 w-16 items-center justify-center rounded-[22px] text-white shadow-lg ring-1 ring-inset ring-white/25",
1078
+ style: { backgroundImage: `linear-gradient(160deg, ${app.color || "#64748b"}, ${app.color || "#64748b"}bb)` },
1079
+ children: /* @__PURE__ */ jsx11("span", { className: "drop-shadow-[0_1px_2px_rgba(0,0,0,0.4)]", children: /* @__PURE__ */ jsx11(DynamicIcon5, { name: app.icon, size: 32 }) })
1080
+ }
1081
+ ),
1082
+ /* @__PURE__ */ jsx11("span", { className: "line-clamp-1 text-xs font-medium text-white [text-shadow:0_1px_3px_rgba(0,0,0,0.7)]", children: app.name })
1083
+ ]
1084
+ }
1085
+ ) }),
1086
+ /* @__PURE__ */ jsxs10(ContextMenuContent4, { className: "w-52", children: [
1087
+ isPinned ? onUnpin && /* @__PURE__ */ jsxs10(ContextMenuItem4, { onClick: () => onUnpin(app.slug), children: [
1088
+ /* @__PURE__ */ jsx11(PinOff2, { className: "me-2 h-4 w-4" }),
1089
+ " Remove from Dock"
1090
+ ] }) : onPin && /* @__PURE__ */ jsxs10(ContextMenuItem4, { onClick: () => onPin(app.slug), children: [
1091
+ /* @__PURE__ */ jsx11(Pin, { className: "me-2 h-4 w-4" }),
1092
+ " Keep in Dock"
1093
+ ] }),
1094
+ onDesktop ? onRemoveFromDesktop && /* @__PURE__ */ jsxs10(ContextMenuItem4, { onClick: () => onRemoveFromDesktop(app.slug), children: [
1095
+ /* @__PURE__ */ jsx11(MonitorX, { className: "me-2 h-4 w-4" }),
1096
+ " Remove from Desktop"
1097
+ ] }) : onAddToDesktop && /* @__PURE__ */ jsxs10(ContextMenuItem4, { onClick: () => onAddToDesktop(app.slug), children: [
1098
+ /* @__PURE__ */ jsx11(MonitorUp, { className: "me-2 h-4 w-4" }),
1099
+ " Add to Desktop"
1100
+ ] })
1101
+ ] })
1102
+ ] }, app.slug);
1103
+ }),
1104
+ list.length === 0 && /* @__PURE__ */ jsxs10("p", { className: "col-span-full py-16 text-center text-sm text-white/70", children: [
1105
+ "No apps match \u201C",
1106
+ query,
1107
+ "\u201D."
1108
+ ] })
1109
+ ]
1110
+ }
1111
+ )
1112
+ ]
1113
+ }
1114
+ );
1115
+ }
1116
+ Launchpad.displayName = "Launchpad";
1117
+
1118
+ // src/components/desktop/NotificationCenter.tsx
1119
+ import { Bell, Check, CheckCheck } from "lucide-react";
1120
+ import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@togo-framework/ui-core";
1121
+ import { Button } from "@togo-framework/ui-core";
1122
+ import { ScrollArea } from "@togo-framework/ui-core";
1123
+ import { formatRelativeTime } from "@togo-framework/ui-core";
1124
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1125
+ function NotificationCenter({
1126
+ open,
1127
+ onOpenChange,
1128
+ notifications,
1129
+ unreadCount = 0,
1130
+ onMarkRead,
1131
+ onMarkAllRead,
1132
+ onNavigate,
1133
+ header
1134
+ }) {
1135
+ return /* @__PURE__ */ jsx12(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs11(SheetContent, { side: "right", className: "w-full gap-0 border-white/10 bg-background/95 p-0 backdrop-blur-xl sm:max-w-sm", children: [
1136
+ /* @__PURE__ */ jsxs11(SheetHeader, { className: "flex flex-row items-center justify-between gap-2 border-b border-border px-4 py-3", children: [
1137
+ /* @__PURE__ */ jsxs11(SheetTitle, { className: "flex items-center gap-2 text-base", children: [
1138
+ /* @__PURE__ */ jsx12(Bell, { className: "h-4 w-4" }),
1139
+ "Notifications",
1140
+ unreadCount > 0 && /* @__PURE__ */ jsx12("span", { className: "rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground", children: unreadCount })
1141
+ ] }),
1142
+ notifications.length > 0 && onMarkAllRead && /* @__PURE__ */ jsxs11(Button, { variant: "ghost", size: "sm", className: "h-7 gap-1 text-xs", onClick: onMarkAllRead, children: [
1143
+ /* @__PURE__ */ jsx12(CheckCheck, { className: "h-3.5 w-3.5" }),
1144
+ "Mark all read"
1145
+ ] })
1146
+ ] }),
1147
+ header && /* @__PURE__ */ jsx12("div", { className: "border-b border-border p-4", children: header }),
1148
+ /* @__PURE__ */ jsx12(ScrollArea, { className: "h-[calc(100vh-8rem)]", children: notifications.length === 0 ? /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-center justify-center gap-2 py-20 text-center text-muted-foreground", children: [
1149
+ /* @__PURE__ */ jsx12(Bell, { className: "h-8 w-8 opacity-40" }),
1150
+ /* @__PURE__ */ jsx12("p", { className: "text-sm", children: "You're all caught up" })
1151
+ ] }) : /* @__PURE__ */ jsx12("ul", { className: "divide-y divide-border", children: notifications.map((n) => /* @__PURE__ */ jsx12(
1152
+ "li",
1153
+ {
1154
+ className: cnRow(n.read),
1155
+ onClick: () => {
1156
+ if (!n.read) onMarkRead?.(n.id);
1157
+ if (n.action_url) onNavigate?.(n.action_url);
1158
+ },
1159
+ children: /* @__PURE__ */ jsxs11("div", { className: "flex items-start gap-3", children: [
1160
+ !n.read && /* @__PURE__ */ jsx12("span", { className: "mt-1.5 h-2 w-2 shrink-0 rounded-full bg-primary", "aria-hidden": "true" }),
1161
+ /* @__PURE__ */ jsxs11("div", { className: n.read ? "ps-5" : "", children: [
1162
+ /* @__PURE__ */ jsx12("p", { className: "text-sm font-medium text-foreground", children: n.title }),
1163
+ n.body && /* @__PURE__ */ jsx12("p", { className: "mt-0.5 text-xs text-muted-foreground", children: n.body }),
1164
+ /* @__PURE__ */ jsx12("p", { className: "mt-1 text-[11px] text-muted-foreground/70", children: formatRelativeTime(n.created_at) })
1165
+ ] }),
1166
+ !n.read && onMarkRead && /* @__PURE__ */ jsx12(
1167
+ "button",
1168
+ {
1169
+ type: "button",
1170
+ onClick: (e) => {
1171
+ e.stopPropagation();
1172
+ onMarkRead(n.id);
1173
+ },
1174
+ className: "ms-auto text-muted-foreground hover:text-foreground",
1175
+ title: "Mark read",
1176
+ children: /* @__PURE__ */ jsx12(Check, { className: "h-4 w-4" })
1177
+ }
1178
+ )
1179
+ ] })
1180
+ },
1181
+ n.id
1182
+ )) }) })
1183
+ ] }) });
1184
+ }
1185
+ function cnRow(read) {
1186
+ return [
1187
+ "cursor-pointer px-4 py-3 transition-colors hover:bg-accent/50",
1188
+ read ? "opacity-70" : ""
1189
+ ].filter(Boolean).join(" ");
1190
+ }
1191
+ NotificationCenter.displayName = "NotificationCenter";
1192
+
1193
+ // src/components/desktop/DesktopShell.tsx
1194
+ import { cn as cn8 } from "@togo-framework/ui-core";
1195
+ import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1196
+ function DesktopShell({
1197
+ prefs,
1198
+ apps,
1199
+ renderApp,
1200
+ topBar,
1201
+ notifications = [],
1202
+ unreadCount = 0,
1203
+ onMarkRead,
1204
+ onMarkAllRead,
1205
+ onNavigate,
1206
+ onContextAction,
1207
+ onTrash,
1208
+ onReady,
1209
+ iconPositions,
1210
+ onIconMove,
1211
+ desktopHidden,
1212
+ onRemoveDesktopIcon,
1213
+ onAddDesktopIcon,
1214
+ onPinDock,
1215
+ onUnpinDock,
1216
+ className
1217
+ }) {
1218
+ const [spotlightOpen, setSpotlightOpen] = React7.useState(false);
1219
+ const [launchpadOpen, setLaunchpadOpen] = React7.useState(false);
1220
+ const [notifOpen, setNotifOpen] = React7.useState(false);
1221
+ return /* @__PURE__ */ jsx13(WindowManagerProvider, { children: /* @__PURE__ */ jsx13(
1222
+ "div",
1223
+ {
1224
+ className: cn8("fixed inset-0 overflow-hidden", className),
1225
+ style: { background: wallpaperCss(prefs.wallpaper) },
1226
+ children: /* @__PURE__ */ jsx13(
1227
+ DesktopContent,
1228
+ {
1229
+ apps,
1230
+ pinned: prefs.dock_pinned,
1231
+ renderApp,
1232
+ topBar,
1233
+ notifications,
1234
+ unreadCount,
1235
+ onMarkRead,
1236
+ onMarkAllRead,
1237
+ onNavigate,
1238
+ onContextAction,
1239
+ onTrash,
1240
+ onReady,
1241
+ iconPositions,
1242
+ onIconMove,
1243
+ desktopHidden,
1244
+ onRemoveDesktopIcon,
1245
+ onAddDesktopIcon,
1246
+ onPinDock,
1247
+ onUnpinDock,
1248
+ spotlightOpen,
1249
+ setSpotlightOpen,
1250
+ launchpadOpen,
1251
+ setLaunchpadOpen,
1252
+ notifOpen,
1253
+ setNotifOpen
1254
+ }
1255
+ )
1256
+ }
1257
+ ) });
1258
+ }
1259
+ function DesktopContent({
1260
+ apps,
1261
+ pinned,
1262
+ renderApp,
1263
+ topBar,
1264
+ notifications,
1265
+ unreadCount,
1266
+ onMarkRead,
1267
+ onMarkAllRead,
1268
+ onNavigate,
1269
+ onContextAction,
1270
+ onTrash,
1271
+ onReady,
1272
+ iconPositions,
1273
+ onIconMove,
1274
+ desktopHidden,
1275
+ onRemoveDesktopIcon,
1276
+ onAddDesktopIcon,
1277
+ onPinDock,
1278
+ onUnpinDock,
1279
+ spotlightOpen,
1280
+ setSpotlightOpen,
1281
+ launchpadOpen,
1282
+ setLaunchpadOpen,
1283
+ notifOpen,
1284
+ setNotifOpen
1285
+ }) {
1286
+ const wm = useWindowManager();
1287
+ const bySlug = React7.useMemo(() => new Map(apps.map((a) => [a.slug, a])), [apps]);
1288
+ const launch = React7.useCallback(
1289
+ (slug, section) => {
1290
+ const app = bySlug.get(slug);
1291
+ if (!app) return;
1292
+ if (wm.isOpen(slug)) {
1293
+ wm.restore(slug);
1294
+ if (section) wm.setSection(slug, section);
1295
+ return;
1296
+ }
1297
+ wm.open(slug, {
1298
+ title: app.name,
1299
+ icon: app.icon,
1300
+ resizable: app.window?.resizable ?? true,
1301
+ width: app.window?.width,
1302
+ height: app.window?.height,
1303
+ content: renderApp(app),
1304
+ section
1305
+ });
1306
+ },
1307
+ [bySlug, wm, renderApp]
1308
+ );
1309
+ const openWindow = React7.useCallback(
1310
+ (opts) => {
1311
+ if (wm.isOpen(opts.slug)) {
1312
+ wm.restore(opts.slug);
1313
+ if (opts.section) wm.setSection(opts.slug, opts.section);
1314
+ return;
1315
+ }
1316
+ wm.open(opts.slug, { title: opts.title, icon: opts.icon, width: opts.width, height: opts.height, content: opts.content, section: opts.section });
1317
+ },
1318
+ [wm]
1319
+ );
1320
+ const onReadyRef = React7.useRef(onReady);
1321
+ onReadyRef.current = onReady;
1322
+ React7.useEffect(() => {
1323
+ onReadyRef.current?.({ open: launch, openWindow });
1324
+ }, [launch, openWindow]);
1325
+ const openSlugs = wm.windows.map((w) => w.slug);
1326
+ const dockSlugs = pinned.length > 0 ? pinned : apps.map((a) => a.slug);
1327
+ return /* @__PURE__ */ jsxs12(Fragment6, { children: [
1328
+ /* @__PURE__ */ jsx13(
1329
+ TopBar,
1330
+ {
1331
+ ...topBar,
1332
+ notifications,
1333
+ unreadCount,
1334
+ onMarkRead,
1335
+ onMarkAllRead,
1336
+ onNavigate,
1337
+ onOpenSpotlight: () => setSpotlightOpen(true),
1338
+ onOpenNotifications: () => setNotifOpen(true)
1339
+ }
1340
+ ),
1341
+ /* @__PURE__ */ jsx13(DesktopContextMenu, { onAction: (a) => onContextAction?.(a), className: "absolute inset-0 top-9", children: /* @__PURE__ */ jsx13("div", { className: "relative h-full w-full", children: /* @__PURE__ */ jsx13(
1342
+ DesktopIconGrid,
1343
+ {
1344
+ apps,
1345
+ onOpen: launch,
1346
+ positions: iconPositions,
1347
+ onMove: onIconMove,
1348
+ hidden: desktopHidden,
1349
+ onRemove: onRemoveDesktopIcon
1350
+ }
1351
+ ) }) }),
1352
+ /* @__PURE__ */ jsx13(WindowManager, {}),
1353
+ /* @__PURE__ */ jsx13(
1354
+ Dock,
1355
+ {
1356
+ apps,
1357
+ pinned: dockSlugs,
1358
+ openSlugs,
1359
+ onLaunch: launch,
1360
+ onCloseApp: (slug) => wm.close(slug),
1361
+ onUnpin: onUnpinDock,
1362
+ onLaunchpad: () => setLaunchpadOpen(true),
1363
+ onTrash
1364
+ }
1365
+ ),
1366
+ /* @__PURE__ */ jsx13(
1367
+ Launchpad,
1368
+ {
1369
+ open: launchpadOpen,
1370
+ onOpenChange: setLaunchpadOpen,
1371
+ apps,
1372
+ pinned: dockSlugs,
1373
+ hidden: desktopHidden,
1374
+ onLaunch: launch,
1375
+ onPin: onPinDock,
1376
+ onUnpin: onUnpinDock,
1377
+ onAddToDesktop: onAddDesktopIcon,
1378
+ onRemoveFromDesktop: onRemoveDesktopIcon
1379
+ }
1380
+ ),
1381
+ /* @__PURE__ */ jsx13(Spotlight, { open: spotlightOpen, onOpenChange: setSpotlightOpen, apps, onLaunch: launch }),
1382
+ /* @__PURE__ */ jsx13(
1383
+ NotificationCenter,
1384
+ {
1385
+ open: notifOpen,
1386
+ onOpenChange: setNotifOpen,
1387
+ notifications,
1388
+ unreadCount,
1389
+ onMarkRead,
1390
+ onMarkAllRead,
1391
+ onNavigate
1392
+ }
1393
+ )
1394
+ ] });
1395
+ }
1396
+ DesktopShell.displayName = "DesktopShell";
1397
+
1398
+ // src/components/desktop/NotificationBell.tsx
1399
+ import * as React8 from "react";
1400
+ import { Bell as Bell2, Check as Check2 } from "lucide-react";
1401
+ import { Button as Button2 } from "@togo-framework/ui-core";
1402
+ import { Badge } from "@togo-framework/ui-core";
1403
+ import { Popover, PopoverContent, PopoverTrigger } from "@togo-framework/ui-core";
1404
+ import { ScrollArea as ScrollArea2 } from "@togo-framework/ui-core";
1405
+ import { cn as cn9 } from "@togo-framework/ui-core";
1406
+ import { formatRelativeTime as formatRelativeTime2 } from "@togo-framework/ui-core";
1407
+ import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
1408
+ function NotificationBell({
1409
+ notifications,
1410
+ unreadCount,
1411
+ onMarkRead,
1412
+ onMarkAllRead,
1413
+ onFetch,
1414
+ onNavigate,
1415
+ className
1416
+ }) {
1417
+ const [open, setOpen] = React8.useState(false);
1418
+ return /* @__PURE__ */ jsxs13(
1419
+ Popover,
1420
+ {
1421
+ open,
1422
+ onOpenChange: (next) => {
1423
+ setOpen(next);
1424
+ if (next) onFetch?.();
1425
+ },
1426
+ children: [
1427
+ /* @__PURE__ */ jsx14(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs13(Button2, { variant: "ghost", size: "icon", className: cn9("relative h-8 w-8", className), "aria-label": "Notifications", children: [
1428
+ /* @__PURE__ */ jsx14(Bell2, { className: "h-4 w-4" }),
1429
+ unreadCount > 0 && /* @__PURE__ */ jsx14(
1430
+ Badge,
1431
+ {
1432
+ variant: "destructive",
1433
+ className: "absolute -right-1 -top-1 h-4 min-w-4 justify-center px-1 text-[9px] leading-none",
1434
+ children: unreadCount > 99 ? "99+" : unreadCount
1435
+ }
1436
+ )
1437
+ ] }) }),
1438
+ /* @__PURE__ */ jsxs13(PopoverContent, { align: "end", className: "w-80 p-0", children: [
1439
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between border-b border-border p-3", children: [
1440
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground", children: "Notifications" }),
1441
+ unreadCount > 0 && /* @__PURE__ */ jsxs13(
1442
+ "button",
1443
+ {
1444
+ type: "button",
1445
+ onClick: onMarkAllRead,
1446
+ className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
1447
+ children: [
1448
+ /* @__PURE__ */ jsx14(Check2, { className: "h-3 w-3" }),
1449
+ " Mark all read"
1450
+ ]
1451
+ }
1452
+ )
1453
+ ] }),
1454
+ /* @__PURE__ */ jsx14(ScrollArea2, { className: "max-h-80", children: /* @__PURE__ */ jsx14("div", { className: "p-1.5", children: notifications.length === 0 ? /* @__PURE__ */ jsx14("p", { className: "py-6 text-center text-xs text-muted-foreground", children: "No notifications" }) : notifications.map((n) => /* @__PURE__ */ jsxs13(
1455
+ "button",
1456
+ {
1457
+ type: "button",
1458
+ onClick: () => {
1459
+ if (!n.read) onMarkRead(n.id);
1460
+ if (n.action_url) onNavigate?.(n.action_url);
1461
+ },
1462
+ className: cn9(
1463
+ "flex w-full flex-col gap-0.5 rounded-md px-2.5 py-2 text-start hover:bg-muted",
1464
+ !n.read && "bg-primary/5"
1465
+ ),
1466
+ children: [
1467
+ /* @__PURE__ */ jsxs13("span", { className: "flex items-center gap-2", children: [
1468
+ !n.read && /* @__PURE__ */ jsx14("span", { className: "h-1.5 w-1.5 shrink-0 rounded-full bg-primary" }),
1469
+ /* @__PURE__ */ jsx14("span", { className: "text-xs font-medium text-foreground", children: n.title })
1470
+ ] }),
1471
+ n.body && /* @__PURE__ */ jsx14("span", { className: "line-clamp-2 text-xs text-muted-foreground", children: n.body }),
1472
+ /* @__PURE__ */ jsx14("span", { className: "text-[10px] text-muted-foreground/70", children: formatRelativeTime2(n.created_at) })
1473
+ ]
1474
+ },
1475
+ n.id
1476
+ )) }) })
1477
+ ] })
1478
+ ]
1479
+ }
1480
+ );
1481
+ }
1482
+ NotificationBell.displayName = "NotificationBell";
1483
+
1484
+ // src/components/desktop/OSLoginScreen.tsx
1485
+ import * as React9 from "react";
1486
+ import { ShieldCheck, Loader2, ArrowRight, Power as Power2, RotateCcw as RotateCcw2, Moon as Moon2 } from "lucide-react";
1487
+ import { wallpaperCss as wallpaperCss2 } from "@togo-framework/ui-core";
1488
+ import { Input } from "@togo-framework/ui-core";
1489
+ import { Label } from "@togo-framework/ui-core";
1490
+ import { Button as Button3 } from "@togo-framework/ui-core";
1491
+ import {
1492
+ PasswordInput,
1493
+ LockScreen,
1494
+ PasswordLockScreen
1495
+ } from "@togo-framework/ui-auth";
1496
+ import { cn as cn10 } from "@togo-framework/ui-core";
1497
+ import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
1498
+ function brandName(brand, ar) {
1499
+ const n = brand?.name;
1500
+ if (!n) return "togo OS";
1501
+ if (typeof n === "string") return n;
1502
+ return (ar ? n.ar || n.en : n.en || n.ar) ?? "togo OS";
1503
+ }
1504
+ function greeting(hour, ar) {
1505
+ if (hour < 12) return ar ? "\u0635\u0628\u0627\u062D \u0627\u0644\u062E\u064A\u0631" : "Good morning";
1506
+ if (hour < 18) return ar ? "\u0645\u0633\u0627\u0621 \u0627\u0644\u062E\u064A\u0631" : "Good afternoon";
1507
+ return ar ? "\u0645\u0633\u0627\u0621 \u0627\u0644\u062E\u064A\u0631" : "Good evening";
1508
+ }
1509
+ function LiveClock({ ar }) {
1510
+ const [now, setNow] = React9.useState(null);
1511
+ React9.useEffect(() => {
1512
+ setNow(/* @__PURE__ */ new Date());
1513
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), 1e3);
1514
+ return () => clearInterval(id);
1515
+ }, []);
1516
+ if (!now) return /* @__PURE__ */ jsx15("div", { className: "h-[136px]", "aria-hidden": "true" });
1517
+ const locale = ar ? "ar" : "en-US";
1518
+ const hh = now.getHours();
1519
+ const h12 = (hh + 11) % 12 + 1;
1520
+ const mm = now.getMinutes();
1521
+ const ss = now.getSeconds();
1522
+ const ampm = hh < 12 ? "AM" : "PM";
1523
+ const pad = (n) => String(n).padStart(2, "0");
1524
+ const blink = ss % 2 === 0;
1525
+ const dateStr = now.toLocaleDateString(locale, { weekday: "long", month: "long", day: "numeric" });
1526
+ return /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-center text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.55)]", children: [
1527
+ /* @__PURE__ */ jsx15("div", { className: "mb-1 text-sm font-medium uppercase tracking-[0.2em] text-white/70", children: greeting(hh, ar) }),
1528
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-start font-semibold tabular-nums leading-none tracking-tight", "aria-live": "off", children: [
1529
+ /* @__PURE__ */ jsx15("span", { className: "text-[5.5rem]", children: ar ? pad(hh) : h12 }),
1530
+ /* @__PURE__ */ jsx15("span", { className: cn10("text-[5.5rem] transition-opacity duration-200", blink ? "opacity-100" : "opacity-25"), children: ":" }),
1531
+ /* @__PURE__ */ jsx15("span", { className: "text-[5.5rem]", children: pad(mm) }),
1532
+ /* @__PURE__ */ jsxs14("span", { className: "ms-2 mt-3 flex flex-col items-start gap-1", children: [
1533
+ /* @__PURE__ */ jsx15("span", { className: "rounded bg-white/15 px-1.5 py-0.5 text-lg tabular-nums", children: pad(ss) }),
1534
+ !ar && /* @__PURE__ */ jsx15("span", { className: "ps-0.5 text-xs font-medium tracking-widest text-white/70", children: ampm })
1535
+ ] })
1536
+ ] }),
1537
+ /* @__PURE__ */ jsx15("div", { className: "mt-2 text-lg font-medium capitalize text-white/85", children: dateStr })
1538
+ ] });
1539
+ }
1540
+ function OSLoginScreen({
1541
+ wallpaper = "aurora",
1542
+ backgroundImage,
1543
+ language = "en",
1544
+ brand,
1545
+ className,
1546
+ authClient,
1547
+ onSuccess,
1548
+ onRegister,
1549
+ lockedUser,
1550
+ onUnlockPin,
1551
+ onUnlockPassword,
1552
+ onSignOut,
1553
+ onForceLogout,
1554
+ hasTOTP
1555
+ }) {
1556
+ const ar = language === "ar";
1557
+ const dir = ar ? "rtl" : "ltr";
1558
+ const gradient = wallpaperCss2(wallpaper);
1559
+ const background = backgroundImage ? `url("${backgroundImage}") center / cover no-repeat, ${gradient}` : gradient;
1560
+ const [email, setEmail] = React9.useState("");
1561
+ const [password, setPassword] = React9.useState("");
1562
+ const [error, setError] = React9.useState("");
1563
+ const [busy, setBusy] = React9.useState(false);
1564
+ const [devBusy, setDevBusy] = React9.useState(false);
1565
+ if (lockedUser) {
1566
+ return /* @__PURE__ */ jsxs14("div", { className: cn10("fixed inset-0 flex items-center justify-center", className), style: { background }, dir, children: [
1567
+ /* @__PURE__ */ jsx15("div", { className: "absolute inset-0 bg-black/30 backdrop-blur-sm", "aria-hidden": "true" }),
1568
+ /* @__PURE__ */ jsx15("div", { className: "relative z-10", children: lockedUser.usePassword && onUnlockPassword ? /* @__PURE__ */ jsx15(
1569
+ PasswordLockScreen,
1570
+ {
1571
+ user: lockedUser,
1572
+ onUnlock: onUnlockPassword,
1573
+ onSignOut: onSignOut ?? (() => {
1574
+ }),
1575
+ onForceLogout,
1576
+ language,
1577
+ hasTOTP
1578
+ }
1579
+ ) : /* @__PURE__ */ jsx15(
1580
+ LockScreen,
1581
+ {
1582
+ user: lockedUser,
1583
+ onUnlock: onUnlockPin ?? (async () => {
1584
+ }),
1585
+ onSignOut: onSignOut ?? (() => {
1586
+ }),
1587
+ language
1588
+ }
1589
+ ) })
1590
+ ] });
1591
+ }
1592
+ const crest = brand && "icon" in brand && brand.icon !== void 0 ? brand.icon : /* @__PURE__ */ jsx15(ShieldCheck, { className: "h-8 w-8", strokeWidth: 1.75 });
1593
+ async function submit(e) {
1594
+ e.preventDefault();
1595
+ if (!authClient) return;
1596
+ setBusy(true);
1597
+ setError("");
1598
+ try {
1599
+ const res = await authClient.login(email.trim(), password);
1600
+ if (res.challenge === "none") onSuccess?.();
1601
+ else setError(ar ? "\u0647\u0630\u0627 \u0627\u0644\u062D\u0633\u0627\u0628 \u064A\u062A\u0637\u0644\u0628 \u062A\u062D\u0642\u0642\u0627\u064B \u0625\u0636\u0627\u0641\u064A\u0627\u064B." : "This account requires an extra verification step.");
1602
+ } catch (err) {
1603
+ const msg = err?.message ?? "";
1604
+ setError(msg || (ar ? "\u0627\u0644\u0628\u0631\u064A\u062F \u0627\u0644\u0625\u0644\u0643\u062A\u0631\u0648\u0646\u064A \u0623\u0648 \u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631 \u063A\u064A\u0631 \u0635\u062D\u064A\u062D\u0629." : "Incorrect email or password."));
1605
+ } finally {
1606
+ setBusy(false);
1607
+ }
1608
+ }
1609
+ async function devLogin() {
1610
+ if (!authClient?.devLogin) return;
1611
+ setDevBusy(true);
1612
+ setError("");
1613
+ try {
1614
+ await authClient.devLogin();
1615
+ onSuccess?.();
1616
+ } catch {
1617
+ setError(ar ? "\u062A\u0639\u0630\u0651\u0631 \u0627\u0644\u062F\u062E\u0648\u0644 \u0643\u0645\u0637\u0648\u0651\u0631." : "Dev login failed.");
1618
+ } finally {
1619
+ setDevBusy(false);
1620
+ }
1621
+ }
1622
+ return /* @__PURE__ */ jsxs14(
1623
+ "div",
1624
+ {
1625
+ className: cn10("fixed inset-0 flex flex-col items-center justify-center overflow-hidden", className),
1626
+ style: { background },
1627
+ dir,
1628
+ children: [
1629
+ /* @__PURE__ */ jsx15("div", { className: "absolute inset-0 bg-gradient-to-b from-black/30 via-black/15 to-black/60", "aria-hidden": "true" }),
1630
+ /* @__PURE__ */ jsx15(
1631
+ "div",
1632
+ {
1633
+ className: "absolute inset-0",
1634
+ style: { background: "radial-gradient(120% 80% at 50% 30%, transparent 40%, rgba(0,0,0,0.45) 100%)" },
1635
+ "aria-hidden": "true"
1636
+ }
1637
+ ),
1638
+ /* @__PURE__ */ jsxs14("div", { className: "relative z-10 flex w-full max-w-sm flex-col items-center gap-8 px-6", children: [
1639
+ /* @__PURE__ */ jsx15(LiveClock, { ar }),
1640
+ /* @__PURE__ */ jsxs14("div", { className: "w-full rounded-3xl border border-white/20 bg-white/10 p-7 shadow-[0_20px_60px_-15px_rgba(0,0,0,0.6)] backdrop-blur-2xl", children: [
1641
+ /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-center gap-3 text-center text-white", children: [
1642
+ /* @__PURE__ */ jsx15("div", { className: "flex h-[68px] w-[68px] items-center justify-center rounded-full border border-white/30 bg-gradient-to-br from-white/25 to-white/5 text-white shadow-inner ring-1 ring-white/10", children: crest }),
1643
+ /* @__PURE__ */ jsxs14("div", { children: [
1644
+ /* @__PURE__ */ jsx15("div", { className: "text-lg font-semibold", children: brandName(brand, ar) }),
1645
+ brand?.tagline && /* @__PURE__ */ jsx15("div", { className: "text-sm text-white/70", children: ar ? brand.tagline.ar : brand.tagline.en })
1646
+ ] })
1647
+ ] }),
1648
+ /* @__PURE__ */ jsxs14("form", { onSubmit: submit, className: "mt-6 flex flex-col gap-4", children: [
1649
+ error && /* @__PURE__ */ jsx15("div", { role: "alert", className: "rounded-lg border border-red-300/40 bg-red-500/20 px-3 py-2 text-sm text-white", children: error }),
1650
+ /* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-1.5", children: [
1651
+ /* @__PURE__ */ jsx15(Label, { htmlFor: "os-login-email", className: "text-sm font-medium text-white/90", children: ar ? "\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645 \u0623\u0648 \u0627\u0644\u0628\u0631\u064A\u062F" : "Username or email" }),
1652
+ /* @__PURE__ */ jsx15(
1653
+ Input,
1654
+ {
1655
+ id: "os-login-email",
1656
+ type: "email",
1657
+ dir: "ltr",
1658
+ autoComplete: "username",
1659
+ placeholder: "you@example.com",
1660
+ value: email,
1661
+ onChange: (e) => setEmail(e.target.value),
1662
+ disabled: busy,
1663
+ required: true,
1664
+ autoFocus: true,
1665
+ className: "border-white/25 bg-white/10 text-white placeholder:text-white/50 focus-visible:ring-white/40"
1666
+ }
1667
+ )
1668
+ ] }),
1669
+ /* @__PURE__ */ jsxs14("div", { className: "flex flex-col gap-1.5", children: [
1670
+ /* @__PURE__ */ jsx15(Label, { htmlFor: "os-login-password", className: "text-sm font-medium text-white/90", children: ar ? "\u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631" : "Password" }),
1671
+ /* @__PURE__ */ jsx15(
1672
+ PasswordInput,
1673
+ {
1674
+ id: "os-login-password",
1675
+ language,
1676
+ autoComplete: "current-password",
1677
+ placeholder: ar ? "\u0623\u062F\u062E\u0644 \u0643\u0644\u0645\u0629 \u0627\u0644\u0645\u0631\u0648\u0631" : "Enter your password",
1678
+ value: password,
1679
+ onChange: (e) => setPassword(e.target.value),
1680
+ disabled: busy,
1681
+ required: true,
1682
+ className: "border-white/25 bg-white/10 text-white placeholder:text-white/50 focus-visible:ring-white/40"
1683
+ }
1684
+ )
1685
+ ] }),
1686
+ /* @__PURE__ */ jsxs14(
1687
+ Button3,
1688
+ {
1689
+ type: "submit",
1690
+ disabled: busy,
1691
+ className: "w-full gap-2 bg-white font-semibold text-slate-900 shadow-lg transition hover:bg-white/90",
1692
+ children: [
1693
+ busy ? /* @__PURE__ */ jsx15(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx15(ArrowRight, { className: "h-4 w-4 rtl:rotate-180" }),
1694
+ busy ? ar ? "\u062C\u0627\u0631\u064D \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644\u2026" : "Signing in\u2026" : ar ? "\u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644" : "Sign in"
1695
+ ]
1696
+ }
1697
+ ),
1698
+ authClient?.devLogin && /* @__PURE__ */ jsxs14(
1699
+ Button3,
1700
+ {
1701
+ type: "button",
1702
+ variant: "outline",
1703
+ onClick: devLogin,
1704
+ disabled: devBusy,
1705
+ className: "w-full gap-2 border-white/25 bg-transparent text-white hover:bg-white/10 hover:text-white",
1706
+ children: [
1707
+ devBusy ? /* @__PURE__ */ jsx15(Loader2, { className: "h-4 w-4 animate-spin" }) : null,
1708
+ ar ? "\u0627\u0644\u062F\u062E\u0648\u0644 \u0643\u0645\u0637\u0648\u0651\u0631" : "Continue as developer"
1709
+ ]
1710
+ }
1711
+ )
1712
+ ] }),
1713
+ onRegister && /* @__PURE__ */ jsxs14("p", { className: "mt-5 text-center text-sm text-white/70", children: [
1714
+ ar ? "\u0644\u064A\u0633 \u0644\u062F\u064A\u0643 \u062D\u0633\u0627\u0628\u061F " : "No account? ",
1715
+ /* @__PURE__ */ jsx15("button", { type: "button", onClick: onRegister, className: "font-medium text-white underline-offset-4 hover:underline", children: ar ? "\u0623\u0646\u0634\u0626 \u0648\u0627\u062D\u062F\u0627\u064B" : "Create one" })
1716
+ ] })
1717
+ ] })
1718
+ ] }),
1719
+ /* @__PURE__ */ jsx15("div", { className: "absolute inset-x-0 bottom-7 z-10 flex items-center justify-center gap-5", "aria-hidden": "true", children: [Moon2, RotateCcw2, Power2].map((Icon, i) => /* @__PURE__ */ jsx15(
1720
+ "button",
1721
+ {
1722
+ type: "button",
1723
+ tabIndex: -1,
1724
+ className: "flex h-10 w-10 items-center justify-center rounded-full border border-white/15 bg-white/10 text-white/70 backdrop-blur-md transition hover:bg-white/20 hover:text-white",
1725
+ children: /* @__PURE__ */ jsx15(Icon, { className: "h-4 w-4" })
1726
+ },
1727
+ i
1728
+ )) })
1729
+ ]
1730
+ }
1731
+ );
1732
+ }
1733
+ OSLoginScreen.displayName = "OSLoginScreen";
1734
+
1735
+ // src/hooks/useOSSession.ts
1736
+ import { useCallback as useCallback4, useEffect as useEffect8, useState as useState10 } from "react";
1737
+ var DEFAULT_PREFS = {
1738
+ theme: "dark",
1739
+ accent: "#1FC7DC",
1740
+ wallpaper: "aurora",
1741
+ lock_wallpaper: "monterey",
1742
+ dock_pinned: [],
1743
+ desktop_hidden: [],
1744
+ icon_positions: {}
1745
+ };
1746
+ function useOSSession(basePath = "/api/os/session") {
1747
+ const [prefs, setPrefs] = useState10(DEFAULT_PREFS);
1748
+ const [loading, setLoading] = useState10(true);
1749
+ const [error, setError] = useState10(null);
1750
+ useEffect8(() => {
1751
+ let cancelled = false;
1752
+ (async () => {
1753
+ try {
1754
+ const res = await fetch(basePath, { credentials: "include" });
1755
+ if (!res.ok) throw new Error(`request failed (${res.status})`);
1756
+ const data = await res.json();
1757
+ if (!cancelled) setPrefs({ ...DEFAULT_PREFS, ...data });
1758
+ } catch (e) {
1759
+ if (!cancelled) setError(e instanceof Error ? e.message : "failed to load session");
1760
+ } finally {
1761
+ if (!cancelled) setLoading(false);
1762
+ }
1763
+ })();
1764
+ return () => {
1765
+ cancelled = true;
1766
+ };
1767
+ }, [basePath]);
1768
+ const save = useCallback4(
1769
+ async (next) => {
1770
+ const body = next ?? prefs;
1771
+ const res = await fetch(basePath, {
1772
+ method: "PUT",
1773
+ credentials: "include",
1774
+ headers: { "Content-Type": "application/json" },
1775
+ body: JSON.stringify(body)
1776
+ });
1777
+ if (!res.ok) throw new Error(`save failed (${res.status})`);
1778
+ const data = await res.json().catch(() => body);
1779
+ setPrefs({ ...DEFAULT_PREFS, ...data });
1780
+ },
1781
+ [basePath, prefs]
1782
+ );
1783
+ return { prefs, setPrefs, save, loading, error };
1784
+ }
1785
+
1786
+ // src/hooks/useOSApps.ts
1787
+ import { useCallback as useCallback5, useEffect as useEffect9, useState as useState11 } from "react";
1788
+ function useOSApps(basePath = "/api/os/apps") {
1789
+ const [apps, setApps] = useState11([]);
1790
+ const [loading, setLoading] = useState11(true);
1791
+ const [error, setError] = useState11(null);
1792
+ const refetch = useCallback5(async () => {
1793
+ setLoading(true);
1794
+ try {
1795
+ const res = await fetch(basePath, { credentials: "include" });
1796
+ if (!res.ok) throw new Error(`request failed (${res.status})`);
1797
+ const data = await res.json();
1798
+ setApps(Array.isArray(data) ? data : data.apps ?? []);
1799
+ setError(null);
1800
+ } catch (e) {
1801
+ setError(e instanceof Error ? e.message : "failed to load apps");
1802
+ } finally {
1803
+ setLoading(false);
1804
+ }
1805
+ }, [basePath]);
1806
+ useEffect9(() => {
1807
+ refetch();
1808
+ }, [refetch]);
1809
+ return { apps, loading, error, refetch };
1810
+ }
1811
+ export {
1812
+ DesktopContextMenu,
1813
+ DesktopIcon,
1814
+ DesktopIconGrid,
1815
+ DesktopShell,
1816
+ Dock,
1817
+ Launchpad,
1818
+ NotificationBell,
1819
+ NotificationCenter,
1820
+ OSLoginScreen,
1821
+ Spotlight,
1822
+ SpotlightTrigger,
1823
+ TogoMenu,
1824
+ TopBar,
1825
+ WeatherWidget,
1826
+ Window,
1827
+ WindowManager,
1828
+ WindowManagerProvider,
1829
+ WindowSpinner,
1830
+ clampWindowRect,
1831
+ useOSApps,
1832
+ useOSSession,
1833
+ useWindowManager,
1834
+ useWindowSection
1835
+ };
1836
+ //# sourceMappingURL=index.js.map