@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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 togo-framework
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy...
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ <!-- togo-header -->
2
+ # @togo-framework/ui-desktop
3
+
4
+ OS-like desktop shell on top of the `os` plugin: dock, window manager,
5
+ spotlight, launchpad, notification center, and a lock/login screen. Part of
6
+ the togo UI kit.
7
+
8
+ ```bash
9
+ npm install @togo-framework/ui-desktop
10
+ ```
11
+
12
+ ```tsx
13
+ import { DesktopShell, WindowManagerProvider } from "@togo-framework/ui-desktop";
14
+ ```
15
+
16
+ Requires `@togo-framework/ui-core` and `@togo-framework/ui-auth` (the login
17
+ screen reuses the shared LockScreen/PasswordLockScreen components).
18
+ <!-- togo-sponsors -->
@@ -0,0 +1,467 @@
1
+ import * as React from 'react';
2
+ import { AuthClient, LockScreenUser, PasswordLockScreenUser, UnlockCredentials } from '@togo-framework/ui-auth';
3
+
4
+ interface OSNotification {
5
+ id: string;
6
+ type: string;
7
+ title: string;
8
+ body?: string;
9
+ action_url?: string;
10
+ read: boolean;
11
+ created_at: string;
12
+ }
13
+ interface NotificationBellProps {
14
+ notifications: OSNotification[];
15
+ unreadCount: number;
16
+ onMarkRead: (id: string) => void;
17
+ onMarkAllRead: () => void;
18
+ /** Called when the popover is opened — the consumer fetches fresh data. */
19
+ onFetch?: () => void;
20
+ onNavigate?: (url: string) => void;
21
+ className?: string;
22
+ }
23
+ declare function NotificationBell({ notifications, unreadCount, onMarkRead, onMarkAllRead, onFetch, onNavigate, className, }: NotificationBellProps): React.JSX.Element;
24
+ declare namespace NotificationBell {
25
+ var displayName: string;
26
+ }
27
+
28
+ interface TogoMenuProps {
29
+ /** Logo glyph (defaults to the togo layers mark). */
30
+ logo?: React.ReactNode;
31
+ label?: string;
32
+ onAbout?: () => void;
33
+ onSettings?: () => void;
34
+ onSleep?: () => void;
35
+ onRestart?: () => void;
36
+ onShutDown?: () => void;
37
+ onLogout?: () => void;
38
+ }
39
+ declare function TogoMenu({ logo, label, onAbout, onSettings, onSleep, onRestart, onShutDown, onLogout, }: TogoMenuProps): React.JSX.Element;
40
+ declare namespace TogoMenu {
41
+ var displayName: string;
42
+ }
43
+
44
+ type WeatherCondition = "clear" | "cloudy" | "rain" | "snow" | "storm" | "fog" | "partly";
45
+ interface WeatherData {
46
+ /** Temperature already formatted's numeric value (°). */
47
+ temp: number;
48
+ condition: WeatherCondition;
49
+ location?: string;
50
+ /** Unit label, default "°". */
51
+ unit?: string;
52
+ }
53
+ interface WeatherWidgetProps {
54
+ weather?: WeatherData | null;
55
+ onClick?: () => void;
56
+ className?: string;
57
+ }
58
+ declare function WeatherWidget({ weather, onClick, className }: WeatherWidgetProps): React.JSX.Element | null;
59
+ declare namespace WeatherWidget {
60
+ var displayName: string;
61
+ }
62
+
63
+ interface TopBarProps {
64
+ me: {
65
+ email: string;
66
+ } | null;
67
+ onLogout: () => void;
68
+ onProfile?: () => void;
69
+ menu?: Omit<TogoMenuProps, "onLogout"> & {
70
+ enabled?: boolean;
71
+ };
72
+ notifications?: OSNotification[];
73
+ unreadCount?: number;
74
+ onMarkRead?: (id: string) => void;
75
+ onMarkAllRead?: () => void;
76
+ onFetchNotifications?: () => void;
77
+ onNavigate?: (url: string) => void;
78
+ /** Clicking the clock (and, if provided, the bell) opens the notification center. */
79
+ onOpenNotifications?: () => void;
80
+ onOpenSpotlight?: () => void;
81
+ weather?: WeatherData | null;
82
+ onWeatherClick?: () => void;
83
+ className?: string;
84
+ }
85
+ declare function TopBar({ me, onLogout, onProfile, menu, onOpenNotifications, onOpenSpotlight, weather, onWeatherClick, className, }: TopBarProps): React.JSX.Element;
86
+ declare namespace TopBar {
87
+ var displayName: string;
88
+ }
89
+
90
+ type DesktopContextAction = "change-wallpaper" | "personalize" | "refresh";
91
+ interface DesktopContextMenuProps {
92
+ onAction: (action: DesktopContextAction) => void;
93
+ children: React.ReactNode;
94
+ className?: string;
95
+ }
96
+ declare function DesktopContextMenu({ onAction, children, className }: DesktopContextMenuProps): React.JSX.Element;
97
+ declare namespace DesktopContextMenu {
98
+ var displayName: string;
99
+ }
100
+
101
+ interface OSAppWindow {
102
+ width: number;
103
+ height: number;
104
+ resizable: boolean;
105
+ }
106
+ interface OSApp {
107
+ slug: string;
108
+ name: string;
109
+ icon: string;
110
+ color: string;
111
+ category: string;
112
+ window: OSAppWindow;
113
+ enabled: boolean;
114
+ }
115
+ interface UseOSAppsResult {
116
+ apps: OSApp[];
117
+ loading: boolean;
118
+ error: string | null;
119
+ refetch: () => Promise<void>;
120
+ }
121
+ declare function useOSApps(basePath?: string): UseOSAppsResult;
122
+
123
+ interface DesktopPrefs {
124
+ theme: string;
125
+ accent: string;
126
+ wallpaper: string;
127
+ lock_wallpaper: string;
128
+ dock_pinned: string[];
129
+ desktop_hidden: string[];
130
+ icon_positions: Record<string, {
131
+ x: number;
132
+ y: number;
133
+ }>;
134
+ }
135
+ interface UseOSSessionResult {
136
+ prefs: DesktopPrefs;
137
+ setPrefs: (prefs: DesktopPrefs | ((prev: DesktopPrefs) => DesktopPrefs)) => void;
138
+ /** Persists the current `prefs` (or an explicit override) via PUT. */
139
+ save: (next?: DesktopPrefs) => Promise<void>;
140
+ loading: boolean;
141
+ error: string | null;
142
+ }
143
+ declare function useOSSession(basePath?: string): UseOSSessionResult;
144
+
145
+ interface DesktopShellProps {
146
+ /** Current desktop preferences (wallpaper, dock_pinned, ...) — e.g. from useOSSession(). */
147
+ prefs: DesktopPrefs;
148
+ /** Installed apps — e.g. from useOSApps(). */
149
+ apps: OSApp[];
150
+ /** Renders window content for a given app slug. */
151
+ renderApp: (app: OSApp) => React.ReactNode;
152
+ /** Top-bar props (me / logout / weather / menu callbacks / notifications data). */
153
+ topBar: Omit<TopBarProps, "className" | "onOpenSpotlight" | "onOpenNotifications">;
154
+ /** Notifications for the sideover (same list the bell uses). */
155
+ notifications?: OSNotification[];
156
+ unreadCount?: number;
157
+ onMarkRead?: (id: string) => void;
158
+ onMarkAllRead?: () => void;
159
+ onNavigate?: (url: string) => void;
160
+ onContextAction?: (action: DesktopContextAction) => void;
161
+ /** Opens the Trash (dock). */
162
+ onTrash?: () => void;
163
+ /** Saved desktop-icon positions (DesktopPrefs.icon_positions). */
164
+ iconPositions?: Record<string, {
165
+ x: number;
166
+ y: number;
167
+ }>;
168
+ /** Persist a desktop-icon position after a drag. */
169
+ onIconMove?: (slug: string, pos: {
170
+ x: number;
171
+ y: number;
172
+ }) => void;
173
+ /** Slugs hidden from the desktop (DesktopPrefs.desktop_hidden). */
174
+ desktopHidden?: string[];
175
+ /** Remove an app icon from the desktop. */
176
+ onRemoveDesktopIcon?: (slug: string) => void;
177
+ /** Add an app icon back to the desktop (from Launchpad). */
178
+ onAddDesktopIcon?: (slug: string) => void;
179
+ /** Pin an app to the dock (from Launchpad). */
180
+ onPinDock?: (slug: string) => void;
181
+ /** Unpin an app from the dock (dock/Launchpad context menu). */
182
+ onUnpinDock?: (slug: string) => void;
183
+ /**
184
+ * Called once the shell is mounted, with an imperative API so the host page
185
+ * can open app windows from outside the shell (e.g. a top-bar shortcut).
186
+ */
187
+ onReady?: (api: DesktopApi) => void;
188
+ className?: string;
189
+ }
190
+ /** Imperative handle exposed via DesktopShellProps.onReady. */
191
+ interface DesktopApi {
192
+ /**
193
+ * Open (or restore/focus) a registered app by slug, optionally deep-linking to
194
+ * a section inside it (e.g. open("prefs", "wallpaper") jumps Settings straight
195
+ * to the Wallpaper pane). The app reads the target via useWindowSection().
196
+ */
197
+ open: (slug: string, section?: string) => void;
198
+ /** Open (or restore/focus) an arbitrary window not backed by an installed app. */
199
+ openWindow: (opts: {
200
+ slug: string;
201
+ title: string;
202
+ icon?: string;
203
+ width?: number;
204
+ height?: number;
205
+ content: React.ReactNode;
206
+ section?: string;
207
+ }) => void;
208
+ }
209
+ declare function DesktopShell({ prefs, apps, renderApp, topBar, notifications, unreadCount, onMarkRead, onMarkAllRead, onNavigate, onContextAction, onTrash, onReady, iconPositions, onIconMove, desktopHidden, onRemoveDesktopIcon, onAddDesktopIcon, onPinDock, onUnpinDock, className, }: DesktopShellProps): React.JSX.Element;
210
+ declare namespace DesktopShell {
211
+ var displayName: string;
212
+ }
213
+
214
+ interface DockProps {
215
+ /** All installed apps (used to resolve name/icon/color for pinned slugs). */
216
+ apps: OSApp[];
217
+ /** Slugs to show, in order (pinned, or all installed apps as a fallback). */
218
+ pinned: string[];
219
+ /** Slugs of currently-open windows — shown with an "open" indicator dot. */
220
+ openSlugs?: string[];
221
+ onLaunch: (slug: string) => void;
222
+ /** Right-click → Quit on a running app icon. */
223
+ onCloseApp?: (slug: string) => void;
224
+ /** Right-click → Remove from Dock (unpin). */
225
+ onUnpin?: (slug: string) => void;
226
+ /** Opens the Launchpad / app directory (leading icon). */
227
+ onLaunchpad?: () => void;
228
+ /** Opens the Trash (trailing icon). */
229
+ onTrash?: () => void;
230
+ className?: string;
231
+ }
232
+ declare function Dock({ apps, pinned, openSlugs, onLaunch, onCloseApp, onUnpin, onLaunchpad, onTrash, className }: DockProps): React.JSX.Element;
233
+ declare namespace Dock {
234
+ var displayName: string;
235
+ }
236
+
237
+ interface IconPos {
238
+ x: number;
239
+ y: number;
240
+ }
241
+ interface DesktopIconGridProps {
242
+ apps: OSApp[];
243
+ onOpen: (slug: string) => void;
244
+ /** Saved per-slug positions (DesktopPrefs.icon_positions). */
245
+ positions?: Record<string, IconPos>;
246
+ /** Called when an icon is dragged to a new position. Enables drag mode. */
247
+ onMove?: (slug: string, pos: IconPos) => void;
248
+ /** Slugs hidden from the desktop (DesktopPrefs.desktop_hidden). */
249
+ hidden?: string[];
250
+ /** Right-click → Remove from Desktop. */
251
+ onRemove?: (slug: string) => void;
252
+ className?: string;
253
+ }
254
+ declare function DesktopIconGrid({ apps, onOpen, positions, onMove, hidden, onRemove, className }: DesktopIconGridProps): React.JSX.Element;
255
+ declare namespace DesktopIconGrid {
256
+ var displayName: string;
257
+ }
258
+
259
+ interface DesktopIconProps {
260
+ app: OSApp;
261
+ onOpen: (slug: string) => void;
262
+ selected?: boolean;
263
+ onSelect?: (slug: string) => void;
264
+ /** Open on a single click/tap (mobile-friendly) instead of double-click. */
265
+ tapToOpen?: boolean;
266
+ className?: string;
267
+ style?: React.CSSProperties;
268
+ }
269
+ declare function DesktopIcon({ app, onOpen, selected, onSelect, tapToOpen, className, style }: DesktopIconProps): React.JSX.Element;
270
+ declare namespace DesktopIcon {
271
+ var displayName: string;
272
+ }
273
+
274
+ interface WindowRect {
275
+ x: number;
276
+ y: number;
277
+ w: number;
278
+ h: number;
279
+ }
280
+ declare function clampWindowRect(r: WindowRect): WindowRect;
281
+ interface WindowProps {
282
+ title: string;
283
+ icon?: string;
284
+ rect: WindowRect;
285
+ onRectChange: (rect: WindowRect) => void;
286
+ resizable?: boolean;
287
+ minimized?: boolean;
288
+ zIndex?: number;
289
+ onClose: () => void;
290
+ onMinimize?: () => void;
291
+ onMaximizeToggle?: () => void;
292
+ maximized?: boolean;
293
+ onFocus?: () => void;
294
+ className?: string;
295
+ children?: React.ReactNode;
296
+ }
297
+ declare function Window({ title, icon, rect, onRectChange, resizable, minimized, zIndex, onClose, onMinimize, onMaximizeToggle, maximized, onFocus, className, children, }: WindowProps): React.JSX.Element;
298
+ declare namespace Window {
299
+ var displayName: string;
300
+ }
301
+ /** Centered loading spinner used as the window content fallback. */
302
+ declare function WindowSpinner(): React.JSX.Element;
303
+
304
+ interface OpenWindowOptions {
305
+ title: string;
306
+ icon?: string;
307
+ resizable?: boolean;
308
+ width?: number;
309
+ height?: number;
310
+ content: React.ReactNode;
311
+ /**
312
+ * Deep-link target inside the app — e.g. open Settings straight to
313
+ * "wallpaper". The app reads it via useWindowSection().
314
+ */
315
+ section?: string;
316
+ }
317
+ interface WindowEntry extends OpenWindowOptions {
318
+ slug: string;
319
+ rect: WindowRect;
320
+ minimized: boolean;
321
+ maximized: boolean;
322
+ z: number;
323
+ /** Bumps every time `section` is (re)targeted, so apps re-navigate even to the same section. */
324
+ sectionNonce: number;
325
+ }
326
+ interface WindowManagerContextValue {
327
+ windows: WindowEntry[];
328
+ open: (slug: string, opts: OpenWindowOptions) => void;
329
+ close: (slug: string) => void;
330
+ focus: (slug: string) => void;
331
+ minimize: (slug: string) => void;
332
+ /** Un-minimize + bring to front (without replacing content). */
333
+ restore: (slug: string) => void;
334
+ toggleMaximize: (slug: string) => void;
335
+ isOpen: (slug: string) => boolean;
336
+ isMinimized: (slug: string) => boolean;
337
+ /** Deep-link an already-open window to a section (bumps its nonce so apps re-navigate). */
338
+ setSection: (slug: string, section: string) => void;
339
+ /** @internal used by <WindowManager/> to persist drag/resize geometry. */
340
+ updateRect: (slug: string, rect: WindowRect) => void;
341
+ }
342
+ /** Section deep-link exposed to app window content. */
343
+ interface WindowSection {
344
+ slug: string;
345
+ section?: string;
346
+ nonce: number;
347
+ }
348
+ /**
349
+ * Read the deep-link section targeted for the current app window (e.g. Settings
350
+ * opened straight to "wallpaper"). `nonce` changes on every (re)target — react
351
+ * to it so re-opening to the same section still navigates. Returns an empty
352
+ * section outside a window.
353
+ */
354
+ declare function useWindowSection(): WindowSection;
355
+ declare function WindowManagerProvider({ children }: {
356
+ children: React.ReactNode;
357
+ }): React.JSX.Element;
358
+ declare namespace WindowManagerProvider {
359
+ var displayName: string;
360
+ }
361
+ /** Access the window manager API. Must be used within <WindowManagerProvider>. */
362
+ declare function useWindowManager(): WindowManagerContextValue;
363
+ /**
364
+ * WindowManager — renders one <Window/> per open entry tracked by the
365
+ * nearest <WindowManagerProvider>. Place this once inside the desktop's
366
+ * content area (DesktopShell does this for you).
367
+ */
368
+ declare function WindowManager(): React.JSX.Element;
369
+ declare namespace WindowManager {
370
+ var displayName: string;
371
+ }
372
+
373
+ /** Minimal brand shape for the login crest (name may be bilingual). */
374
+ interface OSLoginBrand {
375
+ name?: string | {
376
+ en: string;
377
+ ar: string;
378
+ };
379
+ tagline?: {
380
+ en: string;
381
+ ar: string;
382
+ };
383
+ icon?: React.ReactNode | null;
384
+ }
385
+ interface OSLoginScreenProps {
386
+ /** Wallpaper id from `theme/wallpapers.ts` (defaults to "aurora"). */
387
+ wallpaper?: string;
388
+ /**
389
+ * Optional background image URL, layered ON TOP of the gradient wallpaper
390
+ * (cover/center). If it fails to load, the gradient shows through — so the
391
+ * screen never ends up blank.
392
+ */
393
+ backgroundImage?: string;
394
+ language?: "en" | "ar";
395
+ brand?: OSLoginBrand;
396
+ className?: string;
397
+ authClient?: AuthClient;
398
+ onSuccess?: () => void;
399
+ /** Optional "Create account" affordance. */
400
+ onRegister?: () => void;
401
+ /** When set, renders LockScreen/PasswordLockScreen instead of the sign-in form. */
402
+ lockedUser?: (LockScreenUser | PasswordLockScreenUser) & {
403
+ usePassword?: boolean;
404
+ };
405
+ onUnlockPin?: (pin: string) => Promise<void>;
406
+ onUnlockPassword?: (creds: UnlockCredentials) => Promise<void>;
407
+ onSignOut?: () => void;
408
+ onForceLogout?: () => void;
409
+ hasTOTP?: boolean;
410
+ }
411
+ declare function OSLoginScreen({ wallpaper, backgroundImage, language, brand, className, authClient, onSuccess, onRegister, lockedUser, onUnlockPin, onUnlockPassword, onSignOut, onForceLogout, hasTOTP, }: OSLoginScreenProps): React.JSX.Element;
412
+ declare namespace OSLoginScreen {
413
+ var displayName: string;
414
+ }
415
+
416
+ interface LaunchpadProps {
417
+ open: boolean;
418
+ onOpenChange: (open: boolean) => void;
419
+ apps: OSApp[];
420
+ /** Slugs currently pinned to the dock. */
421
+ pinned?: string[];
422
+ /** Slugs currently hidden from the desktop. */
423
+ hidden?: string[];
424
+ onLaunch: (slug: string) => void;
425
+ onPin?: (slug: string) => void;
426
+ onUnpin?: (slug: string) => void;
427
+ onAddToDesktop?: (slug: string) => void;
428
+ onRemoveFromDesktop?: (slug: string) => void;
429
+ }
430
+ declare function Launchpad({ open, onOpenChange, apps, pinned, hidden, onLaunch, onPin, onUnpin, onAddToDesktop, onRemoveFromDesktop, }: LaunchpadProps): React.JSX.Element | null;
431
+ declare namespace Launchpad {
432
+ var displayName: string;
433
+ }
434
+
435
+ interface SpotlightProps {
436
+ open: boolean;
437
+ onOpenChange: (open: boolean) => void;
438
+ apps: OSApp[];
439
+ onLaunch: (slug: string) => void;
440
+ }
441
+ declare function Spotlight({ open, onOpenChange, apps, onLaunch }: SpotlightProps): React.JSX.Element | null;
442
+ declare namespace Spotlight {
443
+ var displayName: string;
444
+ }
445
+ /** Small icon button that opens Spotlight (for the top bar). */
446
+ declare function SpotlightTrigger({ onClick, className }: {
447
+ onClick: () => void;
448
+ className?: string;
449
+ }): React.JSX.Element;
450
+
451
+ interface NotificationCenterProps {
452
+ open: boolean;
453
+ onOpenChange: (open: boolean) => void;
454
+ notifications: OSNotification[];
455
+ unreadCount?: number;
456
+ onMarkRead?: (id: string) => void;
457
+ onMarkAllRead?: () => void;
458
+ onNavigate?: (url: string) => void;
459
+ /** Optional slot rendered at the top (e.g. a weather / calendar widget). */
460
+ header?: React.ReactNode;
461
+ }
462
+ declare function NotificationCenter({ open, onOpenChange, notifications, unreadCount, onMarkRead, onMarkAllRead, onNavigate, header, }: NotificationCenterProps): React.JSX.Element;
463
+ declare namespace NotificationCenter {
464
+ var displayName: string;
465
+ }
466
+
467
+ export { type DesktopApi, type DesktopContextAction, DesktopContextMenu, type DesktopContextMenuProps, DesktopIcon, DesktopIconGrid, type DesktopIconGridProps, type DesktopIconProps, type DesktopPrefs, DesktopShell, type DesktopShellProps, Dock, type DockProps, type IconPos, Launchpad, type LaunchpadProps, NotificationBell, type NotificationBellProps, NotificationCenter, type NotificationCenterProps, type OSApp, type OSAppWindow, type OSLoginBrand, OSLoginScreen, type OSLoginScreenProps, type OSNotification, type OpenWindowOptions, Spotlight, type SpotlightProps, SpotlightTrigger, TogoMenu, type TogoMenuProps, TopBar, type TopBarProps, type UseOSAppsResult, type UseOSSessionResult, type WeatherCondition, type WeatherData, WeatherWidget, type WeatherWidgetProps, Window, WindowManager, type WindowManagerContextValue, WindowManagerProvider, type WindowProps, type WindowRect, type WindowSection, WindowSpinner, clampWindowRect, useOSApps, useOSSession, useWindowManager, useWindowSection };