react-os-shell 4.79.4 → 4.83.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/dist/{Browser-3Z6IC25F.js → Browser-TGOOIGEP.js} +7 -7
- package/dist/{Browser-3Z6IC25F.js.map → Browser-TGOOIGEP.js.map} +1 -1
- package/dist/{Calculator-PAPSDNSJ.js → Calculator-2FCNKTPN.js} +6 -6
- package/dist/{Calculator-PAPSDNSJ.js.map → Calculator-2FCNKTPN.js.map} +1 -1
- package/dist/{CurrencyConverter-VJQI2XVV.js → CurrencyConverter-KZU7MTWE.js} +6 -6
- package/dist/{CurrencyConverter-VJQI2XVV.js.map → CurrencyConverter-KZU7MTWE.js.map} +1 -1
- package/dist/{Documents-6LSU5BZL.js → Documents-TRV2LWMG.js} +8 -16
- package/dist/Documents-TRV2LWMG.js.map +1 -0
- package/dist/{Files-6WCC7TQO.js → Files-PFVZJYRV.js} +9 -9
- package/dist/{Files-6WCC7TQO.js.map → Files-PFVZJYRV.js.map} +1 -1
- package/dist/{Notepad-NOA4XZBK.js → Notepad-UBDMMVLB.js} +7 -7
- package/dist/{Notepad-NOA4XZBK.js.map → Notepad-UBDMMVLB.js.map} +1 -1
- package/dist/{PomodoroTimer-GFL4L3XS.js → PomodoroTimer-SL372V3I.js} +7 -7
- package/dist/{PomodoroTimer-GFL4L3XS.js.map → PomodoroTimer-SL372V3I.js.map} +1 -1
- package/dist/{Preview-GEDTZPMG.js → Preview-7QUDHTDN.js} +35 -8
- package/dist/Preview-7QUDHTDN.js.map +1 -0
- package/dist/{Sidebar-B3LY5T2U.js → Sidebar-GFDPPC4X.js} +24 -6
- package/dist/Sidebar-GFDPPC4X.js.map +1 -0
- package/dist/{Spreadsheet-664SZSSQ.js → Spreadsheet-F24CHTRL.js} +9 -8
- package/dist/Spreadsheet-F24CHTRL.js.map +1 -0
- package/dist/{Stock-WZXVQX3G.js → Stock-YDCHLKSV.js} +6 -6
- package/dist/{Stock-WZXVQX3G.js.map → Stock-YDCHLKSV.js.map} +1 -1
- package/dist/{Weather-FQ7ZVXVR.js → Weather-HM3Y5LMI.js} +6 -6
- package/dist/{Weather-FQ7ZVXVR.js.map → Weather-HM3Y5LMI.js.map} +1 -1
- package/dist/{WorldClock-XAFQ5ZQF.js → WorldClock-RXKMY3YJ.js} +6 -6
- package/dist/{WorldClock-XAFQ5ZQF.js.map → WorldClock-RXKMY3YJ.js.map} +1 -1
- package/dist/apps/index.js +19 -19
- package/dist/{chunk-DC6BYGZ6.js → chunk-2P6CJAR5.js} +4 -4
- package/dist/chunk-2P6CJAR5.js.map +1 -0
- package/dist/{chunk-EWDYCO65.js → chunk-35RG2LD2.js} +3 -3
- package/dist/{chunk-EWDYCO65.js.map → chunk-35RG2LD2.js.map} +1 -1
- package/dist/{chunk-ZO4XGUZQ.js → chunk-3TSIRZUW.js} +3 -3
- package/dist/{chunk-ZO4XGUZQ.js.map → chunk-3TSIRZUW.js.map} +1 -1
- package/dist/{chunk-BLLINYDD.js → chunk-5TLIIDRZ.js} +186 -5
- package/dist/chunk-5TLIIDRZ.js.map +1 -0
- package/dist/chunk-6XQHH7D2.js +105 -0
- package/dist/chunk-6XQHH7D2.js.map +1 -0
- package/dist/{chunk-OQ7GRU7G.js → chunk-7RUYFI6O.js} +3 -3
- package/dist/{chunk-OQ7GRU7G.js.map → chunk-7RUYFI6O.js.map} +1 -1
- package/dist/{chunk-TT2WNTDS.js → chunk-IZ2FABWU.js} +3 -3
- package/dist/{chunk-TT2WNTDS.js.map → chunk-IZ2FABWU.js.map} +1 -1
- package/dist/{chunk-YZVJCHXV.js → chunk-LEMEFL6U.js} +300 -194
- package/dist/chunk-LEMEFL6U.js.map +1 -0
- package/dist/chunk-LZJW4FMH.js +7 -0
- package/dist/{chunk-QKVUOX5N.js.map → chunk-LZJW4FMH.js.map} +1 -1
- package/dist/chunk-MGGY74RY.js +14 -0
- package/dist/chunk-MGGY74RY.js.map +1 -0
- package/dist/{chunk-EXSEW5F3.js → chunk-MHUVU7ZX.js} +102 -16
- package/dist/chunk-MHUVU7ZX.js.map +1 -0
- package/dist/{chunk-QJS6RBH4.js → chunk-XWLEA4B7.js} +3 -3
- package/dist/{chunk-QJS6RBH4.js.map → chunk-XWLEA4B7.js.map} +1 -1
- package/dist/{chunk-J46YHNHL.js → chunk-Y2F2RTXM.js} +6 -6
- package/dist/{chunk-J46YHNHL.js.map → chunk-Y2F2RTXM.js.map} +1 -1
- package/dist/{chunk-WNLPTLT6.js → chunk-ZESNO4TZ.js} +2 -2
- package/dist/chunk-ZESNO4TZ.js.map +1 -0
- package/dist/{index-BjEcKEsJ.d.ts → index-D9ZuIsBe.d.ts} +130 -1
- package/dist/index.d.ts +53 -3
- package/dist/index.js +157 -55
- package/dist/index.js.map +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +6 -4
- package/package.json +4 -4
- package/dist/Documents-6LSU5BZL.js.map +0 -1
- package/dist/Preview-GEDTZPMG.js.map +0 -1
- package/dist/Sidebar-B3LY5T2U.js.map +0 -1
- package/dist/Spreadsheet-664SZSSQ.js.map +0 -1
- package/dist/chunk-BLLINYDD.js.map +0 -1
- package/dist/chunk-DC6BYGZ6.js.map +0 -1
- package/dist/chunk-EXSEW5F3.js.map +0 -1
- package/dist/chunk-QKVUOX5N.js +0 -7
- package/dist/chunk-WNLPTLT6.js.map +0 -1
- package/dist/chunk-YZVJCHXV.js.map +0 -1
|
@@ -1,8 +1,117 @@
|
|
|
1
1
|
import { glassStyle, GLASS_DIVIDER } from './chunk-3CUSBZWG.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { forwardRef, useId, createContext, useSyncExternalStore, useMemo, useContext, useRef, useEffect, useState, useCallback } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
|
|
6
|
+
var BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
|
|
7
|
+
var BUTTON_VARIANTS = {
|
|
8
|
+
primary: "bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus:ring-blue-400/40",
|
|
9
|
+
secondary: "border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-blue-400/30",
|
|
10
|
+
ghost: "text-gray-700 hover:bg-gray-100 focus:ring-blue-400/30",
|
|
11
|
+
"ghost-danger": "text-red-600 hover:bg-red-50 focus:ring-red-400/30",
|
|
12
|
+
danger: "bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40",
|
|
13
|
+
/**
|
|
14
|
+
* A button that reads as a link: no box, no padding, underlined on hover.
|
|
15
|
+
* For an action that belongs in running text or beside a field — "Forgot
|
|
16
|
+
* your password?", "Use a different address" — where a boxed button would
|
|
17
|
+
* claim more of the page than the action deserves.
|
|
18
|
+
*
|
|
19
|
+
* It is still a <button>. Anything that NAVIGATES should be an anchor, so it
|
|
20
|
+
* can be middle-clicked and read off the status bar; this is for actions
|
|
21
|
+
* that merely look quiet.
|
|
22
|
+
*/
|
|
23
|
+
link: "text-blue-600 underline-offset-4 hover:underline focus:ring-blue-400/30"
|
|
24
|
+
};
|
|
25
|
+
var SIZES = {
|
|
26
|
+
sm: "gap-1 px-2.5 py-1 text-xs",
|
|
27
|
+
md: "gap-1.5 px-3 py-1.5 text-sm",
|
|
28
|
+
// `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's
|
|
29
|
+
// `h-10`, which is the promise IconButton's docblock makes for every rung.
|
|
30
|
+
// `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a
|
|
31
|
+
// Button and an IconButton sitting in the same row.
|
|
32
|
+
lg: "gap-2 px-4 py-2.5 text-sm",
|
|
33
|
+
// 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it
|
|
34
|
+
// ONLY for chrome that sits outside the task — switching screens, ending a
|
|
35
|
+
// shift — and never on the path someone is actually working through. It
|
|
36
|
+
// exists so a dense toolbar has something legitimate to reach for instead of
|
|
37
|
+
// borrowing the desktop `sm`, which is half this height.
|
|
38
|
+
"touch-sm": "gap-1.5 h-11 px-4 text-base",
|
|
39
|
+
"touch": "gap-2 h-14 px-5 text-base",
|
|
40
|
+
// 56px — the comfortable default
|
|
41
|
+
"touch-lg": "gap-2 h-16 px-6 text-lg",
|
|
42
|
+
// 64px — primary action on a page
|
|
43
|
+
"touch-xl": "gap-3 h-20 px-8 text-2xl"
|
|
44
|
+
// 80px — keypad keys, tender
|
|
45
|
+
};
|
|
46
|
+
var LINK_BASE = "inline-flex items-center font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
|
|
47
|
+
var LINK_SIZES = {
|
|
48
|
+
sm: "gap-1 text-xs",
|
|
49
|
+
md: "gap-1.5 text-sm",
|
|
50
|
+
lg: "gap-2 text-sm",
|
|
51
|
+
"touch-sm": "gap-1.5 text-base",
|
|
52
|
+
"touch": "gap-2 text-base",
|
|
53
|
+
"touch-lg": "gap-2 text-lg",
|
|
54
|
+
"touch-xl": "gap-3 text-2xl"
|
|
55
|
+
};
|
|
56
|
+
function Spinner() {
|
|
57
|
+
return /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
|
|
58
|
+
/* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
59
|
+
/* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
|
|
60
|
+
] });
|
|
61
|
+
}
|
|
62
|
+
var Button = forwardRef(function Button2({
|
|
63
|
+
variant = "primary",
|
|
64
|
+
size = "md",
|
|
65
|
+
loading = false,
|
|
66
|
+
leftIcon,
|
|
67
|
+
rightIcon,
|
|
68
|
+
block = false,
|
|
69
|
+
disabled,
|
|
70
|
+
type = "button",
|
|
71
|
+
children,
|
|
72
|
+
className = "",
|
|
73
|
+
disabledReason,
|
|
74
|
+
...rest
|
|
75
|
+
}, ref) {
|
|
76
|
+
const generatedId = useId();
|
|
77
|
+
const isDisabled = disabled || loading;
|
|
78
|
+
const showReason = isDisabled && disabledReason != null && disabledReason !== "";
|
|
79
|
+
const reasonId = showReason ? generatedId : void 0;
|
|
80
|
+
const button = /* @__PURE__ */ jsxs(
|
|
81
|
+
"button",
|
|
82
|
+
{
|
|
83
|
+
ref,
|
|
84
|
+
type,
|
|
85
|
+
disabled: isDisabled,
|
|
86
|
+
"aria-busy": loading || void 0,
|
|
87
|
+
"aria-describedby": reasonId,
|
|
88
|
+
className: [
|
|
89
|
+
// `link` takes the base WITHOUT the box: no rounding, no padding from
|
|
90
|
+
// the size table. It cannot cancel those by overriding — two padding
|
|
91
|
+
// utilities in one class attribute resolve by compiled-stylesheet
|
|
92
|
+
// order, not by the order they were written — so it never receives
|
|
93
|
+
// them, and keeps only the size's gap and text size.
|
|
94
|
+
variant === "link" ? LINK_BASE : BUTTON_BASE,
|
|
95
|
+
BUTTON_VARIANTS[variant],
|
|
96
|
+
variant === "link" ? LINK_SIZES[size] : SIZES[size],
|
|
97
|
+
block ? "w-full" : "",
|
|
98
|
+
className
|
|
99
|
+
].filter(Boolean).join(" "),
|
|
100
|
+
...rest,
|
|
101
|
+
children: [
|
|
102
|
+
loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
|
|
103
|
+
children,
|
|
104
|
+
!loading && rightIcon
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
if (!showReason) return button;
|
|
109
|
+
return /* @__PURE__ */ jsxs("span", { className: block ? "flex w-full flex-col gap-1" : "inline-flex flex-col items-start gap-1", children: [
|
|
110
|
+
button,
|
|
111
|
+
/* @__PURE__ */ jsx("span", { id: reasonId, className: "text-xs text-red-600", children: disabledReason })
|
|
112
|
+
] });
|
|
113
|
+
});
|
|
114
|
+
var Button_default = Button;
|
|
6
115
|
var QUERY = "(max-width: 767px), (pointer: coarse)";
|
|
7
116
|
function getSnapshot() {
|
|
8
117
|
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
@@ -117,6 +226,78 @@ function ShellStringsProvider({ value, children }) {
|
|
|
117
226
|
function useShellStrings() {
|
|
118
227
|
return useContext(ShellStringsContext);
|
|
119
228
|
}
|
|
229
|
+
var STATUS_TONE = {
|
|
230
|
+
success: "success",
|
|
231
|
+
error: "error",
|
|
232
|
+
warning: "warning",
|
|
233
|
+
info: "info",
|
|
234
|
+
// A missing page is not an error the user made, and colouring it red says it
|
|
235
|
+
// was. Refused and broken are genuinely different from not-found.
|
|
236
|
+
404: "info",
|
|
237
|
+
403: "warning",
|
|
238
|
+
500: "error"
|
|
239
|
+
};
|
|
240
|
+
var TONE_CLASS = {
|
|
241
|
+
success: "text-green-600 bg-green-100",
|
|
242
|
+
error: "text-red-600 bg-red-100",
|
|
243
|
+
warning: "text-amber-600 bg-amber-100",
|
|
244
|
+
info: "text-blue-600 bg-blue-100"
|
|
245
|
+
};
|
|
246
|
+
var DEFAULT_TITLE = {
|
|
247
|
+
404: "Page not found",
|
|
248
|
+
403: "You don't have access to this",
|
|
249
|
+
500: "Something went wrong"
|
|
250
|
+
};
|
|
251
|
+
function StatusIcon({ tone }) {
|
|
252
|
+
const common = { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": true };
|
|
253
|
+
switch (tone) {
|
|
254
|
+
case "success":
|
|
255
|
+
return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) });
|
|
256
|
+
case "error":
|
|
257
|
+
return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) });
|
|
258
|
+
case "warning":
|
|
259
|
+
return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) });
|
|
260
|
+
default:
|
|
261
|
+
return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function Result({
|
|
265
|
+
status = "info",
|
|
266
|
+
title,
|
|
267
|
+
subTitle,
|
|
268
|
+
extra,
|
|
269
|
+
icon,
|
|
270
|
+
children,
|
|
271
|
+
className = ""
|
|
272
|
+
}) {
|
|
273
|
+
const tone = STATUS_TONE[status];
|
|
274
|
+
const announces = tone === "error";
|
|
275
|
+
return /* @__PURE__ */ jsxs(
|
|
276
|
+
"div",
|
|
277
|
+
{
|
|
278
|
+
role: announces ? "alert" : void 0,
|
|
279
|
+
className: `flex flex-col items-center justify-center px-6 py-12 text-center ${className}`.trim(),
|
|
280
|
+
children: [
|
|
281
|
+
/* @__PURE__ */ jsx("div", { className: `flex h-14 w-14 items-center justify-center rounded-full ${TONE_CLASS[tone]}`, children: icon ?? /* @__PURE__ */ jsx(StatusIcon, { tone }) }),
|
|
282
|
+
/* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-semibold text-gray-900", children: title ?? DEFAULT_TITLE[status] ?? "" }),
|
|
283
|
+
subTitle && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-md text-sm text-gray-500", children: subTitle }),
|
|
284
|
+
children && /* @__PURE__ */ jsx("div", { className: "mt-4 w-full max-w-md text-left", children }),
|
|
285
|
+
extra && /* @__PURE__ */ jsx("div", { className: "mt-6 flex flex-wrap items-center justify-center gap-3", children: extra })
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
function LoadingSpinner({ size = "md", padding = "py-12", label, className = "" }) {
|
|
291
|
+
const dim = size === "sm" ? "h-5 w-5" : size === "lg" ? "h-12 w-12" : "h-8 w-8";
|
|
292
|
+
const ring = /* @__PURE__ */ jsx("div", { className: `${dim} animate-spin rounded-full border-2 border-gray-200 border-t-blue-600` });
|
|
293
|
+
if (label == null) {
|
|
294
|
+
return /* @__PURE__ */ jsx("div", { className: `flex items-center justify-center ${padding} ${className}`, children: ring });
|
|
295
|
+
}
|
|
296
|
+
return /* @__PURE__ */ jsxs("div", { role: "status", className: `flex flex-col items-center justify-center gap-3 ${padding} ${className}`, children: [
|
|
297
|
+
ring,
|
|
298
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: label })
|
|
299
|
+
] });
|
|
300
|
+
}
|
|
120
301
|
function getDensity() {
|
|
121
302
|
return getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal";
|
|
122
303
|
}
|
|
@@ -290,6 +471,6 @@ function useShellPrefs() {
|
|
|
290
471
|
} };
|
|
291
472
|
}
|
|
292
473
|
|
|
293
|
-
export { DEFAULT_SHELL_STRINGS, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, ShellStringsProvider, useIsMobile, useLocalStoragePrefs, useShellPrefs, useShellStrings };
|
|
294
|
-
//# sourceMappingURL=chunk-
|
|
295
|
-
//# sourceMappingURL=chunk-
|
|
474
|
+
export { BUTTON_BASE, BUTTON_VARIANTS, Button_default, DEFAULT_SHELL_STRINGS, LoadingSpinner, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, Result, ShellPrefsProvider, ShellStringsProvider, useIsMobile, useLocalStoragePrefs, useShellPrefs, useShellStrings };
|
|
475
|
+
//# sourceMappingURL=chunk-5TLIIDRZ.js.map
|
|
476
|
+
//# sourceMappingURL=chunk-5TLIIDRZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forms/Button.tsx","../src/shell/useIsMobile.ts","../src/shell/strings.tsx","../src/shell/Result.tsx","../src/shell/Spinner.tsx","../src/shell/PopupMenu.tsx","../src/shell/ShellPrefs.tsx"],"names":["Button","jsx","jsxs","forwardRef","PopupMenuItem","createContext","useContext"],"mappings":"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","file":"chunk-5TLIIDRZ.js","sourcesContent":["/**\n * Button — the kit's standard button. `primary` uses `bg-blue-600`, which the\n * theme system remaps to the active accent (and to a readable tone in dark\n * mode), so a primary button always follows the user's chosen accent.\n *\n * Controlled by the consumer like any native button (spread `onClick`, `type`,\n * `form`, …); the component only adds the look, a `loading` spinner state, and\n * optional icon slots.\n */\nimport { forwardRef, useId, type ButtonHTMLAttributes, type ReactNode } from 'react';\n\n/**\n * `ghost-danger` is a destructive action sitting in a row of ordinary ones —\n * Clear, Discard, Remove — where solid `danger` would be wrong. Two solid\n * shouting buttons on one screen means neither is read, so the destructive one\n * borrows `ghost`'s weight and keeps only the colour. The colour is the point:\n * it lets someone tell what the button costs WITHOUT reading it, which under\n * time pressure is what actually happens.\n */\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'ghost-danger' | 'danger' | 'link';\n/**\n * `sm`/`md`/`lg` are the desktop scale. The `touch-*` rungs are for a finger on\n * glass — a till, a warehouse tablet — and are a SEPARATE ladder on purpose:\n * a till's idea of \"medium\" is 56px, and naming it `md` would have made the\n * same word mean two sizes depending on which app you were reading.\n *\n * Nothing picks a touch size automatically. An app that wants them asks for\n * them, so a desktop portal cannot grow finger-sized buttons by accident.\n */\nexport type ButtonSize = 'sm' | 'md' | 'lg' | 'touch-sm' | 'touch' | 'touch-lg' | 'touch-xl';\n\nexport interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n /** Show a spinner and disable the button while an action is in flight. */\n loading?: boolean;\n leftIcon?: ReactNode;\n rightIcon?: ReactNode;\n /** Stretch to the full width of the container. */\n block?: boolean;\n className?: string;\n /**\n * Why this button is disabled, rendered as text BESIDE it — never as a\n * `title` tooltip. A tooltip needs a hover, a touchscreen has no hover, and a\n * cashier facing a dead button with no explanation calls a manager.\n *\n * Only rendered while the button is actually disabled (or loading); ignored\n * otherwise, so it is safe to pass unconditionally. Note it changes the\n * rendered shape: the button gains a wrapper element, which matters to a\n * parent `flex gap-*` row or a `:first-child` selector.\n */\n disabledReason?: ReactNode;\n}\n\n/**\n * Shared with `IconButton`, which composes its own class string rather than\n * passing a square size through this component's `className` — two competing\n * padding utilities in one attribute resolve by compiled-stylesheet order, not\n * by the order they were written (the reasoning in forms/styles.ts applies\n * here identically).\n *\n * Package-internal on purpose: exported from the module so IconButton can share\n * it, deliberately absent from `src/ui/kit.ts` so it is not public API. A\n * consumer restyling a button is a consumer the theme system cannot reach.\n */\nexport const BUTTON_BASE =\n 'inline-flex items-center justify-center rounded-md font-medium transition-colors ' +\n 'focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60';\n\nexport const BUTTON_VARIANTS: Record<ButtonVariant, string> = {\n primary: 'bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus:ring-blue-400/40',\n secondary: 'border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-blue-400/30',\n ghost: 'text-gray-700 hover:bg-gray-100 focus:ring-blue-400/30',\n 'ghost-danger': 'text-red-600 hover:bg-red-50 focus:ring-red-400/30',\n danger: 'bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40',\n /**\n * A button that reads as a link: no box, no padding, underlined on hover.\n * For an action that belongs in running text or beside a field — \"Forgot\n * your password?\", \"Use a different address\" — where a boxed button would\n * claim more of the page than the action deserves.\n *\n * It is still a <button>. Anything that NAVIGATES should be an anchor, so it\n * can be middle-clicked and read off the status bar; this is for actions\n * that merely look quiet.\n */\n link: 'text-blue-600 underline-offset-4 hover:underline focus:ring-blue-400/30',\n};\n\n// A Record keyed by the union, not a ternary chain: adding a member without a\n// size here is then a compile error rather than a silent fall-through to the\n// default branch. `sm`/`md` are byte-for-byte what they have always been, and\n// `tests/touchPrimitives.test.tsx` pins them against a captured literal.\n//\n// Every rung is also asserted to be the same height as the IconButton rung of\n// the same name (`tests/buttonLinkAndLarge.test.tsx`) — pinning the two sides\n// separately is how `lg` first shipped 4px short of its square twin.\n//\n// The touch rungs carry an explicit `h-*` where the desktop pair uses `py-*`.\n// That is deliberate: a hit target has to be a guaranteed height, not a height\n// implied by the current font size. They are separate map entries and never\n// concatenated with each other, so no two competing utilities land in one class\n// attribute.\nconst SIZES: Record<ButtonSize, string> = {\n sm: 'gap-1 px-2.5 py-1 text-xs',\n md: 'gap-1.5 px-3 py-1.5 text-sm',\n // `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's\n // `h-10`, which is the promise IconButton's docblock makes for every rung.\n // `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a\n // Button and an IconButton sitting in the same row.\n lg: 'gap-2 px-4 py-2.5 text-sm',\n // 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it\n // ONLY for chrome that sits outside the task — switching screens, ending a\n // shift — and never on the path someone is actually working through. It\n // exists so a dense toolbar has something legitimate to reach for instead of\n // borrowing the desktop `sm`, which is half this height.\n 'touch-sm': 'gap-1.5 h-11 px-4 text-base',\n 'touch': 'gap-2 h-14 px-5 text-base', // 56px — the comfortable default\n 'touch-lg': 'gap-2 h-16 px-6 text-lg', // 64px — primary action on a page\n 'touch-xl': 'gap-3 h-20 px-8 text-2xl', // 80px — keypad keys, tender\n};\n\n// Everything BUTTON_BASE has except the box: no rounding (there is nothing to\n// round) and no `justify-center` (a link sits in a line of text, it does not\n// centre itself in a shape).\nconst LINK_BASE =\n 'inline-flex items-center font-medium transition-colors ' +\n 'focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60';\n\n// Only the gap and the text size survive from each rung — the padding is what\n// the link variant exists to shed.\nconst LINK_SIZES: Record<ButtonSize, string> = {\n sm: 'gap-1 text-xs',\n md: 'gap-1.5 text-sm',\n lg: 'gap-2 text-sm',\n 'touch-sm': 'gap-1.5 text-base',\n 'touch': 'gap-2 text-base',\n 'touch-lg': 'gap-2 text-lg',\n 'touch-xl': 'gap-3 text-2xl',\n};\n\nfunction Spinner() {\n return (\n <svg className=\"h-3.5 w-3.5 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n );\n}\n\nconst Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n { variant = 'primary', size = 'md', loading = false, leftIcon, rightIcon, block = false,\n disabled, type = 'button', children, className = '', disabledReason, ...rest },\n ref,\n) {\n // useId, not a module counter: a counter assigns different ids on the server\n // and the client and breaks hydration. Called unconditionally — it is a hook.\n const generatedId = useId();\n const isDisabled = disabled || loading;\n const showReason = isDisabled && disabledReason != null && disabledReason !== '';\n const reasonId = showReason ? generatedId : undefined;\n\n const button = (\n <button\n ref={ref}\n type={type}\n disabled={isDisabled}\n aria-busy={loading || undefined}\n aria-describedby={reasonId}\n className={[\n // `link` takes the base WITHOUT the box: no rounding, no padding from\n // the size table. It cannot cancel those by overriding — two padding\n // utilities in one class attribute resolve by compiled-stylesheet\n // order, not by the order they were written — so it never receives\n // them, and keeps only the size's gap and text size.\n variant === 'link' ? LINK_BASE : BUTTON_BASE,\n BUTTON_VARIANTS[variant],\n variant === 'link' ? LINK_SIZES[size] : SIZES[size],\n block ? 'w-full' : '',\n className,\n ].filter(Boolean).join(' ')}\n {...rest}\n >\n {loading ? <Spinner /> : leftIcon}\n {children}\n {!loading && rightIcon}\n </button>\n );\n\n // No reason to show ⇒ the bare <button>, byte-identical to every release\n // before this prop existed. Only the disabled-with-a-reason case wraps.\n if (!showReason) return button;\n\n return (\n <span className={block ? 'flex w-full flex-col gap-1' : 'inline-flex flex-col items-start gap-1'}>\n {button}\n <span id={reasonId} className=\"text-xs text-red-600\">{disabledReason}</span>\n </span>\n );\n});\n\nexport default Button;\n","/**\n * Mobile breakpoint hook — single source of truth for \"is this a phone/tablet\n * portrait viewport?\" Drives whether <Layout> renders the desktop chrome\n * (windows / taskbar / start menu sidebar) or the <MobileAppLanding> screen\n * that points the user at the consumer-wired mobile app. A handful of\n * primitives (Modal, ResizableTable) also read it to adapt touch behaviour.\n *\n * Locked at 768 px so iPad portrait counts as mobile. Also OR's with\n * `pointer: coarse` so touch-only devices that happen to be wider (e.g. an\n * Android tablet in landscape) still get touch-appropriate UI.\n */\nimport { useSyncExternalStore } from 'react';\n\nconst QUERY = '(max-width: 767px), (pointer: coarse)';\n\nfunction getSnapshot(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia(QUERY).matches;\n}\n\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\nfunction subscribe(cb: () => void): () => void {\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\n const mql = window.matchMedia(QUERY);\n // Older Safari uses addListener; modern browsers use addEventListener.\n if (mql.addEventListener) {\n mql.addEventListener('change', cb);\n return () => mql.removeEventListener('change', cb);\n }\n mql.addListener(cb);\n return () => mql.removeListener(cb);\n}\n\nexport function useIsMobile(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","/**\n * ShellStrings — the shell's user-facing strings, in one typed catalog.\n *\n * Every string here was hardcoded English at its call site, which made the\n * shell untranslatable no matter what the consuming portal did. The catalog\n * is the fix's whole shape: components read strings through `useShellStrings`\n * (which works with NO provider — the English defaults are the context\n * default), and a consumer that wants another language mounts\n * `ShellStringsProvider` once with a partial override. Overrides merge per\n * SECTION, so translating the window controls does not oblige anyone to\n * translate the help viewer.\n *\n * This is deliberately not an i18n library: no message IDs, no interpolation\n * DSL, no plural rules. The shell's strings are labels and short sentences;\n * a typed object keeps them discoverable and lets TypeScript flag a\n * translation that goes stale when a string is added.\n *\n * Prop-level text (a `emptyText` prop, a `placeholder`) always wins over the\n * catalog — the catalog replaces hardcoded DEFAULTS, never a caller's words.\n *\n * Kit-safe: no window-manager import, so `react-os-shell/ui` consumers get it\n * too.\n */\nimport { createContext, useContext, useMemo, type ReactNode } from 'react';\n\nexport interface ShellStrings {\n /** Window chrome: control tooltips and the title bar's accessible name. */\n window: {\n minimize: string;\n maximize: string;\n windowed: string;\n pinOnTop: string;\n unpin: string;\n close: string;\n /** Suffix of the title bar's aria-label: `\"{title} — {windowSuffix}\"`. */\n windowSuffix: string;\n prev: string;\n next: string;\n snapLayouts: string;\n snapLeft: string;\n snapRight: string;\n snapTopLeft: string;\n snapTopRight: string;\n snapBottomLeft: string;\n snapBottomRight: string;\n };\n /** Taskbar and exposé. */\n taskbar: {\n closeWindow: string;\n offscreenHint: string;\n offscreenDot: string;\n expose: string;\n addToDesktop: string;\n removeFromDesktop: string;\n };\n /** The logout cover. */\n logout: {\n goodbye: string;\n seeYou: string;\n };\n /** About + What's New dialogs. */\n about: {\n aboutPrefix: string;\n thisApp: string;\n openSourceLicenses: string;\n whatsNew: string;\n noChangelog: string;\n };\n /** Data-display defaults (DataTable, lists). */\n table: {\n empty: string;\n loading: string;\n selectAll: string;\n selectRow: string;\n };\n /** Picker defaults (SearchableSelect, TagInput). */\n select: {\n none: string;\n noMatches: string;\n allSelected: string;\n clear: string;\n /** aria-label of a TagInput chip's remove button: `\"{remove} {label}\"`. */\n remove: string;\n };\n /** Notification bell + popup. */\n notifications: {\n title: string;\n markAllRead: string;\n dnd: string;\n dndOn: string;\n caughtUp: string;\n empty: string;\n viewAll: string;\n cardLabel: string;\n dismiss: string;\n };\n /** Form-level messages. */\n form: {\n errorSummaryTitle: string;\n };\n /** HelpCenter chrome. */\n help: {\n title: string;\n searchPlaceholder: string;\n empty: string;\n noResults: string;\n loading: string;\n pickArticle: string;\n noBody: string;\n draft: string;\n newArticle: string;\n edit: string;\n };\n}\n\nexport const DEFAULT_SHELL_STRINGS: ShellStrings = {\n window: {\n minimize: 'Minimize',\n maximize: 'Maximize',\n windowed: 'Windowed',\n pinOnTop: 'Pin on top',\n unpin: 'Unpin from top',\n close: 'Close',\n windowSuffix: 'window',\n prev: 'Prev',\n next: 'Next',\n snapLayouts: 'Snap layouts',\n snapLeft: 'Snap left half',\n snapRight: 'Snap right half',\n snapTopLeft: 'Snap top-left quarter',\n snapTopRight: 'Snap top-right quarter',\n snapBottomLeft: 'Snap bottom-left quarter',\n snapBottomRight: 'Snap bottom-right quarter',\n },\n taskbar: {\n closeWindow: 'Close window',\n offscreenHint: 'This window is off screen — click to bring it back',\n offscreenDot: 'Off screen — click to bring this window back',\n expose: 'Exposé — show all open windows as thumbnails',\n addToDesktop: 'Add to desktop',\n removeFromDesktop: 'Remove from desktop',\n },\n logout: {\n goodbye: 'Goodbye',\n seeYou: 'See you next time',\n },\n about: {\n aboutPrefix: 'About',\n thisApp: 'this app',\n openSourceLicenses: 'Open Source Licenses',\n whatsNew: \"What's New\",\n noChangelog: 'No changelog available.',\n },\n table: {\n empty: 'Nothing to show',\n loading: 'Loading',\n selectAll: 'Select all rows',\n selectRow: 'Select row',\n },\n select: {\n none: '— None —',\n noMatches: 'No matches',\n allSelected: 'All options selected',\n clear: 'Clear selection',\n remove: 'Remove',\n },\n notifications: {\n title: 'Notifications',\n markAllRead: 'Mark all read',\n dnd: 'Do not disturb',\n dndOn: 'Do not disturb is on',\n caughtUp: 'All caught up',\n empty: 'No notifications yet',\n viewAll: 'View all notifications',\n cardLabel: 'Notification',\n dismiss: 'Dismiss notification',\n },\n form: {\n errorSummaryTitle: 'There is a problem',\n },\n help: {\n title: 'Help',\n searchPlaceholder: 'Search help…',\n empty: 'No help articles yet.',\n noResults: 'No articles match your search.',\n loading: 'Loading…',\n pickArticle: 'Pick a help article from the left.',\n noBody: 'This article has no body yet.',\n draft: 'Draft',\n newArticle: '+ New',\n edit: 'Edit',\n },\n};\n\n/** A per-section partial: override any subset of a section's strings. */\nexport type ShellStringsOverride = {\n [S in keyof ShellStrings]?: Partial<ShellStrings[S]>;\n};\n\nconst ShellStringsContext = createContext<ShellStrings>(DEFAULT_SHELL_STRINGS);\n\n/**\n * Mount once, near the top of the app. `value` is a partial — sections and\n * keys you do not translate keep their English defaults, so a catalog never\n * breaks by falling behind a release.\n */\nexport function ShellStringsProvider({ value, children }: { value: ShellStringsOverride; children: ReactNode }) {\n const merged = useMemo<ShellStrings>(() => {\n const out = {} as ShellStrings;\n for (const key of Object.keys(DEFAULT_SHELL_STRINGS) as (keyof ShellStrings)[]) {\n out[key] = { ...DEFAULT_SHELL_STRINGS[key], ...(value[key] ?? {}) } as never;\n }\n return out;\n }, [value]);\n return <ShellStringsContext.Provider value={merged}>{children}</ShellStringsContext.Provider>;\n}\n\n/** The active catalog — English defaults when no provider is mounted. */\nexport function useShellStrings(): ShellStrings {\n return useContext(ShellStringsContext);\n}\n","/**\n * Result — the full-page outcome state: a 404, a 403, a crashed boundary, a\n * completed checkout.\n *\n * `EmptyState` is the neighbour to know about. That one means \"this list has\n * nothing in it yet\", which is normal and usually actionable. This one means\n * something ENDED — succeeded, failed, or was refused — and it is the whole\n * page rather than a region of one. `ErrorPage` is this component wrapped in a\n * full-screen frame.\n */\nimport { type ReactNode } from 'react';\n\nexport type ResultStatus = 'success' | 'error' | 'warning' | 'info' | '404' | '403' | '500';\n\nexport interface ResultProps {\n status?: ResultStatus;\n title?: ReactNode;\n subTitle?: ReactNode;\n /** Actions — usually one primary route out. Always give the user one. */\n extra?: ReactNode;\n /** Replace the status glyph entirely. */\n icon?: ReactNode;\n children?: ReactNode;\n className?: string;\n}\n\ntype Tone = 'success' | 'error' | 'warning' | 'info';\n\nconst STATUS_TONE: Record<ResultStatus, Tone> = {\n success: 'success', error: 'error', warning: 'warning', info: 'info',\n // A missing page is not an error the user made, and colouring it red says it\n // was. Refused and broken are genuinely different from not-found.\n 404: 'info', 403: 'warning', 500: 'error',\n};\n\nconst TONE_CLASS: Record<Tone, string> = {\n success: 'text-green-600 bg-green-100',\n error: 'text-red-600 bg-red-100',\n warning: 'text-amber-600 bg-amber-100',\n info: 'text-blue-600 bg-blue-100',\n};\n\n/** Default copy, so the common cases need only `status`. */\nconst DEFAULT_TITLE: Partial<Record<ResultStatus, string>> = {\n 404: 'Page not found',\n 403: \"You don't have access to this\",\n 500: 'Something went wrong',\n};\n\nfunction StatusIcon({ tone }: { tone: Tone }) {\n const common = { className: 'h-6 w-6', fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', strokeWidth: 1.5, 'aria-hidden': true } as const;\n switch (tone) {\n case 'success':\n return <svg {...common}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4.5 12.75l6 6 9-13.5\" /></svg>;\n case 'error':\n return <svg {...common}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" /></svg>;\n case 'warning':\n return <svg {...common}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z\" /></svg>;\n default:\n return <svg {...common}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z\" /></svg>;\n }\n}\n\nexport default function Result({\n status = 'info', title, subTitle, extra, icon, children, className = '',\n}: ResultProps) {\n const tone = STATUS_TONE[status];\n // WCAG 4.1.3. A Result that reports a failure is a status message: it appears\n // without the user moving focus, so a screen-reader user is told nothing\n // unless it announces itself. `alert` is assertive, which is right for a\n // failure and wrong for everything else — an outcome someone asked for\n // (a completed checkout) or a neutral one (an empty cart) would interrupt\n // whatever they were reading to say something they already expected.\n const announces = tone === 'error';\n return (\n <div\n role={announces ? 'alert' : undefined}\n className={`flex flex-col items-center justify-center px-6 py-12 text-center ${className}`.trim()}\n >\n <div className={`flex h-14 w-14 items-center justify-center rounded-full ${TONE_CLASS[tone]}`}>\n {icon ?? <StatusIcon tone={tone} />}\n </div>\n <h2 className=\"mt-4 text-lg font-semibold text-gray-900\">\n {title ?? DEFAULT_TITLE[status] ?? ''}\n </h2>\n {subTitle && <p className=\"mt-1 max-w-md text-sm text-gray-500\">{subTitle}</p>}\n {children && <div className=\"mt-4 w-full max-w-md text-left\">{children}</div>}\n {extra && <div className=\"mt-6 flex flex-wrap items-center justify-center gap-3\">{extra}</div>}\n </div>\n );\n}\n","import { type ReactNode } from 'react';\n\nexport interface LoadingSpinnerProps {\n /** Ring diameter: `sm` = 20px, `md` = 32px (default), `lg` = 48px. */\n size?: 'sm' | 'md' | 'lg';\n /** Wrapper padding utility (default `py-12`). Pass `''` to remove. */\n padding?: string;\n /**\n * Caption under the ring, e.g. \"Signing in…\". Naming what is happening beats\n * a bare ring on a full-screen wait, where there is nothing else on the page\n * to infer it from. Adds `role=\"status\"` so it is announced.\n */\n label?: ReactNode;\n /** Extra classes on the centering wrapper. */\n className?: string;\n}\n\n/**\n * LoadingSpinner — a centered animated ring for pending/loading regions.\n * (Distinct from the shell's internal \"Loading…\" text used inside data grids.)\n */\nexport default function LoadingSpinner({ size = 'md', padding = 'py-12', label, className = '' }: LoadingSpinnerProps) {\n const dim = size === 'sm' ? 'h-5 w-5' : size === 'lg' ? 'h-12 w-12' : 'h-8 w-8';\n const ring = <div className={`${dim} animate-spin rounded-full border-2 border-gray-200 border-t-blue-600`} />;\n // Without a label the markup is exactly what it has always been — including\n // no `role`, which would otherwise have changed the a11y tree for every\n // existing caller. A labelled spinner is a status; a bare ring is decoration.\n if (label == null) {\n return <div className={`flex items-center justify-center ${padding} ${className}`}>{ring}</div>;\n }\n return (\n <div role=\"status\" className={`flex flex-col items-center justify-center gap-3 ${padding} ${className}`}>\n {ring}\n <p className=\"text-sm text-gray-500\">{label}</p>\n </div>\n );\n}\n","import { forwardRef, useEffect, useRef, type ReactNode, type CSSProperties } from 'react';\nimport { createPortal } from 'react-dom';\nimport { glassStyle, GLASS_DIVIDER } from '../utils/glass';\n\n/**\n * Unified popup menu component — used for all context menus, dropdowns, and flyouts.\n * Reads --menu-density CSS variable: 'tight', 'normal' (default), or 'large'.\n */\n\nexport interface PopupMenuProps {\n children: ReactNode;\n style?: CSSProperties;\n className?: string;\n onClose?: () => void;\n minWidth?: number;\n /** Render into document.body instead of in place. Required when the menu\n * is opened from INSIDE a window: the window panel is a transformed /\n * backdrop-filtered `overflow-hidden` container, which both re-anchors\n * `position: fixed` descendants to itself and clips them — a menu\n * positioned at viewport coordinates ends up offset or invisible.\n * Portaling restores true viewport positioning. */\n portal?: boolean;\n}\n\nfunction getDensity(): 'tight' | 'normal' | 'large' {\n return (getComputedStyle(document.documentElement).getPropertyValue('--menu-density')?.trim() as any) || 'normal';\n}\n\n/** Container for a popup menu — auto-clamps to stay within viewport */\nexport function PopupMenu({ children, style, className = '', onClose, minWidth = 180, portal = false }: PopupMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!onClose) return;\n const handleClick = (e: PointerEvent | MouseEvent) => {\n if ((e.target as HTMLElement).closest('[data-menu-toggle]')) return;\n if (ref.current && !ref.current.contains(e.target as Node)) onClose();\n };\n const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('pointerdown', handleClick);\n window.addEventListener('keydown', handleKey);\n return () => { window.removeEventListener('pointerdown', handleClick); window.removeEventListener('keydown', handleKey); };\n }, [onClose]);\n\n // After render, clamp position to viewport boundaries\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n requestAnimationFrame(() => {\n const rect = el.getBoundingClientRect();\n const margin = 8;\n // Clamp right edge\n if (rect.right > window.innerWidth - margin) {\n const overflow = rect.right - window.innerWidth + margin;\n if (el.style.left) {\n el.style.left = `${parseFloat(el.style.left) - overflow}px`;\n } else if (!el.style.right) {\n el.style.left = `${rect.left - overflow}px`;\n }\n }\n // Clamp left edge\n if (rect.left < margin) {\n if (el.style.left) {\n el.style.left = `${margin}px`;\n } else if (el.style.right) {\n el.style.right = `${window.innerWidth - rect.width - margin}px`;\n }\n }\n // Clamp bottom edge\n if (rect.bottom > window.innerHeight - margin) {\n const overflow = rect.bottom - window.innerHeight + margin;\n if (el.style.top) {\n el.style.top = `${parseFloat(el.style.top) - overflow}px`;\n } else if (el.style.bottom) {\n // Already anchored to bottom, just clamp\n el.style.bottom = `${margin}px`;\n } else {\n el.style.top = `${rect.top - overflow}px`;\n }\n }\n // Clamp top edge\n if (rect.top < margin) {\n if (el.style.top) {\n el.style.top = `${margin}px`;\n } else if (el.style.bottom) {\n el.style.bottom = `${window.innerHeight - rect.height - margin}px`;\n }\n }\n });\n });\n\n const density = getDensity();\n\n const menu = (\n <div ref={ref}\n className={`fixed z-[400] rounded-2xl ${density === 'tight' ? 'py-1' : density === 'large' ? 'py-2' : 'py-1.5'} ${className}`}\n style={{ minWidth, animation: 'popup-in 0.12s ease-out', ...glassStyle(), ...style }}>\n {children}\n <style>{`@keyframes popup-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }`}</style>\n </div>\n );\n\n return portal ? createPortal(menu, document.body) : menu;\n}\n\n/** A clickable menu item */\nexport interface PopupMenuItemProps {\n onClick?: () => void;\n children: ReactNode;\n className?: string;\n danger?: boolean;\n disabled?: boolean;\n /**\n * The menu semantics, when this item is part of one.\n *\n * A context menu on the desktop is a list of buttons and needs none of this;\n * a dropdown attached to a trigger is a `menu` whose items are `menuitem`s\n * with one tab stop between them. Both draw the same item, so the styling\n * lives here rather than being copied into whichever one is newer.\n */\n role?: 'menuitem';\n tabIndex?: number;\n id?: string;\n onMouseEnter?: () => void;\n}\n\n/**\n * `forwardRef`, not a `ref` prop.\n *\n * Taking `ref` as an ordinary prop is React 19 only. This package's peer range\n * is `react: \">=18\"`, and React 18 strips `ref` from a function component's\n * props and warns — so on an 18 consumer the ref would silently never attach,\n * `DropdownMenu`'s `itemRefs` would all be null, and its arrow keys would move\n * `tabIndex` while DOM focus stayed put. A keyboard-dead menu with no error.\n */\nexport const PopupMenuItem = forwardRef<HTMLButtonElement, PopupMenuItemProps>(function PopupMenuItem({\n onClick, children, className = '', danger, disabled,\n role, tabIndex, id, onMouseEnter,\n}, ref) {\n const density = getDensity();\n // Vertical gap between items. `normal` sits a little tighter than the raw\n // size padding; `large` adds a bit more room. Floored at the tight value so\n // the reduced `normal` never drops below `tight` at the smallest menu size.\n const itemPadY = density === 'tight' ? '0.25rem'\n : density === 'large' ? '0.6rem'\n : 'max(0.25rem, calc(var(--menu-padding-y, 0.5rem) - 0.1rem))';\n return (\n <button\n ref={ref}\n id={id}\n role={role}\n tabIndex={tabIndex}\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n disabled={disabled}\n className={`w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto\n ${danger ? 'text-red-600 hover:bg-red-50' : 'text-gray-700 hover:bg-blue-50 hover:text-blue-700'}\n ${disabled ? 'opacity-40 cursor-not-allowed' : ''}\n ${className}`}\n style={{\n width: 'calc(100% - 8px)',\n marginLeft: 4,\n marginRight: 4,\n fontSize: 'var(--menu-font-size, 14px)',\n paddingLeft: 'var(--menu-padding-x, 1rem)',\n paddingRight: 'var(--menu-padding-x, 1rem)',\n paddingTop: itemPadY,\n paddingBottom: itemPadY,\n }}\n >\n {children}\n </button>\n );\n});\n\n/** A divider line between menu items */\nexport function PopupMenuDivider() {\n const density = getDensity();\n return <div className={`border-t ${GLASS_DIVIDER} ${density === 'tight' ? 'my-0.5' : density === 'large' ? 'my-1.5' : 'my-1'} mx-3`} />;\n}\n\n/** A section header label */\nexport function PopupMenuLabel({ children }: { children: ReactNode }) {\n const density = getDensity();\n const labelPadY = density === 'tight' ? '0.125rem' : density === 'large' ? '0.375rem' : '0.25rem';\n return (\n <div\n className=\"text-[10px] font-medium text-gray-400 uppercase tracking-wider\"\n style={{\n paddingLeft: 'var(--menu-padding-x, 1rem)',\n paddingRight: 'var(--menu-padding-x, 1rem)',\n paddingTop: labelPadY,\n paddingBottom: labelPadY,\n }}\n >\n {children}\n </div>\n );\n}\n\n/** An icon helper */\nexport function MenuIcon({ d, className = 'h-4 w-4 text-gray-400' }: { d: string; className?: string }) {\n return (\n <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d={d} />\n </svg>\n );\n}\n","/**\n * Shell-side user-preferences surface — the package never owns where prefs\n * live. The consumer supplies a hook returning the current prefs object plus\n * a save callback; the shell reads keys it knows about (theme,\n * taskbar_position, taskbar_size, world_clocks, favorite_documents,\n * desktop_folders, desktop_snap, notepad_notes, …) and patches them.\n *\n * When no provider is mounted, the shell falls back to localStorage scoped\n * by `storageKey` — so the package works out of the box for a backend-less\n * consumer.\n */\nimport { createContext, useContext, useState, useCallback, type ReactNode } from 'react';\n\nexport interface ShellPrefsAdapter {\n /** The current prefs object. The shell reads its known keys directly off\n * this; consumer-private keys are ignored. */\n prefs: Record<string, any>;\n /** Patch a subset of prefs. The shell calls this with shallow diffs. */\n save: (patch: Record<string, any>) => Promise<void> | void;\n}\n\nconst ShellPrefsContext = createContext<ShellPrefsAdapter | null>(null);\n\nexport function ShellPrefsProvider({\n value,\n children,\n}: { value: ShellPrefsAdapter; children: ReactNode }) {\n return <ShellPrefsContext.Provider value={value}>{children}</ShellPrefsContext.Provider>;\n}\n\n/** Default localStorage-backed adapter — useful when the consumer doesn't\n * ship a backend. Pass the result into <ShellPrefsProvider value={…}>.\n *\n * `defaults` are merged behind whatever's already stored, so they only\n * apply for keys the user hasn't set yet. Useful for opting out of bundled\n * features (e.g. `{ show_desktop_version: false }`). */\nexport function useLocalStoragePrefs(\n storageKey = 'react-os-shell:prefs',\n defaults?: Record<string, any>,\n): ShellPrefsAdapter {\n const [prefs, setPrefs] = useState<Record<string, any>>(() => {\n try {\n const stored = JSON.parse(localStorage.getItem(storageKey) || '{}');\n return { ...(defaults ?? {}), ...stored };\n } catch { return { ...(defaults ?? {}) }; }\n });\n const save = useCallback((patch: Record<string, any>) => {\n setPrefs(prev => {\n const next = { ...prev, ...patch };\n try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch {}\n return next;\n });\n }, [storageKey]);\n return { prefs, save };\n}\n\n/** Returns the active prefs adapter. When no provider is mounted, returns a\n * no-op adapter that reads as empty and silently drops saves — components\n * still render, but persistence is a no-op. */\nexport function useShellPrefs(): ShellPrefsAdapter {\n const ctx = useContext(ShellPrefsContext);\n if (ctx) return ctx;\n return { prefs: {}, save: () => {} };\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/forms/BrandMark.tsx
|
|
5
|
+
var SLOT_CLASS = {
|
|
6
|
+
favicon: "h-8 w-8",
|
|
7
|
+
compact: "h-10 w-10",
|
|
8
|
+
wordmark: "h-10 w-40"
|
|
9
|
+
};
|
|
10
|
+
function monogram(alt) {
|
|
11
|
+
return alt.trim().charAt(0).toLocaleUpperCase() || "\xB7";
|
|
12
|
+
}
|
|
13
|
+
function resolveBrandMarkTreatment(hasAlpha, isLight, surface) {
|
|
14
|
+
if (hasAlpha === false) return "framed";
|
|
15
|
+
if (hasAlpha == null || isLight == null) return "plate-light";
|
|
16
|
+
if (isLight) return surface === "light" ? "plate-dark" : "bare";
|
|
17
|
+
return surface === "dark" ? "plate-light" : "bare";
|
|
18
|
+
}
|
|
19
|
+
var TREATMENT_CLASS = {
|
|
20
|
+
"plate-light": "bg-white shadow-[0_2px_12px_rgba(0,0,0,0.18)]",
|
|
21
|
+
"plate-dark": "bg-neutral-900 shadow-[0_2px_12px_rgba(0,0,0,0.25)]",
|
|
22
|
+
framed: "border border-black/10 shadow-[0_2px_12px_rgba(0,0,0,0.18)]",
|
|
23
|
+
bare: ""
|
|
24
|
+
};
|
|
25
|
+
function treatmentStyle(treatment, radius, padding) {
|
|
26
|
+
if (treatment === "bare") return {};
|
|
27
|
+
return treatment === "framed" ? { borderRadius: radius } : { borderRadius: radius, padding };
|
|
28
|
+
}
|
|
29
|
+
function BrandMark({
|
|
30
|
+
src,
|
|
31
|
+
fallbackSrc,
|
|
32
|
+
fallbackHasAlpha = null,
|
|
33
|
+
fallbackIsLight = null,
|
|
34
|
+
fallbackAdaptive,
|
|
35
|
+
alt,
|
|
36
|
+
slot = "compact",
|
|
37
|
+
surface = "light",
|
|
38
|
+
size,
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
decorative = false,
|
|
42
|
+
fallbackMode = "monogram",
|
|
43
|
+
adaptive = false,
|
|
44
|
+
hasAlpha = null,
|
|
45
|
+
isLight = null,
|
|
46
|
+
treatmentPadding = 4,
|
|
47
|
+
treatmentRadius = 8,
|
|
48
|
+
className = "",
|
|
49
|
+
style
|
|
50
|
+
}) {
|
|
51
|
+
const [activeSrc, setActiveSrc] = useState(src || fallbackSrc || "");
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
setActiveSrc(src || fallbackSrc || "");
|
|
54
|
+
}, [src, fallbackSrc]);
|
|
55
|
+
const usingFallback = Boolean(
|
|
56
|
+
fallbackSrc && activeSrc === fallbackSrc && activeSrc !== src
|
|
57
|
+
);
|
|
58
|
+
const activeAdaptive = usingFallback ? fallbackAdaptive ?? adaptive : adaptive;
|
|
59
|
+
const activeHasAlpha = usingFallback ? fallbackHasAlpha : hasAlpha;
|
|
60
|
+
const activeIsLight = usingFallback ? fallbackIsLight : isLight;
|
|
61
|
+
const treatment = activeAdaptive ? resolveBrandMarkTreatment(activeHasAlpha, activeIsLight, surface) : "bare";
|
|
62
|
+
const frameClass = [
|
|
63
|
+
"inline-flex shrink-0 items-center justify-center overflow-hidden",
|
|
64
|
+
size == null && width == null && height == null ? SLOT_CLASS[slot] : "",
|
|
65
|
+
surface === "dark" ? "text-white" : "text-gray-700",
|
|
66
|
+
TREATMENT_CLASS[treatment],
|
|
67
|
+
className
|
|
68
|
+
].filter(Boolean).join(" ");
|
|
69
|
+
const frameStyle = {
|
|
70
|
+
...treatmentStyle(treatment, treatmentRadius, treatmentPadding),
|
|
71
|
+
...size == null ? { width, height } : { width: size, height: size },
|
|
72
|
+
...style
|
|
73
|
+
};
|
|
74
|
+
if (!activeSrc) {
|
|
75
|
+
if (fallbackMode === "none") return null;
|
|
76
|
+
return /* @__PURE__ */ jsx(
|
|
77
|
+
"span",
|
|
78
|
+
{
|
|
79
|
+
"data-brand-fallback": true,
|
|
80
|
+
"data-brand-slot": slot,
|
|
81
|
+
"aria-label": decorative ? void 0 : alt,
|
|
82
|
+
"aria-hidden": decorative || void 0,
|
|
83
|
+
role: decorative ? void 0 : "img",
|
|
84
|
+
className: `${frameClass} rounded-md bg-gray-100 font-semibold`,
|
|
85
|
+
style: frameStyle,
|
|
86
|
+
children: decorative ? null : monogram(alt)
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return /* @__PURE__ */ jsx("span", { className: frameClass, "data-brand-treatment": treatment, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
91
|
+
"img",
|
|
92
|
+
{
|
|
93
|
+
src: activeSrc,
|
|
94
|
+
alt: decorative ? "" : alt,
|
|
95
|
+
"data-brand-slot": slot,
|
|
96
|
+
className: "block max-h-full max-w-full",
|
|
97
|
+
style: { objectFit: "contain" },
|
|
98
|
+
onError: () => setActiveSrc((current) => fallbackSrc && current !== fallbackSrc ? fallbackSrc : "")
|
|
99
|
+
}
|
|
100
|
+
) });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { BrandMark, resolveBrandMarkTreatment };
|
|
104
|
+
//# sourceMappingURL=chunk-6XQHH7D2.js.map
|
|
105
|
+
//# sourceMappingURL=chunk-6XQHH7D2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forms/BrandMark.tsx"],"names":[],"mappings":"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","file":"chunk-6XQHH7D2.js","sourcesContent":["import { useEffect, useState, type CSSProperties } from 'react';\n\nexport type BrandMarkSlot = 'favicon' | 'compact' | 'wordmark';\nexport type BrandMarkSurface = 'light' | 'dark';\nexport type BrandMarkTreatment = 'plate-light' | 'plate-dark' | 'framed' | 'bare';\n\nexport interface BrandMarkProps {\n src?: string | null;\n fallbackSrc?: string | null;\n fallbackHasAlpha?: boolean | null;\n fallbackIsLight?: boolean | null;\n fallbackAdaptive?: boolean;\n alt: string;\n slot?: BrandMarkSlot;\n surface?: BrandMarkSurface;\n /** Override the square slot size in pixels for compact shell chrome. */\n size?: number;\n /** Override a natural-aspect slot width without forcing square dimensions. */\n width?: number | string;\n /** Override a natural-aspect slot height without forcing square dimensions. */\n height?: number | string;\n /** Mark the image decorative when the adjacent text already names it. */\n decorative?: boolean;\n /** Hide the slot after load failure when surrounding UI owns the fallback. */\n fallbackMode?: 'monogram' | 'none';\n /** Keep an arbitrary tenant mark readable without recolouring the artwork. */\n adaptive?: boolean;\n /** Server-detected transparency hint for the selected asset. */\n hasAlpha?: boolean | null;\n /** Server-detected tone hint for the selected asset. */\n isLight?: boolean | null;\n treatmentPadding?: number | string;\n treatmentRadius?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst SLOT_CLASS: Record<BrandMarkSlot, string> = {\n favicon: 'h-8 w-8',\n compact: 'h-10 w-10',\n wordmark: 'h-10 w-40',\n};\n\nfunction monogram(alt: string) {\n return alt.trim().charAt(0).toLocaleUpperCase() || '·';\n}\n\n/**\n * Chooses the smallest backing needed to preserve contrast. Opaque artwork\n * carries its own background; transparent artwork only needs a plate when its\n * tone would disappear into the current surface. Unknown artwork gets the\n * conservative light plate used by every portal.\n */\nexport function resolveBrandMarkTreatment(\n hasAlpha: boolean | null,\n isLight: boolean | null,\n surface: BrandMarkSurface,\n): BrandMarkTreatment {\n if (hasAlpha === false) return 'framed';\n if (hasAlpha == null || isLight == null) return 'plate-light';\n if (isLight) return surface === 'light' ? 'plate-dark' : 'bare';\n return surface === 'dark' ? 'plate-light' : 'bare';\n}\n\n/** The plate's colour and border are classes, so they follow the theme like\n * the rest of the file; only the caller-tunable radius and padding are\n * inline, because those arrive as numbers. */\nconst TREATMENT_CLASS: Record<BrandMarkTreatment, string> = {\n 'plate-light': 'bg-white shadow-[0_2px_12px_rgba(0,0,0,0.18)]',\n 'plate-dark': 'bg-neutral-900 shadow-[0_2px_12px_rgba(0,0,0,0.25)]',\n framed: 'border border-black/10 shadow-[0_2px_12px_rgba(0,0,0,0.18)]',\n bare: '',\n};\n\nfunction treatmentStyle(\n treatment: BrandMarkTreatment,\n radius: number,\n padding: number | string,\n): CSSProperties {\n if (treatment === 'bare') return {};\n return treatment === 'framed'\n ? { borderRadius: radius }\n : { borderRadius: radius, padding };\n}\n\n/**\n * Renders a brand asset inside a stable portal slot without ever cropping or\n * stretching the source. Raster normalisation belongs to the server; this\n * component owns only the presentation contract shared by portal surfaces.\n */\nexport default function BrandMark({\n src,\n fallbackSrc,\n fallbackHasAlpha = null,\n fallbackIsLight = null,\n fallbackAdaptive,\n alt,\n slot = 'compact',\n surface = 'light',\n size,\n width,\n height,\n decorative = false,\n fallbackMode = 'monogram',\n adaptive = false,\n hasAlpha = null,\n isLight = null,\n treatmentPadding = 4,\n treatmentRadius = 8,\n className = '',\n style,\n}: BrandMarkProps) {\n const [activeSrc, setActiveSrc] = useState(src || fallbackSrc || '');\n\n useEffect(() => {\n setActiveSrc(src || fallbackSrc || '');\n }, [src, fallbackSrc]);\n\n const usingFallback = Boolean(\n fallbackSrc && activeSrc === fallbackSrc && activeSrc !== src,\n );\n const activeAdaptive = usingFallback ? fallbackAdaptive ?? adaptive : adaptive;\n const activeHasAlpha = usingFallback ? fallbackHasAlpha : hasAlpha;\n const activeIsLight = usingFallback ? fallbackIsLight : isLight;\n const treatment = activeAdaptive\n ? resolveBrandMarkTreatment(activeHasAlpha, activeIsLight, surface)\n : 'bare';\n const frameClass = [\n 'inline-flex shrink-0 items-center justify-center overflow-hidden',\n size == null && width == null && height == null ? SLOT_CLASS[slot] : '',\n surface === 'dark' ? 'text-white' : 'text-gray-700',\n TREATMENT_CLASS[treatment],\n className,\n ].filter(Boolean).join(' ');\n const frameStyle: CSSProperties = {\n ...treatmentStyle(treatment, treatmentRadius, treatmentPadding),\n ...(size == null ? { width, height } : { width: size, height: size }),\n ...style,\n };\n\n if (!activeSrc) {\n if (fallbackMode === 'none') return null;\n return (\n <span\n data-brand-fallback\n data-brand-slot={slot}\n aria-label={decorative ? undefined : alt}\n aria-hidden={decorative || undefined}\n role={decorative ? undefined : 'img'}\n className={`${frameClass} rounded-md bg-gray-100 font-semibold`}\n style={frameStyle}\n >\n {decorative ? null : monogram(alt)}\n </span>\n );\n }\n\n return (\n <span className={frameClass} data-brand-treatment={treatment} style={frameStyle}>\n <img\n src={activeSrc}\n alt={decorative ? '' : alt}\n data-brand-slot={slot}\n className=\"block max-h-full max-w-full\"\n style={{ objectFit: 'contain' }}\n onError={() => setActiveSrc(current => (\n fallbackSrc && current !== fallbackSrc ? fallbackSrc : ''\n ))}\n />\n </span>\n );\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { navIcons } from './chunk-
|
|
1
|
+
import { navIcons } from './chunk-ZESNO4TZ.js';
|
|
2
2
|
import { isValidElement, cloneElement } from 'react';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -167,5 +167,5 @@ function consumeFilesViewRequest() {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export { DesktopItemMiniIcon, FILES_OPEN_DESKTOP_FOLDER_EVENT, FILES_SHOW_TRASH_EVENT, FileIconTile, FolderGlyph, consumeFilesViewRequest, desktopItemTypeLabel, getDesktopFoldersSnapshot, peekFilesViewRequest, publishDesktopFolders, requestFilesDesktopFolderView, requestFilesTrashView, subscribeDesktopFolders };
|
|
170
|
-
//# sourceMappingURL=chunk-
|
|
171
|
-
//# sourceMappingURL=chunk-
|
|
170
|
+
//# sourceMappingURL=chunk-7RUYFI6O.js.map
|
|
171
|
+
//# sourceMappingURL=chunk-7RUYFI6O.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/desktopIcons.tsx"],"names":[],"mappings":"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","file":"chunk-OQ7GRU7G.js","sourcesContent":["/**\n * Shared desktop-icon building blocks.\n *\n * Desktop.tsx renders the desktop surface; Files.tsx renders desktop shortcut\n * folders inside the Files app. Both import the icon tiles, entity-icon config\n * and the desktop-folder bridge from here so the surfaces never visually\n * diverge — and so Desktop and Files never import each other (that would be an\n * import cycle).\n */\nimport { isValidElement, cloneElement, type ReactElement, type ReactNode } from 'react';\nimport { navIcons } from '../shell-config/nav';\nimport type { PreviewFileKind } from '../utils/openPreviewFile';\n\n// ── Gradient tiles ──────────────────────────────────────────────────────────\n// Per-app colored tile background, shared by the mobile home grid and the\n// desktop \"page\" shortcuts. Tailwind's JIT scans the source so each gradient\n// class string must appear in full somewhere — keep them inline.\nexport const ICON_GRADIENTS = [\n 'from-blue-500 to-blue-700',\n 'from-indigo-500 to-purple-600',\n 'from-purple-500 to-pink-600',\n 'from-pink-500 to-rose-600',\n 'from-red-500 to-rose-600',\n 'from-orange-500 to-red-600',\n 'from-amber-500 to-orange-600',\n 'from-yellow-500 to-amber-500',\n 'from-lime-500 to-green-600',\n 'from-green-500 to-emerald-600',\n 'from-emerald-500 to-teal-600',\n 'from-teal-500 to-cyan-600',\n 'from-cyan-500 to-sky-600',\n 'from-sky-500 to-blue-600',\n 'from-violet-500 to-fuchsia-600',\n];\n\nexport function hashGradient(seed: string): string {\n let h = 0;\n for (let i = 0; i < seed.length; i++) h = (h << 5) - h + seed.charCodeAt(i);\n return ICON_GRADIENTS[Math.abs(h) % ICON_GRADIENTS.length];\n}\n\n// ── Entity icon config ──────────────────────────────────────────────────────\nexport const ENTITY_ICON_COLORS: Record<string, string> = {\n order: 'text-blue-600', purchase_order: 'text-purple-600', invoice: 'text-green-600',\n client: 'text-indigo-600', manufacturer: 'text-orange-600', shipment: 'text-teal-600',\n part_number: 'text-gray-600', project: 'text-pink-600', mould: 'text-red-600',\n design: 'text-cyan-600', brand: 'text-amber-600', price_sheet: 'text-emerald-600',\n folder: 'text-yellow-600', page: 'text-blue-500',\n};\nexport const ENTITY_ICONS: Record<string, string> = {\n order: 'SO', purchase_order: 'PO', invoice: 'INV', client: 'CLI',\n manufacturer: 'MFR', shipment: 'DN', part_number: 'PN', project: 'PRJ',\n mould: 'MLD', design: 'DSN', brand: 'BRD', price_sheet: 'PS',\n vendor_invoice: 'VI', vendor_payment: 'VP', warranty_claim: 'WC',\n qc_report: 'QC', vendor_shipment: 'GRN', bank_account: 'BA',\n wheel_finish: 'WF', weight_log: 'WL', production_progress: 'PP',\n vendor_price_sheet: 'VPS', proposal: 'PR', folder: 'FLD',\n};\n\n// Glyphs and colors for the auto-tracked preview shortcuts that live in the\n// Documents folder. Keyed by `fileKind`, not `entityType`.\nexport const PREVIEW_FILE_CODES: Record<PreviewFileKind, string> = {\n pdf: 'PDF', dxf: 'DXF', '3d': 'STP', image: 'IMG', csv: 'CSV',\n};\nexport const PREVIEW_FILE_COLORS: Record<PreviewFileKind, string> = {\n pdf: 'text-red-600', dxf: 'text-blue-600', '3d': 'text-purple-600',\n image: 'text-emerald-600', csv: 'text-green-600',\n};\n\nexport interface DesktopItem {\n entityType: string;\n entityId: string;\n label: string;\n x?: number;\n y?: number;\n folderId?: string; // if inside a folder\n // Legacy free-form position inside the old folder window (pixels, relative\n // to the folder body). Folders now open in the Files app, which lists items\n // in a table — kept so previously-persisted shortcuts keep parsing.\n folderX?: number;\n folderY?: number;\n // Set for `entityType: 'preview-file'` shortcuts auto-recorded when the\n // user previews a file. `filePath` is the server-relative path passed\n // back into `openPreviewFile` on shortcut click.\n filePath?: string;\n fileKind?: PreviewFileKind;\n}\n\n/** Solid amber folder — the exact glyph the Files app uses for folders, so\n * desktop folders and Files-app folders read as the same object. */\nexport function FolderGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M2.25 7.125A2.25 2.25 0 014.5 4.875h4.504c.61 0 1.193.243 1.624.673l1.494 1.494a.75.75 0 00.53.22h7.098A2.25 2.25 0 0122 9.51v8.366A2.25 2.25 0 0119.75 20.125H4.25A2.25 2.25 0 012 17.875V7.125z\" />\n </svg>\n );\n}\n\n/** Page shortcuts reuse the consumer-registered nav icons; historically the\n * label was tried first, then the route. */\nfunction pageGlyph(entityId?: string, label?: string): ReactNode {\n return (label && navIcons[label]) || (entityId ? navIcons[entityId] : undefined);\n}\n\nconst PAGE_FALLBACK_GLYPH = (\n <svg className=\"h-7 w-7\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6z\" />\n </svg>\n);\n\n// Shared 48×48 tile used by the desktop icon renderer (and anything else\n// that needs a full-size shortcut tile), so all surfaces stay in sync.\nexport function FileIconTile({ entityType, isSelected, entityId, label, fileKind }: {\n entityType: string;\n isSelected: boolean;\n entityId?: string;\n label?: string;\n fileKind?: PreviewFileKind;\n}) {\n const isPreviewFile = entityType === 'preview-file' && fileKind;\n const previewColor = isPreviewFile ? PREVIEW_FILE_COLORS[fileKind!] : null;\n const previewCode = isPreviewFile ? PREVIEW_FILE_CODES[fileKind!] : null;\n if (entityType === 'folder') {\n return (\n <div className={`w-12 h-12 flex items-center justify-center ${isSelected ? 'rounded-lg bg-blue-400/30 ring-2 ring-blue-500' : ''}`}>\n <FolderGlyph className=\"h-12 w-12 text-amber-500 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]\" />\n </div>\n );\n }\n if (entityType === 'page') {\n // App shortcut — colored gradient tile with the white nav glyph, same\n // treatment as the mobile home grid (and the same hash seed, the route,\n // so an app keeps its color across surfaces).\n const icon = pageGlyph(entityId, label);\n const gradient = hashGradient(entityId || label || 'page');\n return (\n <div className={`w-12 h-12 flex items-center justify-center ${isSelected ? 'rounded-lg bg-blue-400/30 ring-2 ring-blue-500' : ''}`}>\n <span className={`h-11 w-11 rounded-xl bg-gradient-to-br ${gradient} flex items-center justify-center text-white border border-white/30 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]`}>\n {icon && isValidElement(icon)\n ? cloneElement(icon as ReactElement, { className: 'h-7 w-7 text-white' })\n : PAGE_FALLBACK_GLYPH}\n </span>\n </div>\n );\n }\n return (\n <div className={`w-12 h-12 relative flex items-center justify-center ${isSelected ? 'rounded-lg bg-blue-400/30 ring-2 ring-blue-500' : ''}`}>\n <svg className={`w-10 h-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)] ${previewColor ?? ENTITY_ICON_COLORS[entityType] ?? 'text-gray-500'}`} viewBox=\"0 0 40 48\" fill=\"none\">\n <path d=\"M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z\" fill=\"white\" fillOpacity=\"0.92\" />\n <path d=\"M26 0l10 10H30a4 4 0 01-4-4V0z\" fill=\"currentColor\" fillOpacity=\"0.2\" />\n <path d=\"M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeOpacity=\"0.5\" />\n </svg>\n <span className={`absolute inset-0 flex items-center justify-center text-[9px] font-bold pt-2 ${previewColor ?? ENTITY_ICON_COLORS[entityType] ?? 'text-gray-600'}`}>\n {previewCode ?? ENTITY_ICONS[entityType] ?? entityType.slice(0, 3).toUpperCase()}\n </span>\n </div>\n );\n}\n\n/** Compact row icon for desktop shortcuts listed inside the Files app. */\nexport function DesktopItemMiniIcon({ item }: { item: DesktopItem }) {\n if (item.entityType === 'folder') {\n return <FolderGlyph className=\"h-4 w-4 shrink-0 text-amber-500\" />;\n }\n if (item.entityType === 'page') {\n const icon = pageGlyph(item.entityId, item.label);\n return (\n <span className={`h-4 w-4 rounded bg-gradient-to-br ${hashGradient(item.entityId || item.label || 'page')} flex items-center justify-center text-white shrink-0`}>\n {icon && isValidElement(icon)\n ? cloneElement(icon as ReactElement, { className: 'h-3 w-3 text-white' })\n : <span className=\"h-1.5 w-1.5 rounded-[2px] border border-white/80\" />}\n </span>\n );\n }\n const color = (item.entityType === 'preview-file' && item.fileKind\n ? PREVIEW_FILE_COLORS[item.fileKind]\n : ENTITY_ICON_COLORS[item.entityType]) ?? 'text-gray-400';\n return (\n <svg className={`h-4 w-4 shrink-0 ${color}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z\" />\n </svg>\n );\n}\n\n/** Short type tag shown in the Files-app listing of a desktop folder. */\nexport function desktopItemTypeLabel(item: DesktopItem): string {\n if (item.entityType === 'preview-file' && item.fileKind) return PREVIEW_FILE_CODES[item.fileKind];\n if (item.entityType === 'page') return 'App';\n if (item.entityType === 'folder') return 'Folder';\n return ENTITY_ICONS[item.entityType] ?? item.entityType.slice(0, 3).toUpperCase();\n}\n\n// ── Desktop-folder bridge ───────────────────────────────────────────────────\n//\n// Desktop folders are virtual — shortcut collections persisted in the user's\n// prefs, not directories on the file server. The Files app shows them anyway\n// (sidebar section + folder listing), so Desktop publishes its live folder\n// state here and Files subscribes via useSyncExternalStore. Mutations route\n// back through the callbacks so persistence stays in Desktop's host-aware\n// save path.\n\nexport interface DesktopFoldersSnapshot {\n folders: { id: string; name: string; itemCount: number }[];\n itemsByFolder: Record<string, DesktopItem[]>;\n /** Clear `folderId` on these shortcuts — they reappear on the desktop. */\n moveToDesktop: (items: DesktopItem[]) => void;\n /** Delete these shortcuts entirely. */\n removeShortcuts: (items: DesktopItem[]) => void;\n}\n\nlet desktopFoldersSnapshot: DesktopFoldersSnapshot | null = null;\nconst desktopFoldersListeners = new Set<() => void>();\n\nexport function publishDesktopFolders(next: DesktopFoldersSnapshot | null) {\n desktopFoldersSnapshot = next;\n desktopFoldersListeners.forEach(l => l());\n}\n\nexport function subscribeDesktopFolders(listener: () => void): () => void {\n desktopFoldersListeners.add(listener);\n return () => { desktopFoldersListeners.delete(listener); };\n}\n\nexport function getDesktopFoldersSnapshot(): DesktopFoldersSnapshot | null {\n return desktopFoldersSnapshot;\n}\n\n// ── Files-app view side channel ─────────────────────────────────────────────\n//\n// Used by the desktop Trash icon and desktop folders to open the Files app\n// on a specific view. Two cases handled:\n// 1) Files isn't open yet — the flag below is read once on first mount.\n// 2) Files is already open — the event tells the live instance to flip view.\n// The caller follows up with `openPage('/files')` either way.\n\nexport type FilesViewRequest = 'trash' | { view: 'desktop-folder'; folderId: string };\n\nexport const FILES_VIEW_FLAG = '__REACT_OS_SHELL_FILES_VIEW__';\nexport const FILES_SHOW_TRASH_EVENT = 'react-os-shell:files-show-trash';\nexport const FILES_OPEN_DESKTOP_FOLDER_EVENT = 'react-os-shell:files-open-desktop-folder';\n\nexport function requestFilesTrashView() {\n if (typeof window === 'undefined') return;\n (window as any)[FILES_VIEW_FLAG] = 'trash' satisfies FilesViewRequest;\n window.dispatchEvent(new CustomEvent(FILES_SHOW_TRASH_EVENT));\n}\n\nexport function requestFilesDesktopFolderView(folderId: string) {\n if (typeof window === 'undefined') return;\n (window as any)[FILES_VIEW_FLAG] = { view: 'desktop-folder', folderId } satisfies FilesViewRequest;\n window.dispatchEvent(new CustomEvent(FILES_OPEN_DESKTOP_FOLDER_EVENT, { detail: { folderId } }));\n}\n\n/** Read the pending view request without clearing it. Side-effect free so\n * it is safe inside a `useState` initializer (StrictMode double-invokes\n * those; a read-and-clear there would hand the second invocation null). */\nexport function peekFilesViewRequest(): FilesViewRequest | null {\n if (typeof window === 'undefined') return null;\n const raw = (window as any)[FILES_VIEW_FLAG];\n if (raw === 'trash') return 'trash';\n if (raw && typeof raw === 'object' && raw.view === 'desktop-folder' && typeof raw.folderId === 'string') return raw;\n return null;\n}\n\n/** Read-and-clear the pending view request (Files calls this once mounted). */\nexport function consumeFilesViewRequest(): FilesViewRequest | null {\n const req = peekFilesViewRequest();\n if (typeof window !== 'undefined') (window as any)[FILES_VIEW_FLAG] = null;\n return req;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shell/desktopIcons.tsx"],"names":[],"mappings":"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","file":"chunk-7RUYFI6O.js","sourcesContent":["/**\n * Shared desktop-icon building blocks.\n *\n * Desktop.tsx renders the desktop surface; Files.tsx renders desktop shortcut\n * folders inside the Files app. Both import the icon tiles, entity-icon config\n * and the desktop-folder bridge from here so the surfaces never visually\n * diverge — and so Desktop and Files never import each other (that would be an\n * import cycle).\n */\nimport { isValidElement, cloneElement, type ReactElement, type ReactNode } from 'react';\nimport { navIcons } from '../shell-config/nav';\nimport type { PreviewFileKind } from '../utils/openPreviewFile';\n\n// ── Gradient tiles ──────────────────────────────────────────────────────────\n// Per-app colored tile background, shared by the mobile home grid and the\n// desktop \"page\" shortcuts. Tailwind's JIT scans the source so each gradient\n// class string must appear in full somewhere — keep them inline.\nexport const ICON_GRADIENTS = [\n 'from-blue-500 to-blue-700',\n 'from-indigo-500 to-purple-600',\n 'from-purple-500 to-pink-600',\n 'from-pink-500 to-rose-600',\n 'from-red-500 to-rose-600',\n 'from-orange-500 to-red-600',\n 'from-amber-500 to-orange-600',\n 'from-yellow-500 to-amber-500',\n 'from-lime-500 to-green-600',\n 'from-green-500 to-emerald-600',\n 'from-emerald-500 to-teal-600',\n 'from-teal-500 to-cyan-600',\n 'from-cyan-500 to-sky-600',\n 'from-sky-500 to-blue-600',\n 'from-violet-500 to-fuchsia-600',\n];\n\nexport function hashGradient(seed: string): string {\n let h = 0;\n for (let i = 0; i < seed.length; i++) h = (h << 5) - h + seed.charCodeAt(i);\n return ICON_GRADIENTS[Math.abs(h) % ICON_GRADIENTS.length];\n}\n\n// ── Entity icon config ──────────────────────────────────────────────────────\nexport const ENTITY_ICON_COLORS: Record<string, string> = {\n order: 'text-blue-600', purchase_order: 'text-purple-600', invoice: 'text-green-600',\n client: 'text-indigo-600', manufacturer: 'text-orange-600', shipment: 'text-teal-600',\n part_number: 'text-gray-600', project: 'text-pink-600', mould: 'text-red-600',\n design: 'text-cyan-600', brand: 'text-amber-600', price_sheet: 'text-emerald-600',\n folder: 'text-yellow-600', page: 'text-blue-500',\n};\nexport const ENTITY_ICONS: Record<string, string> = {\n order: 'SO', purchase_order: 'PO', invoice: 'INV', client: 'CLI',\n manufacturer: 'MFR', shipment: 'DN', part_number: 'PN', project: 'PRJ',\n mould: 'MLD', design: 'DSN', brand: 'BRD', price_sheet: 'PS',\n vendor_invoice: 'VI', vendor_payment: 'VP', warranty_claim: 'WC',\n qc_report: 'QC', vendor_shipment: 'GRN', bank_account: 'BA',\n wheel_finish: 'WF', weight_log: 'WL', production_progress: 'PP',\n vendor_price_sheet: 'VPS', proposal: 'PR', folder: 'FLD',\n};\n\n// Glyphs and colors for the auto-tracked preview shortcuts that live in the\n// Documents folder. Keyed by `fileKind`, not `entityType`.\nexport const PREVIEW_FILE_CODES: Record<PreviewFileKind, string> = {\n pdf: 'PDF', dxf: 'DXF', '3d': 'STP', image: 'IMG', csv: 'CSV',\n};\nexport const PREVIEW_FILE_COLORS: Record<PreviewFileKind, string> = {\n pdf: 'text-red-600', dxf: 'text-blue-600', '3d': 'text-purple-600',\n image: 'text-emerald-600', csv: 'text-green-600',\n};\n\nexport interface DesktopItem {\n entityType: string;\n entityId: string;\n label: string;\n x?: number;\n y?: number;\n folderId?: string; // if inside a folder\n // Legacy free-form position inside the old folder window (pixels, relative\n // to the folder body). Folders now open in the Files app, which lists items\n // in a table — kept so previously-persisted shortcuts keep parsing.\n folderX?: number;\n folderY?: number;\n // Set for `entityType: 'preview-file'` shortcuts auto-recorded when the\n // user previews a file. `filePath` is the server-relative path passed\n // back into `openPreviewFile` on shortcut click.\n filePath?: string;\n fileKind?: PreviewFileKind;\n}\n\n/** Solid amber folder — the exact glyph the Files app uses for folders, so\n * desktop folders and Files-app folders read as the same object. */\nexport function FolderGlyph({ className }: { className?: string }) {\n return (\n <svg className={className} fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M2.25 7.125A2.25 2.25 0 014.5 4.875h4.504c.61 0 1.193.243 1.624.673l1.494 1.494a.75.75 0 00.53.22h7.098A2.25 2.25 0 0122 9.51v8.366A2.25 2.25 0 0119.75 20.125H4.25A2.25 2.25 0 012 17.875V7.125z\" />\n </svg>\n );\n}\n\n/** Page shortcuts reuse the consumer-registered nav icons; historically the\n * label was tried first, then the route. */\nfunction pageGlyph(entityId?: string, label?: string): ReactNode {\n return (label && navIcons[label]) || (entityId ? navIcons[entityId] : undefined);\n}\n\nconst PAGE_FALLBACK_GLYPH = (\n <svg className=\"h-7 w-7\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6z\" />\n </svg>\n);\n\n// Shared 48×48 tile used by the desktop icon renderer (and anything else\n// that needs a full-size shortcut tile), so all surfaces stay in sync.\nexport function FileIconTile({ entityType, isSelected, entityId, label, fileKind }: {\n entityType: string;\n isSelected: boolean;\n entityId?: string;\n label?: string;\n fileKind?: PreviewFileKind;\n}) {\n const isPreviewFile = entityType === 'preview-file' && fileKind;\n const previewColor = isPreviewFile ? PREVIEW_FILE_COLORS[fileKind!] : null;\n const previewCode = isPreviewFile ? PREVIEW_FILE_CODES[fileKind!] : null;\n if (entityType === 'folder') {\n return (\n <div className={`w-12 h-12 flex items-center justify-center ${isSelected ? 'rounded-lg bg-blue-400/30 ring-2 ring-blue-500' : ''}`}>\n <FolderGlyph className=\"h-12 w-12 text-amber-500 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]\" />\n </div>\n );\n }\n if (entityType === 'page') {\n // App shortcut — colored gradient tile with the white nav glyph, same\n // treatment as the mobile home grid (and the same hash seed, the route,\n // so an app keeps its color across surfaces).\n const icon = pageGlyph(entityId, label);\n const gradient = hashGradient(entityId || label || 'page');\n return (\n <div className={`w-12 h-12 flex items-center justify-center ${isSelected ? 'rounded-lg bg-blue-400/30 ring-2 ring-blue-500' : ''}`}>\n <span className={`h-11 w-11 rounded-xl bg-gradient-to-br ${gradient} flex items-center justify-center text-white border border-white/30 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)]`}>\n {icon && isValidElement(icon)\n ? cloneElement(icon as ReactElement, { className: 'h-7 w-7 text-white' })\n : PAGE_FALLBACK_GLYPH}\n </span>\n </div>\n );\n }\n return (\n <div className={`w-12 h-12 relative flex items-center justify-center ${isSelected ? 'rounded-lg bg-blue-400/30 ring-2 ring-blue-500' : ''}`}>\n <svg className={`w-10 h-12 drop-shadow-[0_2px_3px_rgba(0,0,0,0.3)] ${previewColor ?? ENTITY_ICON_COLORS[entityType] ?? 'text-gray-500'}`} viewBox=\"0 0 40 48\" fill=\"none\">\n <path d=\"M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z\" fill=\"white\" fillOpacity=\"0.92\" />\n <path d=\"M26 0l10 10H30a4 4 0 01-4-4V0z\" fill=\"currentColor\" fillOpacity=\"0.2\" />\n <path d=\"M4 0h22l10 10v34a4 4 0 01-4 4H4a4 4 0 01-4-4V4a4 4 0 014-4z\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeOpacity=\"0.5\" />\n </svg>\n <span className={`absolute inset-0 flex items-center justify-center text-[9px] font-bold pt-2 ${previewColor ?? ENTITY_ICON_COLORS[entityType] ?? 'text-gray-600'}`}>\n {previewCode ?? ENTITY_ICONS[entityType] ?? entityType.slice(0, 3).toUpperCase()}\n </span>\n </div>\n );\n}\n\n/** Compact row icon for desktop shortcuts listed inside the Files app. */\nexport function DesktopItemMiniIcon({ item }: { item: DesktopItem }) {\n if (item.entityType === 'folder') {\n return <FolderGlyph className=\"h-4 w-4 shrink-0 text-amber-500\" />;\n }\n if (item.entityType === 'page') {\n const icon = pageGlyph(item.entityId, item.label);\n return (\n <span className={`h-4 w-4 rounded bg-gradient-to-br ${hashGradient(item.entityId || item.label || 'page')} flex items-center justify-center text-white shrink-0`}>\n {icon && isValidElement(icon)\n ? cloneElement(icon as ReactElement, { className: 'h-3 w-3 text-white' })\n : <span className=\"h-1.5 w-1.5 rounded-[2px] border border-white/80\" />}\n </span>\n );\n }\n const color = (item.entityType === 'preview-file' && item.fileKind\n ? PREVIEW_FILE_COLORS[item.fileKind]\n : ENTITY_ICON_COLORS[item.entityType]) ?? 'text-gray-400';\n return (\n <svg className={`h-4 w-4 shrink-0 ${color}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z\" />\n </svg>\n );\n}\n\n/** Short type tag shown in the Files-app listing of a desktop folder. */\nexport function desktopItemTypeLabel(item: DesktopItem): string {\n if (item.entityType === 'preview-file' && item.fileKind) return PREVIEW_FILE_CODES[item.fileKind];\n if (item.entityType === 'page') return 'App';\n if (item.entityType === 'folder') return 'Folder';\n return ENTITY_ICONS[item.entityType] ?? item.entityType.slice(0, 3).toUpperCase();\n}\n\n// ── Desktop-folder bridge ───────────────────────────────────────────────────\n//\n// Desktop folders are virtual — shortcut collections persisted in the user's\n// prefs, not directories on the file server. The Files app shows them anyway\n// (sidebar section + folder listing), so Desktop publishes its live folder\n// state here and Files subscribes via useSyncExternalStore. Mutations route\n// back through the callbacks so persistence stays in Desktop's host-aware\n// save path.\n\nexport interface DesktopFoldersSnapshot {\n folders: { id: string; name: string; itemCount: number }[];\n itemsByFolder: Record<string, DesktopItem[]>;\n /** Clear `folderId` on these shortcuts — they reappear on the desktop. */\n moveToDesktop: (items: DesktopItem[]) => void;\n /** Delete these shortcuts entirely. */\n removeShortcuts: (items: DesktopItem[]) => void;\n}\n\nlet desktopFoldersSnapshot: DesktopFoldersSnapshot | null = null;\nconst desktopFoldersListeners = new Set<() => void>();\n\nexport function publishDesktopFolders(next: DesktopFoldersSnapshot | null) {\n desktopFoldersSnapshot = next;\n desktopFoldersListeners.forEach(l => l());\n}\n\nexport function subscribeDesktopFolders(listener: () => void): () => void {\n desktopFoldersListeners.add(listener);\n return () => { desktopFoldersListeners.delete(listener); };\n}\n\nexport function getDesktopFoldersSnapshot(): DesktopFoldersSnapshot | null {\n return desktopFoldersSnapshot;\n}\n\n// ── Files-app view side channel ─────────────────────────────────────────────\n//\n// Used by the desktop Trash icon and desktop folders to open the Files app\n// on a specific view. Two cases handled:\n// 1) Files isn't open yet — the flag below is read once on first mount.\n// 2) Files is already open — the event tells the live instance to flip view.\n// The caller follows up with `openPage('/files')` either way.\n\nexport type FilesViewRequest = 'trash' | { view: 'desktop-folder'; folderId: string };\n\nexport const FILES_VIEW_FLAG = '__REACT_OS_SHELL_FILES_VIEW__';\nexport const FILES_SHOW_TRASH_EVENT = 'react-os-shell:files-show-trash';\nexport const FILES_OPEN_DESKTOP_FOLDER_EVENT = 'react-os-shell:files-open-desktop-folder';\n\nexport function requestFilesTrashView() {\n if (typeof window === 'undefined') return;\n (window as any)[FILES_VIEW_FLAG] = 'trash' satisfies FilesViewRequest;\n window.dispatchEvent(new CustomEvent(FILES_SHOW_TRASH_EVENT));\n}\n\nexport function requestFilesDesktopFolderView(folderId: string) {\n if (typeof window === 'undefined') return;\n (window as any)[FILES_VIEW_FLAG] = { view: 'desktop-folder', folderId } satisfies FilesViewRequest;\n window.dispatchEvent(new CustomEvent(FILES_OPEN_DESKTOP_FOLDER_EVENT, { detail: { folderId } }));\n}\n\n/** Read the pending view request without clearing it. Side-effect free so\n * it is safe inside a `useState` initializer (StrictMode double-invokes\n * those; a read-and-clear there would hand the second invocation null). */\nexport function peekFilesViewRequest(): FilesViewRequest | null {\n if (typeof window === 'undefined') return null;\n const raw = (window as any)[FILES_VIEW_FLAG];\n if (raw === 'trash') return 'trash';\n if (raw && typeof raw === 'object' && raw.view === 'desktop-folder' && typeof raw.folderId === 'string') return raw;\n return null;\n}\n\n/** Read-and-clear the pending view request (Files calls this once mounted). */\nexport function consumeFilesViewRequest(): FilesViewRequest | null {\n const req = peekFilesViewRequest();\n if (typeof window !== 'undefined') (window as any)[FILES_VIEW_FLAG] = null;\n return req;\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toast_default } from './chunk-2VM2FRZJ.js';
|
|
2
|
-
import { useShellPrefs } from './chunk-
|
|
2
|
+
import { useShellPrefs } from './chunk-5TLIIDRZ.js';
|
|
3
3
|
import { useSyncExternalStore, useCallback, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
// src/shell/pomodoroStore.ts
|
|
@@ -545,5 +545,5 @@ function migratePomodoroTasksOnce(currentTasks, setAllTasks) {
|
|
|
545
545
|
}
|
|
546
546
|
|
|
547
547
|
export { ALARM_OPTIONS, FOCUS_SOUND_OPTIONS, getPomoSnapshot, migratePomodoroTasksOnce, playAlarm, pomoPause, pomoStart, pomoSwitchMode, previewFocusSound, setPomoAlarm, setPomoAlarmConfig, setPomoBehaviour, setPomoDurations, setPomoFocusSound, setShellTodoProvider, subscribePomo, useTodoTasks };
|
|
548
|
-
//# sourceMappingURL=chunk-
|
|
549
|
-
//# sourceMappingURL=chunk-
|
|
548
|
+
//# sourceMappingURL=chunk-IZ2FABWU.js.map
|
|
549
|
+
//# sourceMappingURL=chunk-IZ2FABWU.js.map
|