react-os-shell 0.4.0 → 0.6.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.
Files changed (54) hide show
  1. package/dist/{Browser-6JQTSHQF.js → Browser-XZEAEJLW.js} +4 -4
  2. package/dist/{Browser-6JQTSHQF.js.map → Browser-XZEAEJLW.js.map} +1 -1
  3. package/dist/{Calculator-6ANBG6S2.js → Calculator-Y7TKDGFS.js} +5 -5
  4. package/dist/{Calculator-6ANBG6S2.js.map → Calculator-Y7TKDGFS.js.map} +1 -1
  5. package/dist/{Calendar-R4IBIPIU.js → Calendar-LUQWUGFY.js} +4 -4
  6. package/dist/{Calendar-R4IBIPIU.js.map → Calendar-LUQWUGFY.js.map} +1 -1
  7. package/dist/{CurrencyConverter-RVLGYNDE.js → CurrencyConverter-GZFBW5PE.js} +5 -5
  8. package/dist/{CurrencyConverter-RVLGYNDE.js.map → CurrencyConverter-GZFBW5PE.js.map} +1 -1
  9. package/dist/{Documents-AUK2YPDI.js → Documents-IT4EQ5CF.js} +4 -4
  10. package/dist/{Documents-AUK2YPDI.js.map → Documents-IT4EQ5CF.js.map} +1 -1
  11. package/dist/{Email-7FJHS6Y7.js → Email-SNA6KIKR.js} +4 -4
  12. package/dist/{Email-7FJHS6Y7.js.map → Email-SNA6KIKR.js.map} +1 -1
  13. package/dist/Files-E4XEDYAP.js +11 -0
  14. package/dist/{Files-PGN4HKTC.js.map → Files-E4XEDYAP.js.map} +1 -1
  15. package/dist/{Minesweeper-ZDK33A6S.js → Minesweeper-6GUVR7CV.js} +4 -4
  16. package/dist/{Minesweeper-ZDK33A6S.js.map → Minesweeper-6GUVR7CV.js.map} +1 -1
  17. package/dist/{Notepad-SRHBJ75G.js → Notepad-PQHDQH2L.js} +4 -4
  18. package/dist/{Notepad-SRHBJ75G.js.map → Notepad-PQHDQH2L.js.map} +1 -1
  19. package/dist/{PomodoroTimer-OJMQVA4G.js → PomodoroTimer-3W7AYUDV.js} +5 -5
  20. package/dist/{PomodoroTimer-OJMQVA4G.js.map → PomodoroTimer-3W7AYUDV.js.map} +1 -1
  21. package/dist/Preview-BUVU7RTT.js +8 -0
  22. package/dist/{Preview-Z5HQ4O3X.js.map → Preview-BUVU7RTT.js.map} +1 -1
  23. package/dist/{Sidebar-4V6NQROQ.js → Sidebar-Q3PRJ2FP.js} +74 -23
  24. package/dist/Sidebar-Q3PRJ2FP.js.map +1 -0
  25. package/dist/Spreadsheet-G4HT4C7B.js +6 -0
  26. package/dist/{Spreadsheet-UWKFN6PH.js.map → Spreadsheet-G4HT4C7B.js.map} +1 -1
  27. package/dist/{Weather-ZYNKEAOJ.js → Weather-CXK57AZW.js} +5 -5
  28. package/dist/{Weather-ZYNKEAOJ.js.map → Weather-CXK57AZW.js.map} +1 -1
  29. package/dist/{WorldClock-A6P3MXEI.js → WorldClock-LBWQKKOA.js} +5 -5
  30. package/dist/{WorldClock-A6P3MXEI.js.map → WorldClock-LBWQKKOA.js.map} +1 -1
  31. package/dist/apps/index.js +20 -20
  32. package/dist/{chunk-GI7ABQPU.js → chunk-3RQ5TVEL.js} +3 -3
  33. package/dist/{chunk-GI7ABQPU.js.map → chunk-3RQ5TVEL.js.map} +1 -1
  34. package/dist/{chunk-NRT2CU5Q.js → chunk-HG3O6XHN.js} +3 -3
  35. package/dist/{chunk-NRT2CU5Q.js.map → chunk-HG3O6XHN.js.map} +1 -1
  36. package/dist/{chunk-Z4JTKA7I.js → chunk-KSRZU3GT.js} +3 -3
  37. package/dist/{chunk-Z4JTKA7I.js.map → chunk-KSRZU3GT.js.map} +1 -1
  38. package/dist/{chunk-5PZGJUQX.js → chunk-MGPY5446.js} +4 -4
  39. package/dist/{chunk-5PZGJUQX.js.map → chunk-MGPY5446.js.map} +1 -1
  40. package/dist/{chunk-6YY6A6SV.js → chunk-NVAWKCAI.js} +3 -3
  41. package/dist/{chunk-6YY6A6SV.js.map → chunk-NVAWKCAI.js.map} +1 -1
  42. package/dist/{chunk-LZCEK7JF.js → chunk-XNXIIGHP.js} +4 -4
  43. package/dist/{chunk-LZCEK7JF.js.map → chunk-XNXIIGHP.js.map} +1 -1
  44. package/dist/{chunk-SSA762W5.js → chunk-ZF6AYO4G.js} +3 -3
  45. package/dist/chunk-ZF6AYO4G.js.map +1 -0
  46. package/dist/index.d.ts +16 -1
  47. package/dist/index.js +160 -39
  48. package/dist/index.js.map +1 -1
  49. package/package.json +1 -1
  50. package/dist/Files-PGN4HKTC.js +0 -11
  51. package/dist/Preview-Z5HQ4O3X.js +0 -8
  52. package/dist/Sidebar-4V6NQROQ.js.map +0 -1
  53. package/dist/Spreadsheet-UWKFN6PH.js +0 -6
  54. package/dist/chunk-SSA762W5.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { setPdfPreview } from './chunk-NRT2CU5Q.js';
1
+ import { setPdfPreview } from './chunk-HG3O6XHN.js';
2
2
  import { toast_default } from './chunk-WIJ45SYD.js';
3
- import { setSpreadsheetPreview } from './chunk-6YY6A6SV.js';
3
+ import { setSpreadsheetPreview } from './chunk-NVAWKCAI.js';
4
4
 
5
5
  // src/utils/openPreviewFile.ts
6
6
  var PREVIEW_OPENED_EVENT = "react-os-shell:preview-opened";
@@ -42,5 +42,5 @@ async function openPreviewFile(opts) {
42
42
  }
43
43
 
44
44
  export { PREVIEW_OPENED_EVENT, openPreviewFile };
45
- //# sourceMappingURL=chunk-LZCEK7JF.js.map
46
- //# sourceMappingURL=chunk-LZCEK7JF.js.map
45
+ //# sourceMappingURL=chunk-XNXIIGHP.js.map
46
+ //# sourceMappingURL=chunk-XNXIIGHP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/openPreviewFile.ts"],"names":[],"mappings":";;;;;AA6BO,IAAM,oBAAA,GAAuB;AAQpC,SAAS,SAAA,GAAY;AACnB,EAAA,MAAM,QAAA,GAAY,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAAe,8BAAA;AACnE,EAAA,OAAA,CAAQ,QAAA,IAAY,uBAAA,EAAyB,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAChE;AAEA,eAAsB,gBAAgB,IAAA,EAA6C;AACjF,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA,EAAM,UAAS,GAAI,IAAA;AAC/C,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA;AAAA,MAChB,GAAG,SAAA,EAAW,CAAA,eAAA,EAAkB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA,MAC5D,EAAE,aAAa,SAAA;AAAU,KAC3B;AACA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,aAAA,CAAM,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAC7C,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,EAAK;AAC7B,MAAA,qBAAA,CAAsB,EAAE,GAAA,EAAK,IAAA,EAAM,QAAA,EAAU,CAAA;AAC7C,MAAA,QAAA,GAAW,cAAc,CAAA;AAAA,IAC3B,CAAA,MAAO;AACL,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,MAAA,aAAA,CAAc,EAAE,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,CAAA;AACrC,MAAA,QAAA,GAAW,UAAU,CAAA;AAAA,IACvB;AACA,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAiC,oBAAA,EAAsB;AAAA,QAC9E,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA;AAAK,OACpC,CAAC,CAAA;AAAA,IACJ;AACA,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,CAAA,EAAQ;AACf,IAAA,aAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,IAAW,aAAa,CAAA;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"chunk-LZCEK7JF.js","sourcesContent":["/**\n * Fetch a file from the user's file-server and route it into the right\n * preview window. Shared by the Files app and the desktop Documents\n * folder's shortcut click-through.\n *\n * On success, dispatches `react-os-shell:preview-opened` so Desktop can\n * record the file as a shortcut without this util needing access to the\n * prefs adapter.\n */\nimport toast from '../shell/toast';\nimport { setPdfPreview } from '../apps/Preview';\nimport { setSpreadsheetPreview } from '../apps/Spreadsheet';\n\nexport type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';\n\nexport interface OpenPreviewFileOpts {\n /** Server-relative path, e.g. \"/reports/Q1.pdf\". */\n filePath: string;\n /** Display name (also used as the download filename). */\n filename: string;\n /** Which viewer to route into. CSV opens in Spreadsheet; the rest open in Preview. */\n kind: PreviewFileKind;\n /** Optional callback invoked after staging the preview, with the route to\n * open (e.g. '/preview' or '/spreadsheet'). The caller is responsible\n * for actually opening the page since the window manager hook is\n * React-scoped. */\n onStaged?: (route: '/preview' | '/spreadsheet') => void;\n}\n\nexport const PREVIEW_OPENED_EVENT = 'react-os-shell:preview-opened';\n\nexport interface PreviewOpenedDetail {\n filePath: string;\n filename: string;\n kind: PreviewFileKind;\n}\n\nfunction getServer() {\n const override = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_FILE_SERVER__) as string | undefined;\n return (override || 'http://localhost:4000').replace(/\\/$/, '');\n}\n\nexport async function openPreviewFile(opts: OpenPreviewFileOpts): Promise<boolean> {\n const { filePath, filename, kind, onStaged } = opts;\n try {\n const res = await fetch(\n `${getServer()}/api/file?path=${encodeURIComponent(filePath)}`,\n { credentials: 'include' },\n );\n if (!res.ok) {\n toast.error(`Download failed (${res.status})`);\n return false;\n }\n const blob = await res.blob();\n if (kind === 'csv') {\n const text = await blob.text();\n setSpreadsheetPreview({ csv: text, filename });\n onStaged?.('/spreadsheet');\n } else {\n const url = URL.createObjectURL(blob);\n setPdfPreview({ url, filename, kind });\n onStaged?.('/preview');\n }\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent<PreviewOpenedDetail>(PREVIEW_OPENED_EVENT, {\n detail: { filePath, filename, kind },\n }));\n }\n return true;\n } catch (e: any) {\n toast.error(e?.message || 'Open failed');\n return false;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/utils/openPreviewFile.ts"],"names":[],"mappings":";;;;;AA6BO,IAAM,oBAAA,GAAuB;AAQpC,SAAS,SAAA,GAAY;AACnB,EAAA,MAAM,QAAA,GAAY,OAAO,MAAA,KAAW,WAAA,IAAgB,MAAA,CAAe,8BAAA;AACnE,EAAA,OAAA,CAAQ,QAAA,IAAY,uBAAA,EAAyB,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAChE;AAEA,eAAsB,gBAAgB,IAAA,EAA6C;AACjF,EAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA,EAAM,UAAS,GAAI,IAAA;AAC/C,EAAA,IAAI;AACF,IAAA,MAAM,MAAM,MAAM,KAAA;AAAA,MAChB,GAAG,SAAA,EAAW,CAAA,eAAA,EAAkB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA,MAC5D,EAAE,aAAa,SAAA;AAAU,KAC3B;AACA,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,aAAA,CAAM,KAAA,CAAM,CAAA,iBAAA,EAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAC7C,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,SAAS,KAAA,EAAO;AAClB,MAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,IAAA,EAAK;AAC7B,MAAA,qBAAA,CAAsB,EAAE,GAAA,EAAK,IAAA,EAAM,QAAA,EAAU,CAAA;AAC7C,MAAA,QAAA,GAAW,cAAc,CAAA;AAAA,IAC3B,CAAA,MAAO;AACL,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,MAAA,aAAA,CAAc,EAAE,GAAA,EAAK,QAAA,EAAU,IAAA,EAAM,CAAA;AACrC,MAAA,QAAA,GAAW,UAAU,CAAA;AAAA,IACvB;AACA,IAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,MAAA,MAAA,CAAO,aAAA,CAAc,IAAI,WAAA,CAAiC,oBAAA,EAAsB;AAAA,QAC9E,MAAA,EAAQ,EAAE,QAAA,EAAU,QAAA,EAAU,IAAA;AAAK,OACpC,CAAC,CAAA;AAAA,IACJ;AACA,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,CAAA,EAAQ;AACf,IAAA,aAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAA,IAAW,aAAa,CAAA;AACvC,IAAA,OAAO,KAAA;AAAA,EACT;AACF","file":"chunk-XNXIIGHP.js","sourcesContent":["/**\n * Fetch a file from the user's file-server and route it into the right\n * preview window. Shared by the Files app and the desktop Documents\n * folder's shortcut click-through.\n *\n * On success, dispatches `react-os-shell:preview-opened` so Desktop can\n * record the file as a shortcut without this util needing access to the\n * prefs adapter.\n */\nimport toast from '../shell/toast';\nimport { setPdfPreview } from '../apps/Preview';\nimport { setSpreadsheetPreview } from '../apps/Spreadsheet';\n\nexport type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';\n\nexport interface OpenPreviewFileOpts {\n /** Server-relative path, e.g. \"/reports/Q1.pdf\". */\n filePath: string;\n /** Display name (also used as the download filename). */\n filename: string;\n /** Which viewer to route into. CSV opens in Spreadsheet; the rest open in Preview. */\n kind: PreviewFileKind;\n /** Optional callback invoked after staging the preview, with the route to\n * open (e.g. '/preview' or '/spreadsheet'). The caller is responsible\n * for actually opening the page since the window manager hook is\n * React-scoped. */\n onStaged?: (route: '/preview' | '/spreadsheet') => void;\n}\n\nexport const PREVIEW_OPENED_EVENT = 'react-os-shell:preview-opened';\n\nexport interface PreviewOpenedDetail {\n filePath: string;\n filename: string;\n kind: PreviewFileKind;\n}\n\nfunction getServer() {\n const override = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_FILE_SERVER__) as string | undefined;\n return (override || 'http://localhost:4000').replace(/\\/$/, '');\n}\n\nexport async function openPreviewFile(opts: OpenPreviewFileOpts): Promise<boolean> {\n const { filePath, filename, kind, onStaged } = opts;\n try {\n const res = await fetch(\n `${getServer()}/api/file?path=${encodeURIComponent(filePath)}`,\n { credentials: 'include' },\n );\n if (!res.ok) {\n toast.error(`Download failed (${res.status})`);\n return false;\n }\n const blob = await res.blob();\n if (kind === 'csv') {\n const text = await blob.text();\n setSpreadsheetPreview({ csv: text, filename });\n onStaged?.('/spreadsheet');\n } else {\n const url = URL.createObjectURL(blob);\n setPdfPreview({ url, filename, kind });\n onStaged?.('/preview');\n }\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent<PreviewOpenedDetail>(PREVIEW_OPENED_EVENT, {\n detail: { filePath, filename, kind },\n }));\n }\n return true;\n } catch (e: any) {\n toast.error(e?.message || 'Open failed');\n return false;\n }\n}\n"]}
@@ -26,7 +26,7 @@ function setShellNavIcons(icons) {
26
26
  }
27
27
  var sectionIcons = {};
28
28
  var navSections = [];
29
- var startMenuCategories = { erp: [], system: [], virtual: [] };
29
+ var startMenuCategories = { erp: [], system: [], virtual: [], footer: [] };
30
30
 
31
31
  // src/utils/glass.ts
32
32
  function getMenuOpacity() {
@@ -67,5 +67,5 @@ var GLASS_DIVIDER = "border-white/20";
67
67
  var GLASS_INPUT_BG = "glass-input-bg";
68
68
 
69
69
  export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle, isSection, navIcons, navSections, sectionIcons, setShellNavIcons, startMenuCategories };
70
- //# sourceMappingURL=chunk-SSA762W5.js.map
71
- //# sourceMappingURL=chunk-SSA762W5.js.map
70
+ //# sourceMappingURL=chunk-ZF6AYO4G.js.map
71
+ //# sourceMappingURL=chunk-ZF6AYO4G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shell/nav-types.ts","../src/shell-config/nav.tsx","../src/utils/glass.ts"],"names":[],"mappings":";AAkDO,SAAS,UAAU,IAAA,EAAgD;AACxE,EAAA,OAAO,OAAA,IAAW,IAAA;AACpB;;;ACpCA,IAAM,YAAuC,EAAC;AACvC,IAAM,QAAA,GAAsC,IAAI,KAAA,CAAM,SAAA,EAAW;AAAA,EACtE,GAAA,CAAI,IAAI,CAAA,EAAW;AAAE,IAAA,OAAO,UAAU,CAAC,CAAA;AAAA,EAAG,CAAA;AAAA,EAC1C,GAAA,CAAI,IAAI,CAAA,EAAW;AAAE,IAAA,OAAO,CAAA,IAAK,SAAA;AAAA,EAAW,CAAA;AAAA,EAC5C,OAAA,GAAU;AAAE,IAAA,OAAO,MAAA,CAAO,KAAK,SAAS,CAAA;AAAA,EAAG,CAAA;AAAA,EAC3C,wBAAA,CAAyB,IAAI,CAAA,EAAW;AACtC,IAAA,IAAI,CAAA,IAAK,SAAA,EAAW,OAAO,EAAE,YAAA,EAAc,IAAA,EAAM,UAAA,EAAY,IAAA,EAAM,KAAA,EAAO,SAAA,CAAU,CAAC,CAAA,EAAE;AACvF,IAAA,OAAO,MAAA;AAAA,EACT;AACF,CAAC;AACM,SAAS,iBAAiB,KAAA,EAAwC;AACvE,EAAA,KAAA,MAAW,KAAK,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA,EAAG,OAAO,UAAU,CAAC,CAAA;AAC1D,EAAA,MAAA,CAAO,MAAA,CAAO,WAAW,KAAK,CAAA;AAChC;AAEO,IAAM,eAA0C;AAChD,IAAM,cAAwC;AAC9C,IAAM,mBAAA,GAA2C,EAAE,GAAA,EAAK,EAAC,EAAG,MAAA,EAAQ,EAAC,EAAG,OAAA,EAAS,EAAC,EAAG,MAAA,EAAQ,EAAC;;;AC9BrG,SAAS,cAAA,GAAyB;AAChC,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,iBAAiB,QAAA,CAAS,eAAe,EAAE,gBAAA,CAAiB,gBAAgB,GAAG,IAAA,EAAK;AAChG,IAAA,IAAI,GAAA,EAAK,OAAO,UAAA,CAAW,GAAG,CAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,WAAA,GAAuB;AAC9B,EAAA,IAAI;AACF,IAAA,OAAO,QAAA,CAAS,eAAA,CAAgB,YAAA,CAAa,YAAY,CAAA,KAAM,MAAA;AAAA,EACjE,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAKO,SAAS,WAAW,OAAA,EAAiC;AAC1D,EAAA,MAAM,CAAA,GAAI,WAAW,cAAA,EAAe;AACpC,EAAA,IAAI,aAAY,EAAG;AAGjB,IAAA,OAAO;AAAA,MACL,UAAA,EAAY,yCAAyC,CAAA,GAAI,IAAI,uBAAuB,CAAA,GAAI,IAAI,CAAA,qBAAA,EAAwB,CAAA,GAAI,IAAI,CAAA,OAAA,CAAA;AAAA,MAC5H,cAAA,EAAgB,0BAAA;AAAA,MAChB,oBAAA,EAAsB,0BAAA;AAAA,MACtB,MAAA,EAAQ,kCAAA;AAAA,MACR,SAAA,EAAW;AAAA,KACb;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,4CAA4C,CAAA,GAAI,IAAI,0BAA0B,CAAA,GAAI,IAAI,CAAA,wBAAA,EAA2B,CAAA,GAAI,IAAI,CAAA,OAAA,CAAA;AAAA,IACrI,cAAA,EAAgB,0BAAA;AAAA,IAChB,oBAAA,EAAsB,0BAAA;AAAA,IACtB,MAAA,EAAQ,kCAAA;AAAA,IACR,SAAA,EAAW;AAAA,GACb;AACF;AAGO,IAAM,aAAA,GAAgB;AAKtB,IAAM,cAAA,GAAiB","file":"chunk-ZF6AYO4G.js","sourcesContent":["/**\n * Nav data types used by <Layout> + <StartMenu>. The actual sections, icons,\n * and categories are consumer-supplied via Layout props — the package never\n * ships nav DATA, only the shape it expects.\n */\nimport type { ReactNode } from 'react';\n\nexport interface NavItem {\n to: string;\n label: string;\n perms?: string[];\n dividerAfter?: boolean;\n /** Optional 3rd-level sub-items. Hovering the parent in <StartMenu> opens\n * a nested flyout; in <Sidebar> the parent becomes an inline sub-accordion. */\n children?: NavItem[];\n}\n\nexport interface NavSection {\n label: string;\n items: NavItem[];\n perms?: string[];\n /** Optional landing route for clicks on the section title itself\n * (e.g. R&D's `/rd` dashboard). */\n to?: string;\n}\n\nexport interface VirtualSection {\n label: string;\n items: NavItem[];\n icon?: ReactNode;\n}\n\nexport interface StartMenuCategories {\n /** Section labels rendered in the \"ERP\" group (bold, with section icon). */\n erp: string[];\n /** Section labels rendered in the \"system\" group. */\n system: string[];\n /** Optional virtual flyouts (e.g. a \"Utilities\" tray). */\n virtual?: VirtualSection[];\n /** Optional section labels pinned to the bottom of the menu, next to the user\n * profile, separated from the ERP group by a divider. */\n footer?: string[];\n /** Optional flat top-level items pinned to the bottom of the menu, next to\n * the user profile, separated from the ERP group by a divider. Unlike\n * `footer` (section labels rendered as flyouts), these render as direct\n * clickable rows — use for standalone destinations like System Preferences\n * or a bug-report link. */\n footerItems?: NavItem[];\n}\n\nexport function isSection(item: NavSection | NavItem): item is NavSection {\n return 'items' in item;\n}\n","/**\n * INTERNAL stub — package-side compatibility for files that legacy-imported\n * default nav data from `shell-config/nav`. The package never ships nav\n * DATA; consumers always supply `navSections` / `navIcons` / `sectionIcons` /\n * `categories` as Layout props.\n *\n * Empty defaults exported here so the copied files compile during the\n * extraction transition. Each consumer-facing field is also re-exported as\n * a TYPE alias for code that only needed the shape.\n */\nimport type { ReactNode } from 'react';\nimport type { NavItem, NavSection, StartMenuCategories, VirtualSection } from '../shell/nav-types';\n\n// Live proxy: WindowManager reads window-title icons from this module-level\n// map. Consumers register their full icon set once at app startup so the\n// title bars show the same glyphs as the start menu.\nconst _navIcons: Record<string, ReactNode> = {};\nexport const navIcons: Record<string, ReactNode> = new Proxy(_navIcons, {\n get(_t, k: string) { return _navIcons[k]; },\n has(_t, k: string) { return k in _navIcons; },\n ownKeys() { return Object.keys(_navIcons); },\n getOwnPropertyDescriptor(_t, k: string) {\n if (k in _navIcons) return { configurable: true, enumerable: true, value: _navIcons[k] };\n return undefined;\n },\n});\nexport function setShellNavIcons(icons: Record<string, ReactNode>): void {\n for (const k of Object.keys(_navIcons)) delete _navIcons[k];\n Object.assign(_navIcons, icons);\n}\n\nexport const sectionIcons: Record<string, ReactNode> = {};\nexport const navSections: (NavSection | NavItem)[] = [];\nexport const startMenuCategories: StartMenuCategories = { erp: [], system: [], virtual: [], footer: [] };\n\nexport { isSection } from '../shell/nav-types';\nexport type { NavItem, NavSection, StartMenuCategories, VirtualSection };\n","import type { CSSProperties } from 'react';\n\n/** Read the system menu opacity from CSS custom property set by Layout */\nfunction getMenuOpacity(): number {\n try {\n const val = getComputedStyle(document.documentElement).getPropertyValue('--menu-opacity')?.trim();\n if (val) return parseFloat(val);\n } catch {}\n return 0.95;\n}\n\nfunction isDarkTheme(): boolean {\n try {\n return document.documentElement.getAttribute('data-theme') === 'dark';\n } catch {\n return false;\n }\n}\n\n/** Frosted glass style — shared across all menus, popups, and glass UI elements.\n * Reads --menu-opacity CSS variable set by the theme system, and adapts the\n * base tint to dark mode so menus don't stay light-cream when text is light. */\nexport function glassStyle(opacity?: number): CSSProperties {\n const o = opacity ?? getMenuOpacity();\n if (isDarkTheme()) {\n // Dark frosted glass — Catppuccin-aligned base (#1e1e2e / 30,30,46) with\n // a subtle gradient and lighter inner highlight.\n return {\n background: `linear-gradient(135deg, rgba(30,30,46,${o * 0.85}) 0%, rgba(24,24,37,${o * 0.75}) 50%, rgba(30,30,46,${o * 0.85}) 100%)`,\n backdropFilter: 'blur(40px) saturate(1.6)',\n WebkitBackdropFilter: 'blur(40px) saturate(1.6)',\n border: '1px solid rgba(255,255,255,0.08)',\n boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.06), inset 0 -1px 0 rgba(0,0,0,0.4), 0 8px 32px rgba(0,0,0,0.5), 0 2px 8px rgba(0,0,0,0.3)',\n };\n }\n return {\n background: `linear-gradient(135deg, rgba(255,255,255,${o * 0.85}) 0%, rgba(255,255,255,${o * 0.65}) 50%, rgba(255,255,255,${o * 0.75}) 100%)`,\n backdropFilter: 'blur(40px) saturate(1.8)',\n WebkitBackdropFilter: 'blur(40px) saturate(1.8)',\n border: '1px solid rgba(255,255,255,0.35)',\n boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.4), inset 0 -1px 0 rgba(255,255,255,0.1), 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08)',\n };\n}\n\n/** Glass divider border color */\nexport const GLASS_DIVIDER = 'border-white/20';\n\n/** Glass input/search bar background — declared in styles.css so it can adapt\n * to dark mode (a flat `bg-white/15` reads as a too-bright tile on the dark\n * glass gradient). */\nexport const GLASS_INPUT_BG = 'glass-input-bg';\n"]}
package/dist/index.d.ts CHANGED
@@ -33,6 +33,9 @@ interface NavItem {
33
33
  label: string;
34
34
  perms?: string[];
35
35
  dividerAfter?: boolean;
36
+ /** Optional 3rd-level sub-items. Hovering the parent in <StartMenu> opens
37
+ * a nested flyout; in <Sidebar> the parent becomes an inline sub-accordion. */
38
+ children?: NavItem[];
36
39
  }
37
40
  interface NavSection {
38
41
  label: string;
@@ -54,6 +57,15 @@ interface StartMenuCategories {
54
57
  system: string[];
55
58
  /** Optional virtual flyouts (e.g. a "Utilities" tray). */
56
59
  virtual?: VirtualSection[];
60
+ /** Optional section labels pinned to the bottom of the menu, next to the user
61
+ * profile, separated from the ERP group by a divider. */
62
+ footer?: string[];
63
+ /** Optional flat top-level items pinned to the bottom of the menu, next to
64
+ * the user profile, separated from the ERP group by a divider. Unlike
65
+ * `footer` (section labels rendered as flyouts), these render as direct
66
+ * clickable rows — use for standalone destinations like System Preferences
67
+ * or a bug-report link. */
68
+ footerItems?: NavItem[];
57
69
  }
58
70
 
59
71
  /**
@@ -705,8 +717,11 @@ interface LayoutProps {
705
717
  * the shell. The shell renders the node as-is — keep it small (a
706
718
  * single icon-sized button) so it fits the existing tray rhythm. */
707
719
  taskbarTrayLeft?: ReactNode;
720
+ /** Show the Mail & Calendar connect button in the system tray.
721
+ * Defaults to true. Set false for portals with no mail integration. */
722
+ showMail?: boolean;
708
723
  }
709
- declare function Layout({ productName, productIcon, wallpapers, navSections, navIcons, sectionIcons, categories, notifications, search, taskbarTrayLeft, }?: LayoutProps): react_jsx_runtime.JSX.Element;
724
+ declare function Layout({ productName, productIcon, wallpapers, navSections, navIcons, sectionIcons, categories, notifications, search, taskbarTrayLeft, showMail, }?: LayoutProps): react_jsx_runtime.JSX.Element;
710
725
 
711
726
  interface StartMenuProps {
712
727
  open: boolean;
package/dist/index.js CHANGED
@@ -6,21 +6,21 @@ export { setShellMailServer, useMailAuth } from './chunk-VBFB3ZIN.js';
6
6
  import { subscribePomo, getPomoSnapshot } from './chunk-MK3HLUO4.js';
7
7
  import { useShellPrefs } from './chunk-36VM54SC.js';
8
8
  export { ShellPrefsProvider, useLocalStoragePrefs, useShellPrefs } from './chunk-36VM54SC.js';
9
- import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-LZCEK7JF.js';
9
+ import { PREVIEW_OPENED_EVENT, openPreviewFile } from './chunk-XNXIIGHP.js';
10
10
  import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds, playLogout } from './chunk-D7PYW2QS.js';
11
- import { setPdfPreview } from './chunk-NRT2CU5Q.js';
11
+ import { setPdfPreview } from './chunk-HG3O6XHN.js';
12
12
  import './chunk-KUIPWCTJ.js';
13
13
  import { toast_default } from './chunk-WIJ45SYD.js';
14
14
  export { toast_default as toast } from './chunk-WIJ45SYD.js';
15
- export { EditableGrid } from './chunk-6YY6A6SV.js';
16
- import { useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, ModalActions, useIsMobile, useModalActive, client_default, LoadingSpinner, WINDOW_REGISTRY, isPageEntry, ThumbCard, activateModal } from './chunk-GI7ABQPU.js';
17
- export { CancelButton, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, commitExposeHighlight, exitExposeMode, getActiveWindowRoute, getExposeHighlight, isEntityEntry, isPageEntry, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, subscribeExposeHighlight, toggleExposeMode, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-GI7ABQPU.js';
15
+ export { EditableGrid } from './chunk-NVAWKCAI.js';
16
+ import { useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, Modal, ModalActions, useIsMobile, useModalActive, client_default, LoadingSpinner, WINDOW_REGISTRY, isPageEntry, ThumbCard, activateModal } from './chunk-3RQ5TVEL.js';
17
+ export { CancelButton, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, WindowManagerProvider, WindowTitle, commitExposeHighlight, exitExposeMode, getActiveWindowRoute, getExposeHighlight, isEntityEntry, isPageEntry, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, subscribeExposeHighlight, toggleExposeMode, useModalActive, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-3RQ5TVEL.js';
18
18
  import { confirm } from './chunk-PLGHQ7QW.js';
19
19
  export { ConfirmProvider, confirm, confirmDestructive, prompt } from './chunk-PLGHQ7QW.js';
20
20
  import { useAuth, useShellAuth } from './chunk-ADJ3CERD.js';
21
21
  export { ShellAuthProvider, setShellAuthBridge, useShellAuth } from './chunk-ADJ3CERD.js';
22
- import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons } from './chunk-SSA762W5.js';
23
- export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle, setShellNavIcons } from './chunk-SSA762W5.js';
22
+ import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons } from './chunk-ZF6AYO4G.js';
23
+ export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle, setShellNavIcons } from './chunk-ZF6AYO4G.js';
24
24
  import { createContext, lazy, useState, useRef, useEffect, useCallback, useLayoutEffect, useContext, Suspense, isValidElement, cloneElement, useMemo, useSyncExternalStore } from 'react';
25
25
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
26
26
  import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react';
@@ -915,7 +915,7 @@ function StatusBadge({ status }) {
915
915
  }
916
916
 
917
917
  // src/version.ts
918
- var VERSION = "0.4.0" ;
918
+ var VERSION = "0.6.1" ;
919
919
  var APP_VERSION = VERSION;
920
920
 
921
921
  // src/changelog.ts
@@ -2887,25 +2887,35 @@ function StartMenu({
2887
2887
  }) {
2888
2888
  const erpLabels = new Set(categories.erp);
2889
2889
  const systemLabels = new Set(categories.system);
2890
+ const footerLabels = new Set(categories.footer ?? []);
2890
2891
  const virtualSections = categories.virtual ?? [];
2891
2892
  const virtualByLabel = Object.fromEntries(
2892
2893
  virtualSections.map((v) => [v.label, v])
2893
2894
  );
2894
2895
  const { hasAnyPerm } = useAuth();
2896
+ const footerItems = (categories.footerItems ?? []).filter((item) => !item.perms || hasAnyPerm(item.perms));
2895
2897
  const isMobile = useIsMobile();
2896
2898
  const [hoveredSection, setHoveredSection] = useState(null);
2897
2899
  const [hoveredY, setHoveredY] = useState(0);
2900
+ const [hoveredChild, setHoveredChild] = useState(null);
2901
+ const [hoveredChildY, setHoveredChildY] = useState(0);
2898
2902
  const [search, setSearch] = useState("");
2899
2903
  const [searchIdx, setSearchIdx] = useState(0);
2900
2904
  const menuRef = useRef(null);
2905
+ const flyoutRef = useRef(null);
2901
2906
  const hoverTimeout = useRef();
2907
+ const childHoverTimeout = useRef();
2902
2908
  useEffect(() => {
2903
2909
  if (!open) {
2904
2910
  setSearch("");
2905
2911
  setHoveredSection(null);
2912
+ setHoveredChild(null);
2906
2913
  setSearchIdx(0);
2907
2914
  }
2908
2915
  }, [open]);
2916
+ useEffect(() => {
2917
+ setHoveredChild(null);
2918
+ }, [hoveredSection]);
2909
2919
  useEffect(() => {
2910
2920
  if (!open) return;
2911
2921
  const handler = (e) => {
@@ -2929,20 +2939,23 @@ function StartMenu({
2929
2939
  };
2930
2940
  if (isMobile) {
2931
2941
  const allItems = [];
2942
+ const pushItem = (it, sectionLabel) => {
2943
+ if (it.perms && !hasAnyPerm(it.perms)) return;
2944
+ allItems.push({ item: it, sectionLabel });
2945
+ if (it.children) {
2946
+ for (const c of it.children) pushItem(c, it.label);
2947
+ }
2948
+ };
2932
2949
  for (const entry of navSections2) {
2933
2950
  if (isSection(entry)) {
2934
2951
  const sec = entry;
2935
2952
  if (sec.perms && !hasAnyPerm(sec.perms)) continue;
2936
- for (const it of sec.items) {
2937
- if (it.perms && !hasAnyPerm(it.perms)) continue;
2938
- allItems.push({ item: it, sectionLabel: sec.label });
2939
- }
2953
+ for (const it of sec.items) pushItem(it, sec.label);
2940
2954
  } else {
2941
- const it = entry;
2942
- if (it.perms && !hasAnyPerm(it.perms)) continue;
2943
- allItems.push({ item: it });
2955
+ pushItem(entry);
2944
2956
  }
2945
2957
  }
2958
+ for (const it of footerItems) pushItem(it);
2946
2959
  const filtered = search.length >= 1 ? allItems.filter(({ item }) => item.label.toLowerCase().includes(search.toLowerCase())) : allItems;
2947
2960
  return /* @__PURE__ */ jsxs(
2948
2961
  "div",
@@ -2993,13 +3006,29 @@ function StartMenu({
2993
3006
  const topItems = navSections2.filter((item) => !isSection(item));
2994
3007
  const erpSections = navSections2.filter((item) => isSection(item) && erpLabels.has(item.label));
2995
3008
  const systemSections = navSections2.filter((item) => isSection(item) && systemLabels.has(item.label));
3009
+ const footerSections = navSections2.filter((item) => isSection(item) && footerLabels.has(item.label));
2996
3010
  const getVisibleItems = (section) => section.items.filter((item) => !item.perms || hasAnyPerm(item.perms));
2997
- const searchResults = search.length >= 2 ? navSections2.flatMap((item) => {
2998
- if (isSection(item)) {
2999
- return item.items.filter((i) => (!i.perms || hasAnyPerm(i.perms)) && i.label.toLowerCase().includes(search.toLowerCase())).map((i) => ({ ...i, section: item.label }));
3011
+ const matchTree = (it, sectionLabel) => {
3012
+ if (it.perms && !hasAnyPerm(it.perms)) return [];
3013
+ const hits = [];
3014
+ if (it.label.toLowerCase().includes(search.toLowerCase())) {
3015
+ hits.push({ ...it, section: sectionLabel });
3016
+ }
3017
+ if (it.children) {
3018
+ for (const c of it.children) hits.push(...matchTree(c, it.label));
3000
3019
  }
3001
- return item.label.toLowerCase().includes(search.toLowerCase()) ? [{ ...item, section: "" }] : [];
3002
- }) : [];
3020
+ return hits;
3021
+ };
3022
+ const searchResults = search.length >= 2 ? [
3023
+ ...navSections2.flatMap((item) => {
3024
+ if (isSection(item)) {
3025
+ const sec = item;
3026
+ return sec.items.flatMap((i) => matchTree(i, sec.label));
3027
+ }
3028
+ return matchTree(item, "");
3029
+ }),
3030
+ ...footerItems.flatMap((item) => matchTree(item, ""))
3031
+ ] : [];
3003
3032
  const posStyle = taskbarPosition === "top" ? { top: taskbarH + 8, left: 8 } : taskbarPosition === "left" ? { top: 8, left: taskbarW + 8 } : taskbarPosition === "right" ? { top: 8, right: taskbarW + 8 } : { bottom: taskbarH + 8, left: 8 };
3004
3033
  const iconEl = (path) => {
3005
3034
  const icon = navIcons2[path];
@@ -3012,7 +3041,7 @@ function StartMenu({
3012
3041
  return null;
3013
3042
  };
3014
3043
  const hoveredVirtual = hoveredSection ? virtualByLabel[hoveredSection] : void 0;
3015
- const hoveredData = hoveredVirtual ? null : hoveredSection ? [...erpSections, ...systemSections].find((s) => s.label === hoveredSection) : null;
3044
+ const hoveredData = hoveredVirtual ? null : hoveredSection ? [...erpSections, ...systemSections, ...footerSections].find((s) => s.label === hoveredSection) : null;
3016
3045
  const flyoutItems = hoveredVirtual ? hoveredVirtual.items : hoveredData ? getVisibleItems(hoveredData) : [];
3017
3046
  const menuDensity = typeof document !== "undefined" ? getComputedStyle(document.documentElement).getPropertyValue("--menu-density")?.trim() || "normal" : "normal";
3018
3047
  const tight = menuDensity === "tight";
@@ -3126,6 +3155,20 @@ function StartMenu({
3126
3155
  i
3127
3156
  )) }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto px-1 pb-1 flex flex-col", children: [
3128
3157
  isVertical && /* @__PURE__ */ jsxs(Fragment, { children: [
3158
+ footerItems.map((item) => /* @__PURE__ */ jsxs(
3159
+ "button",
3160
+ {
3161
+ onClick: () => handleClick(item.to),
3162
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
3163
+ children: [
3164
+ iconEl(item.to),
3165
+ /* @__PURE__ */ jsx("span", { children: item.label })
3166
+ ]
3167
+ },
3168
+ item.to
3169
+ )),
3170
+ footerSections.map((s) => renderSection(s, false)),
3171
+ (footerSections.length > 0 || footerItems.length > 0) && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
3129
3172
  erpSections.map((s) => renderSection(s, true)),
3130
3173
  /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
3131
3174
  topItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
@@ -3171,7 +3214,21 @@ function StartMenu({
3171
3214
  systemSections.map((s) => renderSection(s, false)),
3172
3215
  virtualSections.map((v) => renderVirtualSection(v)),
3173
3216
  /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
3174
- erpSections.map((s) => renderSection(s, true))
3217
+ erpSections.map((s) => renderSection(s, true)),
3218
+ (footerSections.length > 0 || footerItems.length > 0) && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" }),
3219
+ footerSections.map((s) => renderSection(s, false)),
3220
+ footerItems.map((item) => /* @__PURE__ */ jsxs(
3221
+ "button",
3222
+ {
3223
+ onClick: () => handleClick(item.to),
3224
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
3225
+ children: [
3226
+ iconEl(item.to),
3227
+ /* @__PURE__ */ jsx("span", { children: item.label })
3228
+ ]
3229
+ },
3230
+ item.to
3231
+ ))
3175
3232
  ] })
3176
3233
  ] }),
3177
3234
  /* @__PURE__ */ jsx("div", { className: `${isVertical ? "border-b" : "border-t"} border-white/20 p-1`, children: /* @__PURE__ */ jsxs(
@@ -3204,28 +3261,91 @@ function StartMenu({
3204
3261
  hoveredSection && flyoutItems.length > 0 && search.length < 2 && /* @__PURE__ */ jsx(
3205
3262
  "div",
3206
3263
  {
3264
+ ref: flyoutRef,
3207
3265
  className: `fixed ${sizeConfig.fw} rounded-2xl overflow-hidden`,
3208
3266
  style: { left: menuRef.current ? menuRef.current.getBoundingClientRect().right + 4 : menuWidth + 12, top: flyoutTop, animation: "submenu-in 0.1s ease-out", ...menuGlass },
3209
3267
  onMouseEnter: () => clearTimeout(hoverTimeout.current),
3210
3268
  onMouseLeave: () => {
3211
- hoverTimeout.current = setTimeout(() => setHoveredSection(null), 200);
3269
+ hoverTimeout.current = setTimeout(() => {
3270
+ setHoveredSection(null);
3271
+ setHoveredChild(null);
3272
+ }, 200);
3212
3273
  },
3213
- children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1", children: flyoutItems.map((item) => /* @__PURE__ */ jsxs("div", { children: [
3214
- /* @__PURE__ */ jsxs(
3215
- "button",
3274
+ children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1", children: flyoutItems.map((item) => {
3275
+ const hasChildren = !!item.children && item.children.length > 0;
3276
+ const isChildHovered = hoveredChild === item.to;
3277
+ return /* @__PURE__ */ jsxs(
3278
+ "div",
3216
3279
  {
3217
- onClick: () => handleClick(item.to),
3218
- className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
3280
+ onMouseEnter: hasChildren ? (e) => {
3281
+ clearTimeout(childHoverTimeout.current);
3282
+ const rect = e.currentTarget.getBoundingClientRect();
3283
+ setHoveredChildY(rect.top + rect.height / 2);
3284
+ setHoveredChild(item.to);
3285
+ } : () => {
3286
+ childHoverTimeout.current = setTimeout(() => setHoveredChild(null), 200);
3287
+ },
3219
3288
  children: [
3220
- iconEl(item.to),
3221
- /* @__PURE__ */ jsx("span", { children: item.label })
3289
+ /* @__PURE__ */ jsxs(
3290
+ "button",
3291
+ {
3292
+ onClick: () => handleClick(item.to),
3293
+ className: `${itemCls} transition-colors ${isChildHovered ? "bg-blue-50 text-blue-700" : "text-gray-700 hover:bg-blue-50 hover:text-blue-700"}`,
3294
+ children: [
3295
+ iconEl(item.to),
3296
+ /* @__PURE__ */ jsx("span", { children: item.label }),
3297
+ hasChildren && /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5 ml-auto text-gray-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) })
3298
+ ]
3299
+ }
3300
+ ),
3301
+ item.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
3222
3302
  ]
3223
- }
3224
- ),
3225
- item.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
3226
- ] }, item.to)) })
3303
+ },
3304
+ item.to
3305
+ );
3306
+ }) })
3227
3307
  }
3228
- )
3308
+ ),
3309
+ (() => {
3310
+ if (search.length >= 2 || !hoveredChild) return null;
3311
+ const parent = flyoutItems.find((it) => it.to === hoveredChild);
3312
+ const kids = (parent?.children ?? []).filter((c) => !c.perms || hasAnyPerm(c.perms));
3313
+ if (!parent || kids.length === 0) return null;
3314
+ const flyoutRect = flyoutRef.current?.getBoundingClientRect();
3315
+ const subLeft = flyoutRect ? flyoutRect.right + 4 : 0;
3316
+ const subH = kids.length * sizeConfig.itemH + 12;
3317
+ let subTop = hoveredChildY - subH / 2;
3318
+ if (subTop < minTop) subTop = minTop;
3319
+ if (subTop + subH > maxBottom) subTop = maxBottom - subH;
3320
+ return /* @__PURE__ */ jsx(
3321
+ "div",
3322
+ {
3323
+ className: `fixed ${sizeConfig.fw} rounded-2xl overflow-hidden`,
3324
+ style: { left: subLeft, top: subTop, animation: "submenu-in 0.1s ease-out", ...menuGlass },
3325
+ onMouseEnter: () => {
3326
+ clearTimeout(hoverTimeout.current);
3327
+ clearTimeout(childHoverTimeout.current);
3328
+ },
3329
+ onMouseLeave: () => {
3330
+ childHoverTimeout.current = setTimeout(() => setHoveredChild(null), 200);
3331
+ },
3332
+ children: /* @__PURE__ */ jsx("div", { className: "py-1 px-1", children: kids.map((child) => /* @__PURE__ */ jsxs("div", { children: [
3333
+ /* @__PURE__ */ jsxs(
3334
+ "button",
3335
+ {
3336
+ onClick: () => handleClick(child.to),
3337
+ className: `${itemCls} text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition-colors`,
3338
+ children: [
3339
+ iconEl(child.to),
3340
+ /* @__PURE__ */ jsx("span", { children: child.label })
3341
+ ]
3342
+ }
3343
+ ),
3344
+ child.dividerAfter && /* @__PURE__ */ jsx("div", { className: "border-t border-white/20 my-1.5 mx-2" })
3345
+ ] }, child.to)) })
3346
+ }
3347
+ );
3348
+ })()
3229
3349
  ] }),
3230
3350
  /* @__PURE__ */ jsx("style", { children: `
3231
3351
  @keyframes menu-in { from { opacity: 0; transform: scale(0.95) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } }
@@ -3996,7 +4116,7 @@ function MobileBottomNav({
3996
4116
  }
3997
4117
  );
3998
4118
  }
3999
- var Sidebar = lazy(() => import('./Sidebar-4V6NQROQ.js'));
4119
+ var Sidebar = lazy(() => import('./Sidebar-Q3PRJ2FP.js'));
4000
4120
  function useFavorites(wallpapers) {
4001
4121
  const { prefs, save } = useShellPrefs();
4002
4122
  const favorites = prefs.favorite_pages || [];
@@ -4193,7 +4313,8 @@ function Layout({
4193
4313
  categories = startMenuCategories,
4194
4314
  notifications,
4195
4315
  search,
4196
- taskbarTrayLeft
4316
+ taskbarTrayLeft,
4317
+ showMail = true
4197
4318
  } = {}) {
4198
4319
  const bugReport = useBugReport();
4199
4320
  const { user, logout, hasAnyPerm } = useAuth();
@@ -4526,7 +4647,7 @@ function Layout({
4526
4647
  /* Vertical: clock + mail-connect + bell evenly spaced */
4527
4648
  /* @__PURE__ */ jsx("div", { className: "w-full px-2", children: /* @__PURE__ */ jsxs("div", { className: `flex items-center justify-center gap-2 ${taskbarPosition === "right" ? "flex-row-reverse" : ""}`, children: [
4528
4649
  /* @__PURE__ */ jsx(TaskbarClock, {}),
4529
- /* @__PURE__ */ jsx(
4650
+ showMail && /* @__PURE__ */ jsx(
4530
4651
  "button",
4531
4652
  {
4532
4653
  onClick: () => setMailConnectOpen(true),
@@ -4545,7 +4666,7 @@ function Layout({
4545
4666
  /* @__PURE__ */ jsx(TaskbarPomodoro, {}),
4546
4667
  taskbarTrayLeft,
4547
4668
  notifications && /* @__PURE__ */ jsx(NotificationBell, { ...notifications }),
4548
- /* @__PURE__ */ jsx(
4669
+ showMail && /* @__PURE__ */ jsx(
4549
4670
  "button",
4550
4671
  {
4551
4672
  onClick: () => setMailConnectOpen(true),