@xenide-io/the-old-ui-theme 0.3.2 → 0.4.3

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 (62) hide show
  1. package/dist/chunk-54ZR4VL5.mjs +4641 -0
  2. package/dist/chunk-G53YLHVE.mjs +1023 -0
  3. package/dist/{index-BgG8Homu.d.mts → index-B5NOyoKS.d.mts} +100 -100
  4. package/dist/{index-BgG8Homu.d.ts → index-B5NOyoKS.d.ts} +100 -100
  5. package/dist/index.d.mts +9 -145
  6. package/dist/index.d.ts +9 -145
  7. package/dist/index.js +653 -1111
  8. package/dist/index.mjs +86 -342
  9. package/dist/suite.d.mts +760 -0
  10. package/dist/suite.d.ts +760 -0
  11. package/dist/suite.js +2800 -0
  12. package/dist/suite.mjs +2441 -0
  13. package/dist/ui.d.mts +1 -2
  14. package/dist/ui.d.ts +1 -2
  15. package/dist/ui.js +14 -10
  16. package/dist/ui.mjs +13 -11
  17. package/package.json +43 -24
  18. package/src/components/icons/icons.tsx +0 -2
  19. package/src/components/icons/index.ts +0 -3
  20. package/src/components/sections/IconShowcase.tsx +1 -110
  21. package/src/components/sections/NewComponentsShowcase.tsx +4 -4
  22. package/src/components/sections/SidebarShowcase.tsx +3 -3
  23. package/src/components/sections/SuiteShowcase.tsx +669 -0
  24. package/src/components/ui/ComponentDocs.tsx +2 -2
  25. package/src/components/ui/ContextMenu.tsx +1 -1
  26. package/src/components/ui/Menubar.tsx +1 -1
  27. package/src/components/ui/Sidebar.tsx +10 -8
  28. package/src/styles/suite-skin.css +255 -0
  29. package/src/suite/components/ai-panel.tsx +202 -0
  30. package/src/suite/components/app-switcher.tsx +196 -0
  31. package/src/suite/components/command-palette-host.tsx +62 -0
  32. package/src/suite/components/deferred-chrome.tsx +12 -0
  33. package/src/suite/components/suite-app-layout.tsx +69 -0
  34. package/src/suite/components/suite-bottom-nav.tsx +113 -0
  35. package/src/suite/components/suite-layout.tsx +285 -0
  36. package/src/suite/components/suite-mobile-drawer.tsx +150 -0
  37. package/src/suite/components/suite-mobile-header.tsx +71 -0
  38. package/src/suite/components/suite-notification-bell.tsx +227 -0
  39. package/src/suite/components/suite-settings-mobile-nav.tsx +83 -0
  40. package/src/suite/components/suite-sidebar.tsx +198 -0
  41. package/src/suite/components/suite-skeleton.tsx +191 -0
  42. package/src/suite/components/suite-user-menu.tsx +109 -0
  43. package/src/suite/components/theme-provider.tsx +174 -0
  44. package/src/suite/components/today-calibrating.tsx +95 -0
  45. package/src/suite/components/today-page-frame.tsx +28 -0
  46. package/src/suite/components/today-ui.tsx +370 -0
  47. package/src/suite/icons/app-accents.ts +75 -0
  48. package/src/suite/icons/glyph-parts.tsx +67 -0
  49. package/src/suite/icons/glyphs.ts +203 -0
  50. package/src/suite/icons/index.ts +12 -0
  51. package/src/suite/icons/suite-app-icon.tsx +68 -0
  52. package/src/suite/icons/suite-icon.tsx +93 -0
  53. package/src/suite/index.ts +108 -0
  54. package/src/suite/lib/apps.ts +75 -0
  55. package/src/suite/lib/cn.ts +6 -0
  56. package/src/suite/lib/injected.ts +54 -0
  57. package/src/suite/lib/motion.tsx +48 -0
  58. package/src/suite/lib/use-idle-mount.ts +25 -0
  59. package/dist/chunk-5HSOBJ4F.mjs +0 -5968
  60. package/src/components/icons/lemon-brand-icons.tsx +0 -16
  61. package/src/components/icons/lemon-category-icons.tsx +0 -72
  62. package/src/components/icons/lemon-icons.tsx +0 -57
package/dist/suite.js ADDED
@@ -0,0 +1,2800 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __objRest = (source, exclude) => {
26
+ var target = {};
27
+ for (var prop in source)
28
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
29
+ target[prop] = source[prop];
30
+ if (source != null && __getOwnPropSymbols)
31
+ for (var prop of __getOwnPropSymbols(source)) {
32
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
33
+ target[prop] = source[prop];
34
+ }
35
+ return target;
36
+ };
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, { get: all[name], enumerable: true });
40
+ };
41
+ var __copyProps = (to, from, except, desc) => {
42
+ if (from && typeof from === "object" || typeof from === "function") {
43
+ for (let key of __getOwnPropNames(from))
44
+ if (!__hasOwnProp.call(to, key) && key !== except)
45
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
+ }
47
+ return to;
48
+ };
49
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
50
+ // If the importer is in node compatibility mode or this is not an ESM
51
+ // file that has been converted to a CommonJS file using a Babel-
52
+ // compatible transform (i.e. "__esModule" has not been set), then set
53
+ // "default" to the CommonJS "module.exports" for node compatibility.
54
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
55
+ mod
56
+ ));
57
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
58
+
59
+ // src/suite/index.ts
60
+ var suite_exports = {};
61
+ __export(suite_exports, {
62
+ APP_ACCENTS: () => APP_ACCENTS,
63
+ APP_GLYPHS: () => APP_GLYPHS,
64
+ AnimatedMoon: () => AnimatedMoon,
65
+ AnimatedSun: () => AnimatedSun,
66
+ AppSwitcher: () => AppSwitcher,
67
+ AppSwitcherChevron: () => AppSwitcherChevron,
68
+ AppSwitcherMark: () => AppSwitcherMark,
69
+ CommandPaletteHost: () => CommandPaletteHost,
70
+ DeferredChrome: () => DeferredChrome,
71
+ SUITE_APPS: () => SUITE_APPS2,
72
+ SUITE_APP_MAP: () => SUITE_APP_MAP,
73
+ SUITE_GLYPHS: () => SUITE_GLYPHS,
74
+ SUITE_ICON_NAMES: () => SUITE_ICON_NAMES,
75
+ SUITE_SPRINGS: () => SUITE_SPRINGS,
76
+ SourceAppGlyph: () => SourceAppGlyph,
77
+ SuiteAiPanel: () => SuiteAiPanel,
78
+ SuiteAppIcon: () => SuiteAppIcon,
79
+ SuiteAppLayout: () => SuiteAppLayout,
80
+ SuiteAppStrip: () => SuiteAppStrip,
81
+ SuiteBottomNav: () => SuiteBottomNav,
82
+ SuiteBreadcrumbs: () => SuiteBreadcrumbs,
83
+ SuiteEmptyState: () => SuiteEmptyState,
84
+ SuiteIcon: () => SuiteIcon,
85
+ SuiteMobileDrawer: () => SuiteMobileDrawer,
86
+ SuiteMobileHeader: () => SuiteMobileHeader,
87
+ SuiteMotionProvider: () => SuiteMotionProvider,
88
+ SuiteNotificationBell: () => SuiteNotificationBell,
89
+ SuitePage: () => SuitePage,
90
+ SuitePageHeader: () => SuitePageHeader,
91
+ SuiteSectionHeader: () => SuiteSectionHeader,
92
+ SuiteSettingsMobileNav: () => SuiteSettingsMobileNav,
93
+ SuiteSidebar: () => SuiteSidebar,
94
+ SuiteSkeleton: () => SuiteSkeleton,
95
+ SuiteSkeletonCard: () => SuiteSkeletonCard,
96
+ SuiteSkeletonList: () => SuiteSkeletonList,
97
+ SuiteTabList: () => SuiteTabList,
98
+ SuiteThemeProvider: () => SuiteThemeProvider,
99
+ SuiteToolbar: () => SuiteToolbar,
100
+ SuiteUserMenu: () => SuiteUserMenu,
101
+ TodayCalibrating: () => TodayCalibrating,
102
+ TodayEmptyState: () => TodayEmptyState,
103
+ TodayHero: () => TodayHero,
104
+ TodayPageFrame: () => TodayPageFrame,
105
+ TodayPanel: () => TodayPanel,
106
+ TodaySection: () => TodaySection,
107
+ TodayTimeIcon: () => TodayTimeIcon,
108
+ TodayTopBar: () => TodayTopBar,
109
+ appSwitcherMarkClass: () => appSwitcherMarkClass,
110
+ appSwitcherMenuItemClass: () => appSwitcherMenuItemClass,
111
+ appSwitcherTriggerClass: () => appSwitcherTriggerClass,
112
+ cn: () => cn,
113
+ getTodayGreeting: () => getTodayGreeting,
114
+ m: () => import_react8.m,
115
+ sourceToSuiteApp: () => sourceToSuiteApp,
116
+ suiteAppBaseUrl: () => suiteAppBaseUrl,
117
+ suiteAppLabel: () => suiteAppLabel,
118
+ useIdleMount: () => useIdleMount,
119
+ useSuiteTheme: () => useSuiteTheme
120
+ });
121
+ module.exports = __toCommonJS(suite_exports);
122
+
123
+ // src/suite/components/today-page-frame.tsx
124
+ var import_jsx_runtime = require("react/jsx-runtime");
125
+ function TodayPageFrame({ children }) {
126
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
127
+ "div",
128
+ {
129
+ "data-test": "today-page",
130
+ className: "today-page-frame relative flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden",
131
+ children: [
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
133
+ "div",
134
+ {
135
+ className: "pointer-events-none absolute inset-x-0 top-0 h-[34rem] overflow-hidden",
136
+ "aria-hidden": true,
137
+ children: [
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "today-page-frame__glow absolute inset-0" }),
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "today-page-frame__sun absolute -right-16 -top-16 h-72 w-72 rounded-full bg-gradient-to-br from-amber-300/30 via-rose-300/20 to-rose-400/10 blur-3xl opacity-60 motion-safe:animate-[pulse_8s_ease-in-out_infinite] motion-reduce:opacity-40" }),
140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "today-page-frame__top-fade absolute inset-x-0 top-0 h-64" }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "today-page-frame__cloud today-page-frame__cloud--1 absolute -left-24 top-28 h-80 w-[26rem] rounded-[45%]" }),
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "today-page-frame__cloud today-page-frame__cloud--2 absolute -right-20 top-40 h-72 w-80 rounded-[40%]" }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "today-page-frame__cloud today-page-frame__cloud--3 absolute left-[28%] top-[58%] h-64 w-[22rem] rounded-[50%]" })
144
+ ]
145
+ }
146
+ ),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative flex-1 px-4 pb-10 pt-5 sm:px-6 sm:pt-6 lg:px-8 [&_.list-row]:min-h-[52px] [&_.list-row]:sm:min-h-[44px]", children })
148
+ ]
149
+ }
150
+ );
151
+ }
152
+
153
+ // src/suite/components/today-ui.tsx
154
+ var import_iconoir_react = require("iconoir-react");
155
+
156
+ // src/suite/lib/cn.ts
157
+ var import_clsx = require("clsx");
158
+ var import_tailwind_merge = require("tailwind-merge");
159
+ function cn(...inputs) {
160
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
161
+ }
162
+
163
+ // src/suite/icons/app-accents.ts
164
+ var APP_ACCENTS = {
165
+ turtletime: {
166
+ label: "TurtleTime",
167
+ accent: "#3f8f57",
168
+ tile: "#173521",
169
+ onTile: "#f7ead0",
170
+ onTileAccent: "#f3c24e"
171
+ },
172
+ tides: {
173
+ label: "Tides",
174
+ accent: "#a63bb5",
175
+ tile: "#8f2d96",
176
+ onTile: "#ffffff",
177
+ onTileAccent: "#bfe3ff"
178
+ },
179
+ kraken: {
180
+ label: "Kraken",
181
+ accent: "#737373",
182
+ tile: "#191919",
183
+ onTile: "#ffffff",
184
+ onTileAccent: "#d4d4d4"
185
+ },
186
+ shellstack: {
187
+ label: "ShellStack",
188
+ accent: "#5e5893",
189
+ tile: "#5e5893",
190
+ onTile: "#ffffff",
191
+ onTileAccent: "#c4a7e7"
192
+ },
193
+ crew: {
194
+ label: "Crew",
195
+ accent: "#0891b2",
196
+ tile: "#155e75",
197
+ onTile: "#ffffff",
198
+ onTileAccent: "#a5f3fc"
199
+ }
200
+ };
201
+ var APP_GLYPHS = {
202
+ turtletime: "timer-shell",
203
+ tides: "kanban-wave",
204
+ kraken: "squid-doc",
205
+ shellstack: "stack-hex",
206
+ crew: "crew-bot"
207
+ };
208
+
209
+ // src/suite/icons/glyph-parts.tsx
210
+ var import_jsx_runtime2 = require("react/jsx-runtime");
211
+ function GlyphElement({ element }) {
212
+ const fill = element.filled ? "currentColor" : "none";
213
+ const stroke = element.filled ? "none" : void 0;
214
+ switch (element.kind) {
215
+ case "path":
216
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: element.d, fill, stroke });
217
+ case "circle":
218
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
219
+ "circle",
220
+ {
221
+ cx: element.cx,
222
+ cy: element.cy,
223
+ r: element.r,
224
+ fill,
225
+ stroke
226
+ }
227
+ );
228
+ case "rect":
229
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
230
+ "rect",
231
+ {
232
+ x: element.x,
233
+ y: element.y,
234
+ width: element.width,
235
+ height: element.height,
236
+ rx: element.rx,
237
+ fill,
238
+ stroke
239
+ }
240
+ );
241
+ }
242
+ }
243
+ function GlyphParts({
244
+ elements,
245
+ accentClassName
246
+ }) {
247
+ const base = elements.filter((el) => !el.accent);
248
+ const accented = elements.filter((el) => el.accent);
249
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
250
+ base.map((el, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GlyphElement, { element: el }, i)),
251
+ accented.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
252
+ "g",
253
+ {
254
+ className: accentClassName,
255
+ style: { color: "var(--suite-icon-accent, currentColor)" },
256
+ children: accented.map((el, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GlyphElement, { element: el }, i))
257
+ }
258
+ ) : null
259
+ ] });
260
+ }
261
+
262
+ // src/suite/icons/glyphs.ts
263
+ var SUITE_GLYPHS = {
264
+ "timer-shell": {
265
+ description: "TurtleTime \u2014 turtle-shell dome holding clock hands",
266
+ elements: [
267
+ { kind: "path", d: "M3 17h18" },
268
+ { kind: "path", d: "M6.5 17a5.5 5.5 0 0 1 11 0" },
269
+ { kind: "circle", cx: 4, cy: 15.3, r: 1.5 },
270
+ { kind: "path", d: "M12 17v-3.4", accent: true },
271
+ { kind: "path", d: "M12 17l2.4-1.4", accent: true }
272
+ ]
273
+ },
274
+ "kanban-wave": {
275
+ description: "Tides \u2014 three kanban bars riding a wave",
276
+ elements: [
277
+ { kind: "rect", x: 5.5, y: 8, width: 3, height: 8, rx: 1.5 },
278
+ { kind: "rect", x: 10.5, y: 5.5, width: 3, height: 10.5, rx: 1.5 },
279
+ { kind: "rect", x: 15.5, y: 9.5, width: 3, height: 6.5, rx: 1.5 },
280
+ {
281
+ kind: "path",
282
+ d: "M3 19.5q2.25-2.4 4.5 0t4.5 0t4.5 0t4.5 0",
283
+ accent: true
284
+ }
285
+ ]
286
+ },
287
+ "squid-doc": {
288
+ description: "Kraken \u2014 squid with a pen-nib centre tentacle",
289
+ elements: [
290
+ {
291
+ kind: "path",
292
+ d: "M12 3.2c2.7 2.3 4.2 5.3 4.2 9.3H7.8c0-4 1.5-7 4.2-9.3Z"
293
+ },
294
+ { kind: "circle", cx: 10.1, cy: 9, r: 0.9, filled: true },
295
+ { kind: "circle", cx: 13.9, cy: 9, r: 0.9, filled: true },
296
+ { kind: "path", d: "M9.4 12.4v4.8q0 2.3-2.3 2.3" },
297
+ { kind: "path", d: "M14.6 12.4v4.8q0 2.3 2.3 2.3" },
298
+ { kind: "path", d: "M12 12.4v7.1", accent: true }
299
+ ]
300
+ },
301
+ "stack-hex": {
302
+ description: "ShellStack \u2014 hexagon with the resolving spiral",
303
+ elements: [
304
+ { kind: "path", d: "M18 12l-3 5.2H9l-3-5.2 3-5.2h6Z" },
305
+ { kind: "path", d: "M14.2 9.3H9.8v5.4h4.4v-2.7h-2.2", accent: true }
306
+ ]
307
+ },
308
+ "crew-bot": {
309
+ description: "Crew \u2014 friendly robot head with antenna and smile",
310
+ elements: [
311
+ { kind: "path", d: "M12 7.5V5" },
312
+ { kind: "circle", cx: 12, cy: 4, r: 1, filled: true },
313
+ { kind: "rect", x: 5, y: 8, width: 14, height: 9.5, rx: 3 },
314
+ { kind: "path", d: "M5 11.8H3.4" },
315
+ { kind: "path", d: "M19 11.8h1.6" },
316
+ { kind: "circle", cx: 9.2, cy: 11.8, r: 1, filled: true },
317
+ { kind: "circle", cx: 14.8, cy: 11.8, r: 1, filled: true },
318
+ { kind: "path", d: "M9.6 14.6q2.4 1.7 4.8 0", accent: true }
319
+ ]
320
+ },
321
+ workspace: {
322
+ description: "Workspace container \u2014 panel with header bar and content",
323
+ elements: [
324
+ { kind: "rect", x: 3.5, y: 4.5, width: 17, height: 15, rx: 2.5 },
325
+ { kind: "path", d: "M3.5 8.7h17" },
326
+ { kind: "circle", cx: 6.2, cy: 6.6, r: 0.9, accent: true, filled: true },
327
+ { kind: "path", d: "M6.4 12.2h7" },
328
+ { kind: "path", d: "M6.4 15.4h10.5" }
329
+ ]
330
+ },
331
+ organisation: {
332
+ description: "Organisation chart \u2014 root node with two members",
333
+ elements: [
334
+ { kind: "rect", x: 9.5, y: 3.5, width: 5, height: 5, rx: 1.4, accent: true },
335
+ { kind: "path", d: "M12 8.5v2.5" },
336
+ { kind: "path", d: "M6.75 13.5v-2.5h10.5v2.5" },
337
+ { kind: "rect", x: 4.25, y: 13.5, width: 5, height: 5, rx: 1.4 },
338
+ { kind: "rect", x: 14.75, y: 13.5, width: 5, height: 5, rx: 1.4 }
339
+ ]
340
+ },
341
+ "integration-plug": {
342
+ description: "Integration \u2014 two plug halves meeting under a spark",
343
+ elements: [
344
+ { kind: "rect", x: 3.2, y: 9.8, width: 5.6, height: 4.6, rx: 1.4 },
345
+ { kind: "path", d: "M8.8 11h2.4" },
346
+ { kind: "path", d: "M8.8 13.2h2.4" },
347
+ { kind: "rect", x: 15.2, y: 9.8, width: 5.6, height: 4.6, rx: 1.4 },
348
+ { kind: "path", d: "M15.2 11h-2.4" },
349
+ { kind: "path", d: "M15.2 13.2h-2.4" },
350
+ { kind: "path", d: "M6 14.4v2q0 1.4-1.4 1.4H3.4" },
351
+ { kind: "path", d: "M18 14.4v2q0 1.4 1.4 1.4h1.2" },
352
+ { kind: "path", d: "M12 4.4v2.4", accent: true },
353
+ { kind: "path", d: "M10.4 5l3.2 1.2", accent: true },
354
+ { kind: "path", d: "M13.6 5l-3.2 1.2", accent: true }
355
+ ]
356
+ },
357
+ "suite-grid": {
358
+ description: "Suite app switcher \u2014 four tiles, the active app filled",
359
+ elements: [
360
+ { kind: "rect", x: 4, y: 4, width: 7, height: 7, rx: 2 },
361
+ { kind: "rect", x: 13, y: 4, width: 7, height: 7, rx: 2 },
362
+ { kind: "rect", x: 4, y: 13, width: 7, height: 7, rx: 2 },
363
+ {
364
+ kind: "rect",
365
+ x: 13,
366
+ y: 13,
367
+ width: 7,
368
+ height: 7,
369
+ rx: 2,
370
+ accent: true,
371
+ filled: true
372
+ }
373
+ ]
374
+ },
375
+ "bell-wave": {
376
+ description: "Notifications \u2014 bell with sound waves",
377
+ elements: [
378
+ { kind: "path", d: "M7 9.2a5 5 0 0 1 10 0c0 5.3 2.4 7.3 2.4 7.3H4.6S7 14.5 7 9.2" },
379
+ { kind: "path", d: "M10.7 19.3a1.5 1.5 0 0 0 2.6 0" },
380
+ { kind: "path", d: "M17.9 6.4a5.5 5.5 0 0 1 1.4 3.7", accent: true },
381
+ { kind: "path", d: "M15.7 4a9.4 9.4 0 0 1 2.4 6", accent: true }
382
+ ]
383
+ },
384
+ "today-sun": {
385
+ description: "Today \u2014 sun with slowly turning rays (motion-safe)",
386
+ animatedAccent: true,
387
+ elements: [
388
+ { kind: "circle", cx: 12, cy: 12, r: 4 },
389
+ { kind: "path", d: "M12 2.8v2.4", accent: true },
390
+ { kind: "path", d: "M12 18.8v2.4", accent: true },
391
+ { kind: "path", d: "M2.8 12h2.4", accent: true },
392
+ { kind: "path", d: "M18.8 12h2.4", accent: true },
393
+ { kind: "path", d: "M5.5 5.5l1.7 1.7", accent: true },
394
+ { kind: "path", d: "M16.8 16.8l1.7 1.7", accent: true },
395
+ { kind: "path", d: "M5.5 18.5l1.7-1.7", accent: true },
396
+ { kind: "path", d: "M16.8 7.2l1.7-1.7", accent: true }
397
+ ]
398
+ }
399
+ };
400
+ var SUITE_ICON_NAMES = Object.keys(SUITE_GLYPHS);
401
+
402
+ // src/suite/icons/suite-app-icon.tsx
403
+ var import_jsx_runtime3 = require("react/jsx-runtime");
404
+ var SIZE_PRESETS = { sm: 28, md: 36, lg: 44 };
405
+ function SuiteAppIcon({
406
+ app,
407
+ size = "md",
408
+ label,
409
+ className,
410
+ style
411
+ }) {
412
+ const px = typeof size === "number" ? size : SIZE_PRESETS[size];
413
+ const brand = APP_ACCENTS[app];
414
+ const glyph = SUITE_GLYPHS[APP_GLYPHS[app]];
415
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
416
+ "svg",
417
+ {
418
+ width: px,
419
+ height: px,
420
+ viewBox: "0 0 24 24",
421
+ className: cn("shrink-0", className),
422
+ style,
423
+ role: label ? "img" : void 0,
424
+ "aria-label": label,
425
+ "aria-hidden": label ? void 0 : true,
426
+ children: [
427
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "1", y: "1", width: "22", height: "22", rx: "5.5", fill: brand.tile }),
428
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
429
+ "g",
430
+ {
431
+ transform: "translate(4.4 4.4) scale(0.6333)",
432
+ fill: "none",
433
+ stroke: "currentColor",
434
+ strokeWidth: 2.7,
435
+ strokeLinecap: "round",
436
+ strokeLinejoin: "round",
437
+ style: {
438
+ color: brand.onTile,
439
+ "--suite-icon-accent": brand.onTileAccent
440
+ },
441
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GlyphParts, { elements: glyph.elements })
442
+ }
443
+ )
444
+ ]
445
+ }
446
+ );
447
+ }
448
+
449
+ // src/suite/icons/suite-icon.tsx
450
+ var import_jsx_runtime4 = require("react/jsx-runtime");
451
+ var TODAY_SUN_STYLE = `
452
+ @media (prefers-reduced-motion: no-preference) {
453
+ .suite-icon-today-sun-rays {
454
+ animation: suite-icon-today-sun-spin 18s linear infinite;
455
+ transform-box: fill-box;
456
+ transform-origin: center;
457
+ }
458
+ }
459
+ @keyframes suite-icon-today-sun-spin {
460
+ to { transform: rotate(360deg); }
461
+ }
462
+ `;
463
+ function SuiteIcon({
464
+ name,
465
+ size = 24,
466
+ accent,
467
+ strokeWidth = 2,
468
+ animated = true,
469
+ label,
470
+ className,
471
+ style
472
+ }) {
473
+ const glyph = SUITE_GLYPHS[name];
474
+ const accentHex = accent ? APP_ACCENTS[accent].accent : void 0;
475
+ const mergedStyle = accentHex ? __spreadProps(__spreadValues({}, style), { "--suite-icon-accent": accentHex }) : style;
476
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
477
+ "svg",
478
+ {
479
+ width: size,
480
+ height: size,
481
+ viewBox: "0 0 24 24",
482
+ fill: "none",
483
+ stroke: "currentColor",
484
+ strokeWidth,
485
+ strokeLinecap: "round",
486
+ strokeLinejoin: "round",
487
+ className: cn("shrink-0", className),
488
+ style: mergedStyle,
489
+ role: label ? "img" : void 0,
490
+ "aria-label": label,
491
+ "aria-hidden": label ? void 0 : true,
492
+ children: [
493
+ glyph.animatedAccent && animated ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: TODAY_SUN_STYLE }) : null,
494
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
495
+ GlyphParts,
496
+ {
497
+ elements: glyph.elements,
498
+ accentClassName: glyph.animatedAccent && animated ? "suite-icon-today-sun-rays" : void 0
499
+ }
500
+ )
501
+ ]
502
+ }
503
+ );
504
+ }
505
+
506
+ // src/suite/components/today-ui.tsx
507
+ var import_jsx_runtime5 = require("react/jsx-runtime");
508
+ var APP_META = {
509
+ shellstack: { name: "ShellStack" },
510
+ tides: { name: "Tides" },
511
+ turtletime: { name: "TurtleTime" },
512
+ kraken: { name: "Kraken" }
513
+ };
514
+ var SUITE_APPS = [
515
+ "shellstack",
516
+ "tides",
517
+ "turtletime",
518
+ "kraken"
519
+ ];
520
+ function sourceToSuiteApp(source) {
521
+ if (source === "tides" || source === "turtletime" || source === "kraken") {
522
+ return source;
523
+ }
524
+ return "shellstack";
525
+ }
526
+ function suiteAppLabel(source) {
527
+ return APP_META[sourceToSuiteApp(source)].name;
528
+ }
529
+ var SOURCE_APP_GLYPH = {
530
+ shellstack: "stack-hex",
531
+ tides: "kanban-wave",
532
+ turtletime: "timer-shell",
533
+ kraken: "squid-doc"
534
+ };
535
+ function SourceAppGlyph({
536
+ source,
537
+ className
538
+ }) {
539
+ const app = sourceToSuiteApp(source);
540
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
541
+ SuiteIcon,
542
+ {
543
+ name: SOURCE_APP_GLYPH[app],
544
+ accent: app,
545
+ className: cn("h-4 w-4 shrink-0 text-ph-subtle", className)
546
+ }
547
+ );
548
+ }
549
+ function formatLongDate(date = /* @__PURE__ */ new Date()) {
550
+ return new Intl.DateTimeFormat(void 0, {
551
+ month: "long",
552
+ day: "numeric"
553
+ }).format(date);
554
+ }
555
+ function SuiteAppStrip({
556
+ className,
557
+ icons
558
+ }) {
559
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
560
+ "div",
561
+ {
562
+ className: cn(
563
+ "flex flex-wrap items-center justify-center gap-2.5",
564
+ className
565
+ ),
566
+ "aria-label": "ShellStack suite apps",
567
+ children: SUITE_APPS.map((app) => {
568
+ var _a;
569
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
570
+ "div",
571
+ {
572
+ className: "flex items-center gap-2 rounded-full border border-ph-border bg-ph-surface px-2.5 py-1.5 shadow-sm",
573
+ children: [
574
+ (_a = icons == null ? void 0 : icons[app]) != null ? _a : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SuiteAppIcon, { app, size: "sm" }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs font-medium text-ph-subtle", children: APP_META[app].name })
576
+ ]
577
+ },
578
+ app
579
+ );
580
+ })
581
+ }
582
+ );
583
+ }
584
+ function AnimatedSun({ className }) {
585
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
586
+ "svg",
587
+ {
588
+ width: "40",
589
+ height: "40",
590
+ viewBox: "0 0 40 40",
591
+ fill: "none",
592
+ xmlns: "http://www.w3.org/2000/svg",
593
+ className: cn(className),
594
+ "aria-hidden": "true",
595
+ children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("linearGradient", { id: "animatedSunCenter", x1: "0", y1: "0", x2: "40", y2: "40", children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("stop", { stopColor: "#f59e0b" }),
598
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("stop", { offset: "1", stopColor: "#fb923c" })
599
+ ] }) }),
600
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("g", { className: "origin-center motion-safe:animate-[spin_20s_linear_infinite] motion-reduce:animate-none", children: [0, 45, 90, 135, 180, 225, 270, 315].map((angle) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
601
+ "line",
602
+ {
603
+ x1: "20",
604
+ y1: "4",
605
+ x2: "20",
606
+ y2: "9",
607
+ stroke: "#fbbf24",
608
+ strokeWidth: "2",
609
+ strokeLinecap: "round",
610
+ transform: `rotate(${angle} 20 20)`
611
+ },
612
+ angle
613
+ )) }),
614
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "20", cy: "20", r: "9", fill: "url(#animatedSunCenter)" })
615
+ ]
616
+ }
617
+ );
618
+ }
619
+ function AnimatedMoon({ className }) {
620
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
621
+ "svg",
622
+ {
623
+ width: "40",
624
+ height: "40",
625
+ viewBox: "0 0 40 40",
626
+ fill: "none",
627
+ xmlns: "http://www.w3.org/2000/svg",
628
+ className: cn(className),
629
+ "aria-hidden": "true",
630
+ children: [
631
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("linearGradient", { id: "animatedMoonBody", x1: "0", y1: "0", x2: "40", y2: "40", children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("stop", { stopColor: "#a5b4fc" }),
633
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("stop", { offset: "1", stopColor: "#818cf8" })
634
+ ] }) }),
635
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
636
+ "path",
637
+ {
638
+ d: "M27.5 25.5A11 11 0 1 1 14.5 9.6a9 9 0 1 0 13 15.9Z",
639
+ fill: "url(#animatedMoonBody)"
640
+ }
641
+ ),
642
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("g", { className: "motion-safe:animate-[pulse_4s_ease-in-out_infinite] motion-reduce:animate-none", children: [
643
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "29", cy: "10", r: "1.4", fill: "#c7d2fe" }),
644
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "34", cy: "16", r: "1", fill: "#c7d2fe" }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "25", cy: "5.5", r: "0.9", fill: "#c7d2fe" })
646
+ ] })
647
+ ]
648
+ }
649
+ );
650
+ }
651
+ function TodayTimeIcon({ className }) {
652
+ const hour = (/* @__PURE__ */ new Date()).getHours();
653
+ const isNight = hour >= 18 || hour < 6;
654
+ return isNight ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AnimatedMoon, { className }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AnimatedSun, { className });
655
+ }
656
+ function TodayTopBar({
657
+ refreshing,
658
+ loading,
659
+ onRefresh,
660
+ appIcon,
661
+ appName
662
+ }) {
663
+ const icon = appIcon != null ? appIcon : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TodayTimeIcon, { className: "h-6 w-6 sm:h-7 sm:w-7 text-amber-500" });
664
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { className: "flex items-center justify-between gap-4", children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 items-center gap-3", children: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hidden shrink-0 sm:block", "aria-hidden": "true", children: icon }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
668
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "font-display text-[1.625rem] font-bold leading-tight tracking-tight text-ph-ink sm:text-[2.25rem]", children: "Today" }),
669
+ appName ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "rounded-md bg-ph-muted px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-ph-subtle", children: appName }) : null,
670
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-sm text-ph-subtle", children: formatLongDate() })
671
+ ] })
672
+ ] }),
673
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex shrink-0 items-center", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
674
+ "button",
675
+ {
676
+ type: "button",
677
+ onClick: onRefresh,
678
+ disabled: refreshing || loading,
679
+ className: "press-subtle inline-flex h-10 w-10 items-center justify-center rounded-lg text-ph-subtle transition hover:bg-ph-surface hover:text-ph-ink disabled:opacity-50",
680
+ "data-test": "today-refresh",
681
+ "aria-label": "Refresh today",
682
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
683
+ import_iconoir_react.Refresh,
684
+ {
685
+ className: cn("h-4 w-4", refreshing && "animate-spin"),
686
+ "aria-hidden": true
687
+ }
688
+ )
689
+ }
690
+ ) })
691
+ ] });
692
+ }
693
+ function getTodayGreeting(user, stats) {
694
+ var _a, _b, _c, _d, _e;
695
+ const hour = (/* @__PURE__ */ new Date()).getHours();
696
+ let period = "morning";
697
+ if (hour >= 12 && hour < 17) period = "afternoon";
698
+ else if (hour >= 17 && hour < 21) period = "evening";
699
+ else if (hour >= 21 || hour < 5) period = "night";
700
+ const name = (user == null ? void 0 : user.first_name) || ((_a = user == null ? void 0 : user.name) == null ? void 0 : _a.split(" ")[0]) || ((_b = user == null ? void 0 : user.email) == null ? void 0 : _b.split("@")[0]) || null;
701
+ let greeting = `Good ${period}`;
702
+ if (name) greeting += `, ${name}`;
703
+ const taskCount = (_c = stats == null ? void 0 : stats.taskCount) != null ? _c : 0;
704
+ const overdueCount = (_d = stats == null ? void 0 : stats.overdueCount) != null ? _d : 0;
705
+ const hours = (_e = stats == null ? void 0 : stats.hours) != null ? _e : 0;
706
+ if (overdueCount > 0) {
707
+ return `${greeting}. You have ${taskCount} task${taskCount === 1 ? "" : "s"} due today, ${overdueCount} overdue.`;
708
+ }
709
+ if (taskCount > 0) {
710
+ return `${greeting}. You have ${taskCount} task${taskCount === 1 ? "" : "s"} due today.`;
711
+ }
712
+ if (hours > 0) {
713
+ return `${greeting}. You logged ${hours} hour${hours === 1 ? "" : "s"} today.`;
714
+ }
715
+ if (period === "evening" || period === "night") {
716
+ return `${greeting}. Time to wrap up.`;
717
+ }
718
+ return `${greeting}. Nothing overdue \u2014 take a nice, deep breath.`;
719
+ }
720
+ function TodayHero({
721
+ message = "Take a nice, deep breath."
722
+ }) {
723
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-8 text-center sm:mt-10", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
724
+ "p",
725
+ {
726
+ className: "today-hero font-display text-2xl font-bold tracking-tight text-ph-ink sm:text-[1.75rem] sm:leading-tight",
727
+ children: message
728
+ },
729
+ typeof message === "string" ? message : "hero"
730
+ ) });
731
+ }
732
+ function TodaySection({
733
+ icon: Icon,
734
+ iconClassName,
735
+ title,
736
+ description,
737
+ action,
738
+ testId,
739
+ children
740
+ }) {
741
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "data-test": testId, className: "space-y-4", children: [
742
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-start justify-between gap-4", children: [
743
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 items-start gap-3.5", children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
745
+ "span",
746
+ {
747
+ className: cn(
748
+ "inline-flex h-10 w-10 sm:h-11 sm:w-11 shrink-0 items-center justify-center rounded-2xl shadow-sm ring-1 ring-black/[0.06]",
749
+ iconClassName != null ? iconClassName : "bg-ph-muted text-ph-ink"
750
+ ),
751
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Icon, { className: "h-5 w-5", "aria-hidden": true })
752
+ }
753
+ ),
754
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "min-w-0 pt-0.5", children: [
755
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "font-display text-lg font-bold tracking-tight text-ph-ink", children: title }),
756
+ description ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1 max-w-xl text-sm leading-relaxed text-ph-subtle", children: description }) : null
757
+ ] })
758
+ ] }),
759
+ action
760
+ ] }),
761
+ children
762
+ ] });
763
+ }
764
+ function TodayPanel({
765
+ children,
766
+ className
767
+ }) {
768
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
769
+ "div",
770
+ {
771
+ className: cn(
772
+ "border-ph-border/70 rounded-2xl border bg-ph-surface p-6 shadow-[0_2px_28px_-10px_rgba(15,23,42,0.14)] transition-all duration-200 ease-out sm:p-6",
773
+ "hover:shadow-[0_8px_32px_-12px_rgba(15,23,42,0.18)] hover:-translate-y-0.5 motion-reduce:transition-none motion-reduce:hover:transform-none",
774
+ className
775
+ ),
776
+ children
777
+ }
778
+ );
779
+ }
780
+ function TodayEmptyState({
781
+ title,
782
+ description
783
+ }) {
784
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "border-ph-border/80 rounded-xl border border-dashed px-5 py-8 text-center", children: [
785
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-sm font-semibold text-ph-ink", children: title }),
786
+ description ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1.5 text-sm leading-relaxed text-ph-subtle", children: description }) : null
787
+ ] });
788
+ }
789
+
790
+ // src/suite/components/app-switcher.tsx
791
+ var import_image = __toESM(require("next/image"));
792
+ var import_react = require("react");
793
+ var import_iconoir_react2 = require("iconoir-react");
794
+ var import_jsx_runtime6 = require("react/jsx-runtime");
795
+ function appSwitcherTriggerClass(open, collapsed = false) {
796
+ return cn(
797
+ "group flex min-w-0 items-center gap-2 rounded-lg border px-2 py-1.5 text-left transition-all duration-200",
798
+ collapsed ? "h-11 w-11 justify-center px-0 py-0" : "w-full",
799
+ open ? "border-ph-brand/30 bg-ph-muted shadow-[0_1px_2px_rgba(0,0,0,0.06)] ring-1 ring-ph-brand/15" : "border-transparent hover:border-ph-border hover:bg-ph-muted/80 hover:shadow-sm"
800
+ );
801
+ }
802
+ function appSwitcherMarkClass() {
803
+ return "h-8 w-8 shrink-0 rounded-lg shadow-[0_1px_2px_rgba(0,0,0,0.1),0_4px_12px_rgba(0,0,0,0.08)] ring-1 ring-black/[0.06]";
804
+ }
805
+ function appSwitcherMenuItemClass() {
806
+ return "rounded-lg px-2 py-2 data-[highlighted]:bg-transparent hover:bg-ph-muted/80";
807
+ }
808
+ function AppSwitcherMark({
809
+ children,
810
+ collapsed
811
+ }) {
812
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
813
+ "span",
814
+ {
815
+ className: cn(
816
+ "relative shrink-0",
817
+ collapsed && "group-hover:scale-[1.02]"
818
+ ),
819
+ children
820
+ }
821
+ );
822
+ }
823
+ function AppSwitcherChevron({
824
+ open,
825
+ collapsed = false
826
+ }) {
827
+ if (collapsed) {
828
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
829
+ "span",
830
+ {
831
+ className: cn(
832
+ "absolute bottom-0 right-0 flex h-3 w-3 items-center justify-center rounded-full border border-ph-border bg-ph-surface shadow-sm transition-colors",
833
+ open && "border-ph-brand/40 bg-ph-brand/10"
834
+ ),
835
+ "aria-hidden": true,
836
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
837
+ import_iconoir_react2.NavArrowDown,
838
+ {
839
+ className: cn(
840
+ "h-2 w-2 text-ph-mutedtext transition-transform duration-200",
841
+ open && "rotate-180 text-ph-brand"
842
+ )
843
+ }
844
+ )
845
+ }
846
+ );
847
+ }
848
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
849
+ "span",
850
+ {
851
+ className: cn(
852
+ "border-ph-border/80 flex h-5 w-5 shrink-0 items-center justify-center rounded-lg border bg-ph-canvas shadow-sm transition-all duration-200",
853
+ open ? "border-ph-brand/35 bg-ph-brand/10 text-ph-brand" : "text-ph-mutedtext group-hover:border-ph-border group-hover:bg-ph-surface"
854
+ ),
855
+ "aria-hidden": true,
856
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
857
+ import_iconoir_react2.NavArrowDown,
858
+ {
859
+ className: cn(
860
+ "h-3 w-3 transition-transform duration-200",
861
+ open && "rotate-180"
862
+ )
863
+ }
864
+ )
865
+ }
866
+ );
867
+ }
868
+ function AppSwitcher({
869
+ apps,
870
+ currentApp,
871
+ onSelect,
872
+ mark,
873
+ title,
874
+ collapsed = false,
875
+ dropdownMenu: DropdownMenu2,
876
+ dropdownItem: DropdownItem2
877
+ }) {
878
+ const [open, setOpen] = (0, import_react.useState)(false);
879
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
880
+ DropdownMenu2,
881
+ {
882
+ "aria-label": "Switch application",
883
+ className: collapsed ? "w-11 shrink-0" : "min-w-0 flex-1",
884
+ panelClassName: "w-64",
885
+ align: "start",
886
+ open,
887
+ onOpenChange: setOpen,
888
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
889
+ "span",
890
+ {
891
+ className: appSwitcherTriggerClass(open, collapsed),
892
+ "data-test": "app-switcher-trigger",
893
+ "aria-expanded": open,
894
+ children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(AppSwitcherMark, { collapsed, children: [
896
+ mark,
897
+ collapsed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AppSwitcherChevron, { open, collapsed: true }) : null
898
+ ] }),
899
+ !collapsed ? title : null,
900
+ !collapsed ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AppSwitcherChevron, { open }) : null
901
+ ]
902
+ }
903
+ ),
904
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-1", "data-test": "app-switcher-menu", children: [
905
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.14em] text-ph-mutedtext", children: "Switch application" }),
906
+ apps.map((app) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
907
+ DropdownItem2,
908
+ {
909
+ "data-test": `switch-app-${app.slug}`,
910
+ onMouseDown: (e) => e.preventDefault(),
911
+ onClick: () => {
912
+ setOpen(false);
913
+ onSelect(app);
914
+ },
915
+ className: appSwitcherMenuItemClass(),
916
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "flex min-w-0 items-center gap-3", children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
918
+ import_image.default,
919
+ {
920
+ src: app.icon,
921
+ alt: "",
922
+ width: 28,
923
+ height: 28,
924
+ className: "h-7 w-7 shrink-0 rounded-md object-contain"
925
+ }
926
+ ),
927
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "min-w-0 flex-1", children: [
928
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate text-sm font-medium text-ph-ink", children: app.name }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "block truncate text-xs text-ph-mutedtext", children: app.description })
930
+ ] }),
931
+ app.slug === currentApp ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
932
+ import_iconoir_react2.Check,
933
+ {
934
+ className: "h-4 w-4 shrink-0 text-ph-brand",
935
+ "aria-hidden": "true"
936
+ }
937
+ ) : null
938
+ ] })
939
+ },
940
+ app.slug
941
+ ))
942
+ ] })
943
+ }
944
+ );
945
+ }
946
+
947
+ // src/suite/components/suite-notification-bell.tsx
948
+ var import_react2 = require("react");
949
+ var import_navigation = require("next/navigation");
950
+ var import_iconoir_react3 = require("iconoir-react");
951
+ var import_jsx_runtime7 = require("react/jsx-runtime");
952
+ var POLL_MS = 6e4;
953
+ function timeAgo(iso) {
954
+ const then = new Date(iso).getTime();
955
+ if (Number.isNaN(then)) return "";
956
+ const secs = Math.max(0, Math.round((Date.now() - then) / 1e3));
957
+ if (secs < 60) return "just now";
958
+ const mins = Math.round(secs / 60);
959
+ if (mins < 60) return `${mins}m ago`;
960
+ const hrs = Math.round(mins / 60);
961
+ if (hrs < 24) return `${hrs}h ago`;
962
+ return `${Math.round(hrs / 24)}d ago`;
963
+ }
964
+ function SuiteNotificationBell({
965
+ fetchNotifications,
966
+ markRead,
967
+ markAllRead,
968
+ dropdownMenu: DropdownMenu2,
969
+ cacheKey = "suite-notifications-cache"
970
+ }) {
971
+ const router = (0, import_navigation.useRouter)();
972
+ const [open, setOpen] = (0, import_react2.useState)(false);
973
+ const [items, setItems] = (0, import_react2.useState)([]);
974
+ const [unread, setUnread] = (0, import_react2.useState)(0);
975
+ const load = (0, import_react2.useCallback)(async () => {
976
+ try {
977
+ const data = await fetchNotifications();
978
+ setItems(data.notifications);
979
+ setUnread(data.unread_count);
980
+ if (cacheKey) {
981
+ try {
982
+ sessionStorage.setItem(
983
+ cacheKey,
984
+ JSON.stringify({
985
+ notifications: data.notifications,
986
+ unread_count: data.unread_count
987
+ })
988
+ );
989
+ } catch (e) {
990
+ }
991
+ }
992
+ } catch (e) {
993
+ }
994
+ }, [fetchNotifications, cacheKey]);
995
+ (0, import_react2.useEffect)(() => {
996
+ if (cacheKey) {
997
+ queueMicrotask(() => {
998
+ var _a;
999
+ try {
1000
+ const raw = sessionStorage.getItem(cacheKey);
1001
+ if (raw) {
1002
+ const cached = JSON.parse(raw);
1003
+ if (Array.isArray(cached.notifications)) {
1004
+ setItems(cached.notifications);
1005
+ setUnread((_a = cached.unread_count) != null ? _a : 0);
1006
+ }
1007
+ }
1008
+ } catch (e) {
1009
+ }
1010
+ });
1011
+ }
1012
+ const kick = window.setTimeout(() => void load(), 0);
1013
+ const id = window.setInterval(() => void load(), POLL_MS);
1014
+ return () => {
1015
+ window.clearTimeout(kick);
1016
+ window.clearInterval(id);
1017
+ };
1018
+ }, [load, cacheKey]);
1019
+ const onOpenChange = (0, import_react2.useCallback)(
1020
+ (next) => {
1021
+ setOpen(next);
1022
+ if (next) void load();
1023
+ },
1024
+ [load]
1025
+ );
1026
+ const openItem = (0, import_react2.useCallback)(
1027
+ async (n) => {
1028
+ setOpen(false);
1029
+ if (!n.read_at) {
1030
+ setItems(
1031
+ (prev) => prev.map(
1032
+ (i) => i.id === n.id ? __spreadProps(__spreadValues({}, i), { read_at: (/* @__PURE__ */ new Date()).toISOString() }) : i
1033
+ )
1034
+ );
1035
+ setUnread((u) => Math.max(0, u - 1));
1036
+ try {
1037
+ await markRead(n.id);
1038
+ } catch (e) {
1039
+ }
1040
+ }
1041
+ if (!n.href) return;
1042
+ if (/^https?:\/\//i.test(n.href)) window.location.assign(n.href);
1043
+ else router.push(n.href);
1044
+ },
1045
+ [router, markRead]
1046
+ );
1047
+ const markAll = (0, import_react2.useCallback)(async () => {
1048
+ setItems(
1049
+ (prev) => prev.map((i) => {
1050
+ var _a;
1051
+ return __spreadProps(__spreadValues({}, i), {
1052
+ read_at: (_a = i.read_at) != null ? _a : (/* @__PURE__ */ new Date()).toISOString()
1053
+ });
1054
+ })
1055
+ );
1056
+ setUnread(0);
1057
+ try {
1058
+ await markAllRead();
1059
+ } catch (e) {
1060
+ }
1061
+ }, [markAllRead]);
1062
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1063
+ DropdownMenu2,
1064
+ {
1065
+ "aria-label": "Notifications",
1066
+ align: "end",
1067
+ panelClassName: "w-80 overflow-hidden p-0",
1068
+ open,
1069
+ onOpenChange,
1070
+ trigger: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "relative inline-flex h-9 w-9 items-center justify-center rounded-full text-ph-mutedtext transition hover:bg-ph-muted hover:text-ph-ink", children: [
1071
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_iconoir_react3.Bell, { className: "h-5 w-5", strokeWidth: 1.75, "aria-hidden": true }),
1072
+ unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-ph-brand px-1 text-[10px] font-semibold leading-none text-white ring-2 ring-ph-surface", children: unread > 9 ? "9+" : unread }) : null
1073
+ ] }),
1074
+ children: [
1075
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between border-b border-ph-border px-3 py-2", children: [
1076
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-semibold text-ph-ink", children: "Notifications" }),
1077
+ unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1078
+ "button",
1079
+ {
1080
+ type: "button",
1081
+ onClick: markAll,
1082
+ className: "text-xs font-medium text-ph-brand hover:underline",
1083
+ children: "Mark all read"
1084
+ }
1085
+ ) : null
1086
+ ] }),
1087
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "max-h-80 overflow-y-auto py-1", children: items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "px-3 py-6 text-center text-sm text-ph-mutedtext", children: "You're all caught up." }) : items.map((n) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1088
+ "button",
1089
+ {
1090
+ type: "button",
1091
+ onClick: () => void openItem(n),
1092
+ className: "flex w-full flex-col gap-0.5 px-3 py-2 text-left transition hover:bg-ph-muted",
1093
+ children: [
1094
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "flex items-center gap-2", children: [
1095
+ !n.read_at ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1096
+ "span",
1097
+ {
1098
+ className: "h-1.5 w-1.5 shrink-0 rounded-full bg-ph-brand",
1099
+ "aria-hidden": true
1100
+ }
1101
+ ) : null,
1102
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm font-medium text-ph-ink", children: n.title })
1103
+ ] }),
1104
+ n.body ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "line-clamp-2 text-xs text-ph-mutedtext", children: n.body }) : null,
1105
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "text-[11px] uppercase tracking-wide text-ph-subtle", children: [
1106
+ n.source_app,
1107
+ " \xB7 ",
1108
+ timeAgo(n.created_at)
1109
+ ] })
1110
+ ]
1111
+ },
1112
+ n.id
1113
+ )) })
1114
+ ]
1115
+ }
1116
+ );
1117
+ }
1118
+
1119
+ // src/suite/components/command-palette-host.tsx
1120
+ var import_react3 = require("react");
1121
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1122
+ function isEditableTarget() {
1123
+ if (typeof document === "undefined") return false;
1124
+ const el = document.activeElement;
1125
+ return el instanceof HTMLElement && Boolean(
1126
+ el.closest(
1127
+ "input, textarea, select, [contenteditable='true'], [role='textbox']"
1128
+ )
1129
+ );
1130
+ }
1131
+ function CommandPaletteHost({
1132
+ items,
1133
+ isAuthenticated = true,
1134
+ placeholder = "Type a command or search\u2026",
1135
+ commandPalette: CommandPalette
1136
+ }) {
1137
+ const [open, setOpen] = (0, import_react3.useState)(false);
1138
+ (0, import_react3.useEffect)(() => {
1139
+ const onKey = (e) => {
1140
+ if (!((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k")) return;
1141
+ if (!open && isEditableTarget()) return;
1142
+ e.preventDefault();
1143
+ setOpen((o) => !o);
1144
+ };
1145
+ window.addEventListener("keydown", onKey);
1146
+ return () => window.removeEventListener("keydown", onKey);
1147
+ }, [open]);
1148
+ if (!isAuthenticated) return null;
1149
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1150
+ CommandPalette,
1151
+ {
1152
+ items,
1153
+ isOpen: open,
1154
+ onClose: () => setOpen(false),
1155
+ placeholder
1156
+ }
1157
+ );
1158
+ }
1159
+
1160
+ // src/suite/lib/use-idle-mount.ts
1161
+ var import_react4 = require("react");
1162
+ function useIdleMount(timeoutMs = 1200) {
1163
+ const [ready, setReady] = (0, import_react4.useState)(false);
1164
+ (0, import_react4.useEffect)(() => {
1165
+ let timeoutId = 0;
1166
+ let idleId = 0;
1167
+ const activate = () => setReady(true);
1168
+ if (typeof window.requestIdleCallback === "function") {
1169
+ idleId = window.requestIdleCallback(activate, { timeout: timeoutMs });
1170
+ return () => window.cancelIdleCallback(idleId);
1171
+ }
1172
+ timeoutId = window.setTimeout(activate, Math.min(timeoutMs, 200));
1173
+ return () => window.clearTimeout(timeoutId);
1174
+ }, [timeoutMs]);
1175
+ return ready;
1176
+ }
1177
+
1178
+ // src/suite/components/deferred-chrome.tsx
1179
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1180
+ function DeferredChrome({ children }) {
1181
+ const ready = useIdleMount();
1182
+ if (!ready) return null;
1183
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children });
1184
+ }
1185
+
1186
+ // src/suite/components/suite-mobile-drawer.tsx
1187
+ var import_react5 = require("react");
1188
+ var import_iconoir_react4 = require("iconoir-react");
1189
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1190
+ function SuiteMobileDrawer({
1191
+ open,
1192
+ onClose,
1193
+ children,
1194
+ side = "left",
1195
+ showCloseButton = false,
1196
+ closeLabel = "Close navigation",
1197
+ backdropLabel = "Close navigation",
1198
+ dialogLabel = "Navigation",
1199
+ durationMs = 200,
1200
+ dataTest,
1201
+ panelDataTest,
1202
+ panelClassName
1203
+ }) {
1204
+ const [rendered, setRendered] = (0, import_react5.useState)(open);
1205
+ const [shown, setShown] = (0, import_react5.useState)(open);
1206
+ const panelRef = (0, import_react5.useRef)(null);
1207
+ const onCloseRef = (0, import_react5.useRef)(onClose);
1208
+ (0, import_react5.useEffect)(() => {
1209
+ onCloseRef.current = onClose;
1210
+ }, [onClose]);
1211
+ (0, import_react5.useEffect)(() => {
1212
+ if (open) {
1213
+ queueMicrotask(() => setRendered(true));
1214
+ let inner = 0;
1215
+ const outer = requestAnimationFrame(() => {
1216
+ inner = requestAnimationFrame(() => setShown(true));
1217
+ });
1218
+ return () => {
1219
+ cancelAnimationFrame(outer);
1220
+ cancelAnimationFrame(inner);
1221
+ };
1222
+ }
1223
+ queueMicrotask(() => setShown(false));
1224
+ const timeout = window.setTimeout(() => setRendered(false), durationMs);
1225
+ return () => window.clearTimeout(timeout);
1226
+ }, [open, durationMs]);
1227
+ (0, import_react5.useEffect)(() => {
1228
+ if (!open) return;
1229
+ const onKeyDown = (event) => {
1230
+ if (event.key === "Escape") onCloseRef.current();
1231
+ };
1232
+ window.addEventListener("keydown", onKeyDown);
1233
+ const previousOverflow = document.body.style.overflow;
1234
+ document.body.style.overflow = "hidden";
1235
+ const previouslyFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1236
+ const frame = requestAnimationFrame(() => {
1237
+ var _a;
1238
+ return (_a = panelRef.current) == null ? void 0 : _a.focus();
1239
+ });
1240
+ return () => {
1241
+ window.removeEventListener("keydown", onKeyDown);
1242
+ document.body.style.overflow = previousOverflow;
1243
+ cancelAnimationFrame(frame);
1244
+ if (previouslyFocused == null ? void 0 : previouslyFocused.isConnected) previouslyFocused.focus();
1245
+ };
1246
+ }, [open]);
1247
+ if (!rendered) return null;
1248
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { "data-test": dataTest, className: "no-print fixed inset-0 z-50 lg:hidden", children: [
1249
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1250
+ "button",
1251
+ {
1252
+ type: "button",
1253
+ tabIndex: -1,
1254
+ className: cn(
1255
+ "absolute inset-0 bg-black/25 backdrop-blur-sm motion-safe:transition-opacity motion-safe:ease-spring-subtle",
1256
+ shown ? "opacity-100" : "opacity-0"
1257
+ ),
1258
+ style: { transitionDuration: `${durationMs}ms` },
1259
+ onClick: onClose,
1260
+ "aria-label": backdropLabel
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1264
+ "aside",
1265
+ {
1266
+ ref: panelRef,
1267
+ role: "dialog",
1268
+ "aria-modal": "true",
1269
+ "aria-label": dialogLabel,
1270
+ tabIndex: -1,
1271
+ "data-test": panelDataTest,
1272
+ className: cn(
1273
+ "suite-scroll-lock relative flex h-full w-[86%] max-w-64 flex-col overflow-hidden bg-ph-surface shadow-xl outline-none motion-safe:transition-transform motion-safe:ease-spring-fast",
1274
+ side === "right" && "ml-auto",
1275
+ shown ? "translate-x-0" : side === "right" ? "translate-x-full" : "-translate-x-full",
1276
+ panelClassName
1277
+ ),
1278
+ style: { transitionDuration: `${durationMs}ms` },
1279
+ children: [
1280
+ showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex h-14 shrink-0 items-center justify-end border-b border-ph-border px-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1281
+ "button",
1282
+ {
1283
+ type: "button",
1284
+ onClick: onClose,
1285
+ className: "-mr-1.5 inline-flex h-11 w-11 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1286
+ "aria-label": closeLabel,
1287
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_iconoir_react4.Xmark, { className: "h-4 w-4", "aria-hidden": true })
1288
+ }
1289
+ ) }) : null,
1290
+ children
1291
+ ]
1292
+ }
1293
+ )
1294
+ ] });
1295
+ }
1296
+
1297
+ // src/suite/components/suite-mobile-header.tsx
1298
+ var import_iconoir_react5 = require("iconoir-react");
1299
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1300
+ function SuiteMobileHeader({
1301
+ onMenuClick,
1302
+ menuIcon: MenuIcon = import_iconoir_react5.Menu,
1303
+ title,
1304
+ actions,
1305
+ menuLabel = "Open navigation",
1306
+ menuDataTest,
1307
+ dataTest,
1308
+ className
1309
+ }) {
1310
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1311
+ "header",
1312
+ {
1313
+ "data-test": dataTest,
1314
+ className: cn(
1315
+ "suite-app-bar no-print sticky top-0 z-30 flex min-h-[56px] shrink-0 items-center gap-1 border-b border-ph-border px-2 lg:hidden",
1316
+ className
1317
+ ),
1318
+ children: [
1319
+ onMenuClick ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1320
+ "button",
1321
+ {
1322
+ type: "button",
1323
+ "data-test": menuDataTest,
1324
+ className: "suite-press inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl text-ph-ink transition-colors hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1325
+ onClick: onMenuClick,
1326
+ "aria-label": menuLabel,
1327
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MenuIcon, { className: "h-5 w-5", "aria-hidden": true })
1328
+ }
1329
+ ) : null,
1330
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1331
+ "div",
1332
+ {
1333
+ className: cn(
1334
+ "flex min-w-0 flex-1 items-center",
1335
+ !onMenuClick && "pl-2"
1336
+ ),
1337
+ children: title
1338
+ }
1339
+ ),
1340
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex shrink-0 items-center gap-1", children: actions }) : null
1341
+ ]
1342
+ }
1343
+ );
1344
+ }
1345
+
1346
+ // src/suite/components/suite-user-menu.tsx
1347
+ var import_image2 = __toESM(require("next/image"));
1348
+ var import_navigation2 = require("next/navigation");
1349
+ var import_iconoir_react6 = require("iconoir-react");
1350
+
1351
+ // src/components/ui/DropdownMenu.tsx
1352
+ var DropdownPrimitive = __toESM(require("@radix-ui/react-dropdown-menu"));
1353
+
1354
+ // src/lib/cn.ts
1355
+ function cn2(...parts) {
1356
+ return parts.filter(Boolean).join(" ");
1357
+ }
1358
+
1359
+ // src/components/ui/DropdownMenu.tsx
1360
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1361
+ function DropdownContent({
1362
+ children,
1363
+ align = "start",
1364
+ side = "bottom",
1365
+ sideOffset = 7,
1366
+ alignOffset = 0,
1367
+ collisionPadding = 8,
1368
+ avoidCollisions = true,
1369
+ panelClassName
1370
+ }) {
1371
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownPrimitive.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1372
+ DropdownPrimitive.Content,
1373
+ {
1374
+ className: cn2("ph-dropdown-panel", panelClassName),
1375
+ align,
1376
+ side,
1377
+ sideOffset,
1378
+ alignOffset,
1379
+ collisionPadding,
1380
+ avoidCollisions,
1381
+ sticky: "partial",
1382
+ children: [
1383
+ children,
1384
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownPrimitive.Arrow, { className: "ph-dropdown-arrow", width: 12, height: 6 })
1385
+ ]
1386
+ }
1387
+ ) });
1388
+ }
1389
+ function DropdownMenu(_a) {
1390
+ var _b = _a, {
1391
+ trigger,
1392
+ children,
1393
+ align,
1394
+ side,
1395
+ sideOffset,
1396
+ alignOffset,
1397
+ collisionPadding,
1398
+ avoidCollisions,
1399
+ className,
1400
+ panelClassName,
1401
+ "aria-label": ariaLabel,
1402
+ open,
1403
+ defaultOpen,
1404
+ onOpenChange,
1405
+ modal = false,
1406
+ disabled
1407
+ } = _b, props = __objRest(_b, [
1408
+ "trigger",
1409
+ "children",
1410
+ "align",
1411
+ "side",
1412
+ "sideOffset",
1413
+ "alignOffset",
1414
+ "collisionPadding",
1415
+ "avoidCollisions",
1416
+ "className",
1417
+ "panelClassName",
1418
+ "aria-label",
1419
+ "open",
1420
+ "defaultOpen",
1421
+ "onOpenChange",
1422
+ "modal",
1423
+ "disabled"
1424
+ ]);
1425
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", __spreadProps(__spreadValues({ className: cn2("ph-dropdown", className) }, props), { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1426
+ DropdownPrimitive.Root,
1427
+ {
1428
+ open,
1429
+ defaultOpen,
1430
+ onOpenChange,
1431
+ modal,
1432
+ children: [
1433
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownPrimitive.Trigger, { asChild: true, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: "ph-dropdown-trigger", "aria-label": ariaLabel, children: trigger }) }),
1434
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1435
+ DropdownContent,
1436
+ {
1437
+ align,
1438
+ side,
1439
+ sideOffset,
1440
+ alignOffset,
1441
+ collisionPadding,
1442
+ avoidCollisions,
1443
+ panelClassName,
1444
+ children
1445
+ }
1446
+ )
1447
+ ]
1448
+ }
1449
+ ) }));
1450
+ }
1451
+ function DropdownItem(_a) {
1452
+ var _b = _a, { className, children, disabled } = _b, props = __objRest(_b, ["className", "children", "disabled"]);
1453
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DropdownPrimitive.Item, { asChild: true, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1454
+ "button",
1455
+ __spreadProps(__spreadValues({
1456
+ type: "button",
1457
+ disabled,
1458
+ className: cn2("ph-dropdown-item w-full text-left", className)
1459
+ }, props), {
1460
+ children
1461
+ })
1462
+ ) });
1463
+ }
1464
+
1465
+ // src/suite/components/suite-user-menu.tsx
1466
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1467
+ function computeInitials(name, email) {
1468
+ const source = (name || email || "?").trim();
1469
+ return source.split(/[\s@]+/).filter(Boolean).slice(0, 2).map((part) => part[0]).join("").toUpperCase();
1470
+ }
1471
+ function SuiteUserMenu({
1472
+ name,
1473
+ email,
1474
+ image,
1475
+ settingsHref,
1476
+ onSignOut,
1477
+ fallbackInitials,
1478
+ dataTest = "suite-user-menu",
1479
+ className
1480
+ }) {
1481
+ const router = (0, import_navigation2.useRouter)();
1482
+ const initials = fallbackInitials != null ? fallbackInitials : computeInitials(name, email);
1483
+ const avatar = /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "inline-flex h-11 w-11 items-center justify-center rounded-full transition-colors hover:bg-ph-muted", children: image ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1484
+ import_image2.default,
1485
+ {
1486
+ src: image,
1487
+ alt: "",
1488
+ width: 32,
1489
+ height: 32,
1490
+ className: "h-8 w-8 rounded-full object-cover",
1491
+ unoptimized: true
1492
+ }
1493
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-ph-muted text-sm font-semibold text-ph-brand", children: initials }) });
1494
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1495
+ DropdownMenu,
1496
+ {
1497
+ trigger: avatar,
1498
+ "aria-label": "Account menu",
1499
+ align: "end",
1500
+ side: "bottom",
1501
+ sideOffset: 6,
1502
+ collisionPadding: 16,
1503
+ modal: false,
1504
+ className,
1505
+ "data-test": dataTest,
1506
+ children: [
1507
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "px-2 py-1.5", children: [
1508
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "truncate text-sm font-medium text-ph-ink", children: name || email || "Account" }),
1509
+ email ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "truncate text-xs text-ph-mutedtext", children: email }) : null
1510
+ ] }),
1511
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "my-1 border-t border-ph-border" }),
1512
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1513
+ DropdownItem,
1514
+ {
1515
+ "data-test": `${dataTest}-settings`,
1516
+ onClick: () => router.push(settingsHref),
1517
+ children: [
1518
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_iconoir_react6.Settings, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1519
+ "Profile settings"
1520
+ ]
1521
+ }
1522
+ ),
1523
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1524
+ DropdownItem,
1525
+ {
1526
+ "data-test": `${dataTest}-sign-out`,
1527
+ onClick: onSignOut,
1528
+ children: [
1529
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_iconoir_react6.LogOut, { className: "h-4 w-4 text-ph-mutedtext", "aria-hidden": true }),
1530
+ "Sign out"
1531
+ ]
1532
+ }
1533
+ )
1534
+ ]
1535
+ }
1536
+ );
1537
+ }
1538
+
1539
+ // src/suite/components/suite-bottom-nav.tsx
1540
+ var import_link = __toESM(require("next/link"));
1541
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1542
+ function SuiteBottomNav({
1543
+ items,
1544
+ ariaLabel = "Primary navigation",
1545
+ dataTest = "suite-bottom-nav",
1546
+ className
1547
+ }) {
1548
+ if (process.env.NODE_ENV !== "production" && items.length > 5) {
1549
+ console.warn(
1550
+ `SuiteBottomNav: ${items.length} items \u2014 bottom nav should have at most 5 (Hick's Law).`
1551
+ );
1552
+ }
1553
+ const activeIndex = items.findIndex((item) => item.active);
1554
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1555
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1556
+ "div",
1557
+ {
1558
+ "aria-hidden": true,
1559
+ className: "shrink-0 lg:hidden",
1560
+ style: { height: "calc(60px + env(safe-area-inset-bottom))" }
1561
+ }
1562
+ ),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1564
+ "nav",
1565
+ {
1566
+ "aria-label": ariaLabel,
1567
+ "data-test": dataTest,
1568
+ className: cn(
1569
+ "suite-bottom-nav no-print z-40 grid auto-cols-fr grid-flow-col lg:hidden",
1570
+ className
1571
+ ),
1572
+ style: {
1573
+ gridTemplateColumns: `repeat(${items.length}, minmax(0, 1fr))`,
1574
+ "--suite-nav-items": items.length,
1575
+ "--suite-nav-active": activeIndex >= 0 ? activeIndex : 0
1576
+ },
1577
+ children: [
1578
+ activeIndex >= 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { "aria-hidden": true, className: "suite-bottom-nav__indicator" }) : null,
1579
+ items.map((item) => {
1580
+ var _a;
1581
+ const Icon = item.icon;
1582
+ const active = Boolean(item.active);
1583
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1584
+ import_link.default,
1585
+ {
1586
+ href: item.href,
1587
+ "data-test": (_a = item.dataTest) != null ? _a : `suite-nav-${item.label.toLowerCase().replace(/\s+/g, "-")}`,
1588
+ "aria-current": active ? "page" : void 0,
1589
+ className: cn(
1590
+ "suite-bottom-nav__item relative flex min-h-[60px] min-w-0 flex-col items-center justify-center gap-1 px-1 pb-1.5 pt-2 text-[10px] font-medium leading-none text-ph-mutedtext transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ph-brand motion-reduce:transition-none",
1591
+ active && "font-semibold text-ph-brand"
1592
+ ),
1593
+ children: [
1594
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1595
+ "span",
1596
+ {
1597
+ className: cn(
1598
+ "suite-bottom-nav__icon relative z-10 inline-flex items-center justify-center",
1599
+ active && "suite-bottom-nav__icon--active"
1600
+ ),
1601
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1602
+ Icon,
1603
+ {
1604
+ className: "h-6 w-6",
1605
+ strokeWidth: active ? 2.25 : 1.75,
1606
+ "aria-hidden": true
1607
+ }
1608
+ )
1609
+ }
1610
+ ),
1611
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "relative z-10 max-w-full truncate", children: item.label })
1612
+ ]
1613
+ },
1614
+ item.href
1615
+ );
1616
+ })
1617
+ ]
1618
+ }
1619
+ )
1620
+ ] });
1621
+ }
1622
+
1623
+ // src/suite/lib/apps.ts
1624
+ var SUITE_APPS2 = [
1625
+ {
1626
+ slug: "shellstack",
1627
+ name: "ShellStack",
1628
+ description: "Organisation and billing",
1629
+ icon: "/icon-shellstack.svg",
1630
+ landing: "/home",
1631
+ baseUrlEnv: "NEXT_PUBLIC_SHELLSTACK_URL",
1632
+ baseUrlFallback: "http://localhost:3002"
1633
+ },
1634
+ {
1635
+ slug: "tides",
1636
+ name: "Tides",
1637
+ description: "Projects and tasks",
1638
+ icon: "/tides-icon.svg",
1639
+ landing: "/today",
1640
+ baseUrlEnv: "NEXT_PUBLIC_TIDES_URL",
1641
+ baseUrlFallback: "http://localhost:3001"
1642
+ },
1643
+ {
1644
+ slug: "turtletime",
1645
+ name: "TurtleTime",
1646
+ description: "Time tracking",
1647
+ icon: "/turtletime-icon.svg",
1648
+ landing: "/tracker",
1649
+ baseUrlEnv: "NEXT_PUBLIC_TURTLETIME_URL",
1650
+ baseUrlFallback: "http://localhost:3000"
1651
+ },
1652
+ {
1653
+ slug: "kraken",
1654
+ name: "Kraken",
1655
+ description: "Documents and knowledge",
1656
+ icon: "/kraken-icon.svg",
1657
+ landing: "/today",
1658
+ baseUrlEnv: "NEXT_PUBLIC_KRAKEN_URL",
1659
+ baseUrlFallback: "http://localhost:3003"
1660
+ }
1661
+ ];
1662
+ var SUITE_APP_MAP = Object.fromEntries(SUITE_APPS2.map((app) => [app.slug, app]));
1663
+ function suiteAppBaseUrl(slug) {
1664
+ const def = SUITE_APP_MAP[slug];
1665
+ const fromEnv = typeof process !== "undefined" ? process.env[def.baseUrlEnv] : void 0;
1666
+ return (fromEnv || def.baseUrlFallback).replace(/\/$/, "");
1667
+ }
1668
+
1669
+ // src/suite/components/today-calibrating.tsx
1670
+ var import_iconoir_react7 = require("iconoir-react");
1671
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1672
+ function TodayCalibrating({
1673
+ onRetry,
1674
+ retrying = false,
1675
+ className,
1676
+ spinner: Spinner,
1677
+ variant = "ring",
1678
+ retrySpinnerClassName = "text-ph-brand"
1679
+ }) {
1680
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1681
+ "div",
1682
+ {
1683
+ className: cn(
1684
+ "relative flex min-h-[14rem] flex-col items-center justify-center overflow-hidden rounded-2xl border border-ph-border bg-ph-surface px-5 py-8 text-center shadow-sm",
1685
+ className
1686
+ ),
1687
+ "data-test": "today-calibrating",
1688
+ children: [
1689
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1690
+ "div",
1691
+ {
1692
+ className: "pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.12),transparent_55%)]",
1693
+ "aria-hidden": true
1694
+ }
1695
+ ),
1696
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "relative mb-6 flex h-24 w-24 items-center justify-center", children: [
1697
+ variant === "ring" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
1698
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1699
+ Spinner,
1700
+ {
1701
+ size: "lg",
1702
+ className: "absolute inset-0 m-auto opacity-40",
1703
+ label: "Loading today"
1704
+ }
1705
+ ),
1706
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1707
+ "span",
1708
+ {
1709
+ className: "bg-ph-muted/30 absolute inset-2 animate-ping rounded-full",
1710
+ "aria-hidden": true
1711
+ }
1712
+ )
1713
+ ] }) : null,
1714
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "relative flex h-14 w-14 items-center justify-center rounded-2xl border border-ph-border bg-ph-surface text-ph-ink shadow-sm", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1715
+ import_iconoir_react7.SunLight,
1716
+ {
1717
+ className: cn("h-9 w-9", variant === "pulse" && "animate-pulse"),
1718
+ strokeWidth: 2,
1719
+ "aria-hidden": true
1720
+ }
1721
+ ) })
1722
+ ] }),
1723
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h2", { className: "relative text-base font-semibold tracking-tight text-ph-ink sm:text-lg md:text-xl", children: "Calibrating your day" }),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "relative mt-1.5 max-w-sm text-sm sm:text-[15px] leading-relaxed text-ph-subtle", children: "Pulling together tasks, time and updates from your workspace\u2026" }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "relative mt-5 flex gap-1.5", "aria-hidden": true, children: [0, 1, 2, 3, 4].map((i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1726
+ "span",
1727
+ {
1728
+ className: "h-1.5 w-5 animate-pulse rounded-full bg-ph-muted",
1729
+ style: { animationDelay: `${i * 120}ms` }
1730
+ },
1731
+ i
1732
+ )) }),
1733
+ onRetry ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
1734
+ "button",
1735
+ {
1736
+ type: "button",
1737
+ onClick: onRetry,
1738
+ disabled: retrying,
1739
+ className: "relative mt-6 inline-flex items-center gap-2 rounded-xl border border-ph-border bg-ph-canvas px-4 py-2 text-sm font-medium text-ph-ink transition hover:bg-ph-muted disabled:opacity-60",
1740
+ children: [
1741
+ retrying ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Spinner, { size: "sm", className: retrySpinnerClassName }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_iconoir_react7.Refresh, { className: "h-4 w-4", "aria-hidden": true }),
1742
+ "Try again"
1743
+ ]
1744
+ }
1745
+ ) : null
1746
+ ]
1747
+ }
1748
+ );
1749
+ }
1750
+
1751
+ // src/suite/components/theme-provider.tsx
1752
+ var import_react6 = require("react");
1753
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1754
+ var SuiteThemeContext = (0, import_react6.createContext)(null);
1755
+ function systemTheme(fallback) {
1756
+ if (typeof window === "undefined") return fallback;
1757
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1758
+ }
1759
+ function resolveTheme(theme, fallback) {
1760
+ return theme === "system" ? systemTheme(fallback) : theme;
1761
+ }
1762
+ function applyTheme(theme, config) {
1763
+ const resolved = resolveTheme(theme, config.fallbackTheme);
1764
+ if (typeof document === "undefined") return resolved;
1765
+ document.documentElement.setAttribute(
1766
+ "data-theme",
1767
+ resolved === "dark" ? config.darkThemeId : config.lightThemeId
1768
+ );
1769
+ document.documentElement.classList.toggle("dark", resolved === "dark");
1770
+ if (config.themeColorLight && config.themeColorDark) {
1771
+ const meta = document.getElementById(
1772
+ "theme-color-meta"
1773
+ );
1774
+ if (meta) {
1775
+ meta.content = resolved === "light" ? config.themeColorLight : config.themeColorDark;
1776
+ }
1777
+ }
1778
+ return resolved;
1779
+ }
1780
+ function readStoredTheme(storageKey) {
1781
+ if (typeof window === "undefined") return "system";
1782
+ const stored = localStorage.getItem(storageKey);
1783
+ if (stored === "light" || stored === "dark" || stored === "system")
1784
+ return stored;
1785
+ return "system";
1786
+ }
1787
+ function SuiteThemeProvider({
1788
+ config,
1789
+ children
1790
+ }) {
1791
+ const [state, setState] = (0, import_react6.useState)(() => {
1792
+ const theme2 = readStoredTheme(config.storageKey);
1793
+ if (typeof document === "undefined") {
1794
+ return { theme: theme2, resolvedTheme: config.fallbackTheme };
1795
+ }
1796
+ const domTheme = document.documentElement.getAttribute("data-theme");
1797
+ const resolvedTheme2 = domTheme === config.darkThemeId ? "dark" : domTheme === config.lightThemeId ? "light" : resolveTheme(theme2, config.fallbackTheme);
1798
+ return { theme: theme2, resolvedTheme: resolvedTheme2 };
1799
+ });
1800
+ const { theme, resolvedTheme } = state;
1801
+ (0, import_react6.useEffect)(() => {
1802
+ if (theme !== "system") return;
1803
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
1804
+ const update = () => {
1805
+ const resolved = applyTheme("system", config);
1806
+ setState((current) => __spreadProps(__spreadValues({}, current), { resolvedTheme: resolved }));
1807
+ };
1808
+ media.addEventListener("change", update);
1809
+ return () => media.removeEventListener("change", update);
1810
+ }, [theme, config]);
1811
+ const setTheme = (0, import_react6.useCallback)(
1812
+ (next) => {
1813
+ const resolved = applyTheme(next, config);
1814
+ setState({ theme: next, resolvedTheme: resolved });
1815
+ localStorage.setItem(config.storageKey, next);
1816
+ },
1817
+ [config]
1818
+ );
1819
+ const toggleTheme = (0, import_react6.useCallback)(() => {
1820
+ setState((prev) => {
1821
+ const next = resolveTheme(prev.theme, config.fallbackTheme) === "dark" ? "light" : "dark";
1822
+ const resolved = applyTheme(next, config);
1823
+ localStorage.setItem(config.storageKey, next);
1824
+ return { theme: next, resolvedTheme: resolved };
1825
+ });
1826
+ }, [config]);
1827
+ const value = (0, import_react6.useMemo)(
1828
+ () => ({ theme, resolvedTheme, setTheme, toggleTheme }),
1829
+ [theme, resolvedTheme, setTheme, toggleTheme]
1830
+ );
1831
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(SuiteThemeContext.Provider, { value, children });
1832
+ }
1833
+ function useSuiteTheme() {
1834
+ const ctx = (0, import_react6.useContext)(SuiteThemeContext);
1835
+ if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
1836
+ return ctx;
1837
+ }
1838
+
1839
+ // src/suite/components/ai-panel.tsx
1840
+ var import_react7 = require("react");
1841
+ var import_iconoir_react8 = require("iconoir-react");
1842
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1843
+ function SuiteAiPanel({
1844
+ presets,
1845
+ emptyState,
1846
+ fetchStatus,
1847
+ complete,
1848
+ brandIcon: BrandIcon,
1849
+ spinner: Spinner
1850
+ }) {
1851
+ const [open, setOpen] = (0, import_react7.useState)(false);
1852
+ const [prompt, setPrompt] = (0, import_react7.useState)("");
1853
+ const [loading, setLoading] = (0, import_react7.useState)(false);
1854
+ const [answer, setAnswer] = (0, import_react7.useState)("");
1855
+ const [error, setError] = (0, import_react7.useState)("");
1856
+ const [copied, setCopied] = (0, import_react7.useState)(false);
1857
+ async function ask(text) {
1858
+ const trimmed = text.trim();
1859
+ if (!trimmed || loading) return;
1860
+ setLoading(true);
1861
+ setError("");
1862
+ setAnswer("");
1863
+ setCopied(false);
1864
+ try {
1865
+ const status = await fetchStatus();
1866
+ if (!status.configured) {
1867
+ setError("ShellStack AI is not configured for this workspace yet.");
1868
+ return;
1869
+ }
1870
+ const result = await complete({ prompt: trimmed });
1871
+ setAnswer(result.text);
1872
+ } catch (err) {
1873
+ setError(
1874
+ err instanceof Error ? err.message : "The AI request failed. Try again."
1875
+ );
1876
+ } finally {
1877
+ setLoading(false);
1878
+ }
1879
+ }
1880
+ async function copyAnswer() {
1881
+ if (!answer) return;
1882
+ try {
1883
+ await navigator.clipboard.writeText(answer);
1884
+ setCopied(true);
1885
+ window.setTimeout(() => setCopied(false), 1500);
1886
+ } catch (e) {
1887
+ }
1888
+ }
1889
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
1890
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1891
+ "button",
1892
+ {
1893
+ type: "button",
1894
+ "data-test": "ai-panel-launcher",
1895
+ onClick: () => setOpen(true),
1896
+ className: "fixed bottom-5 right-5 z-30 flex items-center gap-2 rounded-full border border-ph-border bg-ph-surface px-4 py-2.5 text-sm font-medium text-ph-ink shadow-ph hover:bg-ph-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
1897
+ "aria-label": "Open ShellStack AI",
1898
+ children: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BrandIcon, { className: "h-4 w-4 text-ph-brand" }),
1900
+ "Ask AI"
1901
+ ]
1902
+ }
1903
+ ),
1904
+ open && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "fixed inset-0 z-40", role: "dialog", "aria-modal": "true", children: [
1905
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1906
+ "button",
1907
+ {
1908
+ type: "button",
1909
+ className: "absolute inset-0 bg-black/25",
1910
+ onClick: () => setOpen(false),
1911
+ "aria-label": "Close AI panel"
1912
+ }
1913
+ ),
1914
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("aside", { className: "absolute inset-y-0 right-0 flex w-full max-w-md flex-col border-l border-ph-border bg-ph-surface shadow-2xl", children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between border-b border-ph-border px-4 py-3", children: [
1916
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
1917
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(BrandIcon, { className: "h-4 w-4 text-ph-brand" }),
1918
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sm font-semibold text-ph-ink", children: "ShellStack AI" })
1919
+ ] }),
1920
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1921
+ "button",
1922
+ {
1923
+ type: "button",
1924
+ onClick: () => setOpen(false),
1925
+ className: "rounded p-1 hover:bg-ph-muted",
1926
+ "aria-label": "Close",
1927
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_iconoir_react8.Xmark, { className: "h-4 w-4" })
1928
+ }
1929
+ )
1930
+ ] }),
1931
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex flex-wrap gap-1.5 border-b border-ph-border px-4 py-2", children: presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1932
+ "button",
1933
+ {
1934
+ type: "button",
1935
+ onClick: () => {
1936
+ setPrompt(preset.prompt);
1937
+ void ask(preset.prompt);
1938
+ },
1939
+ disabled: loading,
1940
+ className: "rounded-md border border-ph-border px-2 py-1 text-xs text-ph-ink hover:bg-ph-muted disabled:opacity-50",
1941
+ children: preset.label
1942
+ },
1943
+ preset.label
1944
+ )) }),
1945
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex-1 space-y-3 overflow-y-auto p-4", children: [
1946
+ error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "bg-ph-danger/10 rounded-lg px-3 py-2 text-sm text-ph-danger", children: error }),
1947
+ loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2 text-sm text-ph-mutedtext", children: [
1948
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, { className: "h-4 w-4 animate-spin" }),
1949
+ "Thinking\u2026"
1950
+ ] }),
1951
+ answer && !loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "rounded-lg bg-ph-muted px-3 py-2 text-sm text-ph-ink", children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "whitespace-pre-wrap", children: answer }),
1953
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
1954
+ "button",
1955
+ {
1956
+ type: "button",
1957
+ onClick: () => void copyAnswer(),
1958
+ className: "mt-2 inline-flex items-center gap-1 text-xs text-ph-mutedtext hover:text-ph-ink",
1959
+ children: [
1960
+ copied ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_iconoir_react8.Check, { className: "h-3 w-3" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_iconoir_react8.Copy, { className: "h-3 w-3" }),
1961
+ copied ? "Copied" : "Copy"
1962
+ ]
1963
+ }
1964
+ )
1965
+ ] }),
1966
+ !answer && !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "mt-8 text-center text-xs text-ph-mutedtext", children: emptyState })
1967
+ ] }),
1968
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "border-t border-ph-border p-3", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex gap-2", children: [
1969
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1970
+ "textarea",
1971
+ {
1972
+ value: prompt,
1973
+ onChange: (e) => setPrompt(e.target.value),
1974
+ onKeyDown: (e) => {
1975
+ if (e.key === "Enter" && !e.shiftKey) {
1976
+ e.preventDefault();
1977
+ void ask(prompt);
1978
+ }
1979
+ },
1980
+ placeholder: "Ask anything\u2026",
1981
+ rows: 2,
1982
+ disabled: loading,
1983
+ className: "flex-1 resize-none rounded-lg border border-ph-border bg-ph-canvas p-2 text-sm text-ph-ink outline-none focus:ring-1 focus:ring-ph-brand"
1984
+ }
1985
+ ),
1986
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1987
+ "button",
1988
+ {
1989
+ type: "button",
1990
+ onClick: () => void ask(prompt),
1991
+ disabled: loading || !prompt.trim(),
1992
+ className: "self-end rounded-lg bg-ph-brand px-3 py-2 text-sm font-medium text-white disabled:opacity-50",
1993
+ children: "Ask"
1994
+ }
1995
+ )
1996
+ ] }) })
1997
+ ] })
1998
+ ] })
1999
+ ] });
2000
+ }
2001
+
2002
+ // src/suite/lib/motion.tsx
2003
+ var import_react8 = require("motion/react");
2004
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2005
+ function SuiteMotionProvider({ children }) {
2006
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react8.LazyMotion, { features: import_react8.domAnimation, strict: true, children });
2007
+ }
2008
+ var SUITE_SPRINGS = {
2009
+ /** Snappy panel/sheet entrances — small overshoot. */
2010
+ fast: { type: "spring", stiffness: 400, damping: 32, mass: 0.8 },
2011
+ /** Gentle scrims, fades, list staggers. */
2012
+ subtle: { type: "spring", stiffness: 260, damping: 26, mass: 1 },
2013
+ /** Press/tap feedback — quick settle, no wobble. */
2014
+ press: { type: "spring", stiffness: 600, damping: 35, mass: 0.5 }
2015
+ };
2016
+
2017
+ // src/suite/components/suite-layout.tsx
2018
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2019
+ var WIDTHS = {
2020
+ full: "max-w-none",
2021
+ wide: "max-w-7xl",
2022
+ content: "max-w-6xl",
2023
+ narrow: "max-w-4xl"
2024
+ };
2025
+ function SuiteBreadcrumbs({ items, className }) {
2026
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("nav", { "aria-label": "Breadcrumb", className, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ol", { className: "flex min-w-0 items-center gap-1.5", children: items.map((item, index) => {
2027
+ const isLast = index === items.length - 1;
2028
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: "flex min-w-0 items-center gap-1.5", children: [
2029
+ index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "select-none text-ph-border", "aria-hidden": "true", children: "/" }) : null,
2030
+ item.href && !isLast ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2031
+ "a",
2032
+ {
2033
+ href: item.href,
2034
+ className: "truncate transition hover:text-ph-ink",
2035
+ children: item.label
2036
+ }
2037
+ ) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2038
+ "span",
2039
+ {
2040
+ className: cn(
2041
+ "truncate",
2042
+ isLast ? "font-medium text-ph-ink" : "text-ph-subtle"
2043
+ ),
2044
+ "aria-current": isLast ? "page" : void 0,
2045
+ children: item.label
2046
+ }
2047
+ )
2048
+ ] }, index);
2049
+ }) }) });
2050
+ }
2051
+ function SuitePage({
2052
+ children,
2053
+ width = "wide",
2054
+ inset = true,
2055
+ className,
2056
+ as: Component = "div",
2057
+ dataTest,
2058
+ id,
2059
+ role,
2060
+ "aria-live": ariaLive
2061
+ }) {
2062
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2063
+ Component,
2064
+ {
2065
+ id,
2066
+ "data-test": dataTest,
2067
+ role,
2068
+ "aria-live": ariaLive,
2069
+ className: cn(
2070
+ "mx-auto flex w-full min-w-0 flex-col",
2071
+ WIDTHS[width],
2072
+ // Tight inset keeps page titles aligned without excessive whitespace.
2073
+ inset && "px-4 pb-6 pt-4 sm:px-6 sm:pb-7 sm:pt-5 lg:px-8",
2074
+ className
2075
+ ),
2076
+ children
2077
+ }
2078
+ );
2079
+ }
2080
+ function SuitePageHeader({
2081
+ title,
2082
+ description,
2083
+ eyebrow,
2084
+ actions,
2085
+ className,
2086
+ titleClassName,
2087
+ dataTest,
2088
+ /** Optional icon rendered before the title. */
2089
+ icon,
2090
+ /** Use the animated sun/moon icon from the Today page. */
2091
+ todayIcon,
2092
+ todayIconClassName,
2093
+ /** Small badge/pill next to the title (e.g. "Beta", org name). */
2094
+ badge,
2095
+ /** Breadcrumb line rendered above the title. */
2096
+ breadcrumb
2097
+ }) {
2098
+ const resolvedIcon = todayIcon ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2099
+ TodayTimeIcon,
2100
+ {
2101
+ className: cn(
2102
+ "h-6 w-6 sm:h-7 sm:w-7 text-amber-500",
2103
+ todayIconClassName
2104
+ )
2105
+ }
2106
+ ) : icon;
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2108
+ "header",
2109
+ {
2110
+ "data-test": dataTest,
2111
+ className: cn(
2112
+ "flex items-start justify-between gap-4 pb-4 sm:pb-5",
2113
+ className
2114
+ ),
2115
+ children: [
2116
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex min-w-0 items-start gap-3", children: [
2117
+ resolvedIcon ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2118
+ "div",
2119
+ {
2120
+ className: "hidden shrink-0 pt-1 sm:block",
2121
+ "aria-hidden": "true",
2122
+ children: resolvedIcon
2123
+ }
2124
+ ) : null,
2125
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [
2126
+ eyebrow ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-xs text-ph-subtle", children: eyebrow }) : null,
2127
+ breadcrumb ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "mb-0.5 flex min-w-0 items-center gap-1.5 text-xs text-ph-subtle", children: breadcrumb }) : null,
2128
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [
2129
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2130
+ "h1",
2131
+ {
2132
+ className: cn(
2133
+ "font-display text-[1.625rem] font-bold leading-tight tracking-tight text-ph-ink sm:text-[2.25rem]",
2134
+ titleClassName
2135
+ ),
2136
+ children: title
2137
+ }
2138
+ ),
2139
+ badge ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "rounded-md bg-ph-muted px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-ph-subtle", children: badge }) : null
2140
+ ] }),
2141
+ description ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "mt-1 max-w-2xl text-pretty text-sm leading-5 text-ph-subtle", children: description }) : null
2142
+ ] })
2143
+ ] }),
2144
+ actions ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex shrink-0 items-center gap-2 pt-1", children: actions }) : null
2145
+ ]
2146
+ }
2147
+ );
2148
+ }
2149
+ function SuiteToolbar({
2150
+ children,
2151
+ className,
2152
+ label = "Page tools",
2153
+ dataTest
2154
+ }) {
2155
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2156
+ "div",
2157
+ {
2158
+ role: "toolbar",
2159
+ "aria-label": label,
2160
+ "data-test": dataTest,
2161
+ className: cn("flex min-w-0 flex-wrap items-center gap-2", className),
2162
+ children
2163
+ }
2164
+ );
2165
+ }
2166
+ function SuiteTabList({
2167
+ children,
2168
+ className,
2169
+ label,
2170
+ dataTest
2171
+ }) {
2172
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2173
+ "nav",
2174
+ {
2175
+ "aria-label": label,
2176
+ "data-test": dataTest,
2177
+ className: cn(
2178
+ "flex min-w-0 flex-wrap gap-2 [&_a]:min-h-[48px] [&_button]:min-h-[48px] sm:[&_a]:min-h-[44px] sm:[&_button]:min-h-[44px]",
2179
+ className
2180
+ ),
2181
+ children
2182
+ }
2183
+ );
2184
+ }
2185
+ function SuiteSectionHeader({
2186
+ title,
2187
+ description,
2188
+ action,
2189
+ className
2190
+ }) {
2191
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2192
+ "div",
2193
+ {
2194
+ className: cn(
2195
+ "flex min-w-0 flex-col gap-4 sm:flex-row sm:items-end sm:justify-between",
2196
+ className
2197
+ ),
2198
+ children: [
2199
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "min-w-0", children: [
2200
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { className: "font-display text-balance text-lg font-semibold tracking-tight text-ph-ink sm:text-xl", children: title }),
2201
+ description ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "mt-1 max-w-2xl text-pretty text-sm leading-5 text-ph-subtle", children: description }) : null
2202
+ ] }),
2203
+ action ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex min-h-[44px] shrink-0 items-center", children: action }) : null
2204
+ ]
2205
+ }
2206
+ );
2207
+ }
2208
+
2209
+ // src/suite/components/suite-skeleton.tsx
2210
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2211
+ function SuiteSkeleton({
2212
+ className,
2213
+ lines = 1,
2214
+ circle,
2215
+ width
2216
+ }) {
2217
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2218
+ "div",
2219
+ {
2220
+ className: cn("space-y-2", circle ? "flex flex-col items-center" : ""),
2221
+ "aria-hidden": true,
2222
+ children: Array.from({ length: lines }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2223
+ "div",
2224
+ {
2225
+ className: "animate-[skeleton-stagger_0.6s_ease-out_both]",
2226
+ style: { animationDelay: `${index * 80}ms` },
2227
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2228
+ "div",
2229
+ {
2230
+ className: cn(
2231
+ "ph-skeleton",
2232
+ circle ? "h-10 w-10 rounded-full" : "h-3",
2233
+ className
2234
+ ),
2235
+ style: !circle ? {
2236
+ width: width != null ? width : `${70 + index % 3 * 15}%`
2237
+ } : void 0
2238
+ }
2239
+ )
2240
+ },
2241
+ index
2242
+ ))
2243
+ }
2244
+ );
2245
+ }
2246
+ function SuiteSkeletonCard({
2247
+ className,
2248
+ rows = 3,
2249
+ header = true
2250
+ }) {
2251
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2252
+ "div",
2253
+ {
2254
+ className: cn(
2255
+ "ph-skeleton-card animate-[skeleton-stagger_0.5s_ease-out_both] rounded-2xl border border-ph-border/70 bg-ph-surface p-6 shadow-sm",
2256
+ className
2257
+ ),
2258
+ "aria-hidden": true,
2259
+ children: [
2260
+ header ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2261
+ "div",
2262
+ {
2263
+ className: "mb-5 flex items-center gap-3 animate-[skeleton-stagger_0.5s_ease-out_both]",
2264
+ style: { animationDelay: "100ms" },
2265
+ children: [
2266
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ph-skeleton h-10 w-10 rounded-xl" }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 space-y-2", children: [
2268
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ph-skeleton h-4 w-1/3" }),
2269
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ph-skeleton h-3 w-1/2" })
2270
+ ] })
2271
+ ]
2272
+ }
2273
+ ) : null,
2274
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "space-y-3", children: Array.from({ length: rows }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2275
+ "div",
2276
+ {
2277
+ className: "animate-[skeleton-stagger_0.5s_ease-out_both]",
2278
+ style: { animationDelay: `${200 + index * 80}ms` },
2279
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2280
+ "div",
2281
+ {
2282
+ className: "ph-skeleton h-3",
2283
+ style: { width: `${70 + index % 3 * 15}%` }
2284
+ }
2285
+ )
2286
+ },
2287
+ index
2288
+ )) })
2289
+ ]
2290
+ }
2291
+ );
2292
+ }
2293
+ function SuiteSkeletonList({
2294
+ className,
2295
+ items = 4
2296
+ }) {
2297
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("space-y-3", className), "aria-hidden": true, children: Array.from({ length: items }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2298
+ "div",
2299
+ {
2300
+ className: "animate-[skeleton-stagger_0.6s_ease-out_both]",
2301
+ style: { animationDelay: `${index * 100}ms` },
2302
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-3 rounded-xl border border-ph-border/60 bg-ph-surface p-4", children: [
2303
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ph-skeleton h-10 w-10 rounded-lg" }),
2304
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "min-w-0 flex-1 space-y-2", children: [
2305
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ph-skeleton h-3.5 w-3/4" }),
2306
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "ph-skeleton h-3 w-1/2" })
2307
+ ] })
2308
+ ] })
2309
+ },
2310
+ index
2311
+ )) });
2312
+ }
2313
+ function SuiteEmptyState({
2314
+ icon,
2315
+ title,
2316
+ description,
2317
+ action,
2318
+ className,
2319
+ size = "md"
2320
+ }) {
2321
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2322
+ "div",
2323
+ {
2324
+ className: cn(
2325
+ "relative overflow-hidden rounded-2xl border border-ph-border/80 bg-ph-surface text-center",
2326
+ size === "sm" ? "px-5 py-8" : "px-6 py-12 sm:px-10 sm:py-16",
2327
+ className
2328
+ ),
2329
+ children: [
2330
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "pointer-events-none absolute inset-0 overflow-hidden opacity-60", "aria-hidden": true, children: [
2331
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "absolute -left-10 -top-10 h-40 w-40 rounded-full bg-ph-brand/10 blur-3xl" }),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "absolute -bottom-10 -right-10 h-40 w-40 rounded-full bg-ph-accent/10 blur-3xl" })
2333
+ ] }),
2334
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "relative", children: [
2335
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "mx-auto mb-4 inline-flex items-center justify-center rounded-2xl bg-ph-muted p-3 shadow-sm ring-1 ring-black/[0.06]", children: icon }) : null,
2336
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2337
+ "h3",
2338
+ {
2339
+ className: cn(
2340
+ "font-semibold text-ph-ink",
2341
+ size === "sm" ? "text-base" : "text-lg"
2342
+ ),
2343
+ children: title
2344
+ }
2345
+ ),
2346
+ description ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "mx-auto mt-2 max-w-sm text-sm leading-relaxed text-ph-subtle", children: description }) : null,
2347
+ action ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "mt-5 flex flex-wrap items-center justify-center gap-2", children: action }) : null
2348
+ ] })
2349
+ ]
2350
+ }
2351
+ );
2352
+ }
2353
+
2354
+ // src/suite/components/suite-settings-mobile-nav.tsx
2355
+ var import_react9 = require("react");
2356
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2357
+ function SuiteSettingsMobileNav({
2358
+ items,
2359
+ activeHref,
2360
+ onSelect,
2361
+ dataTest
2362
+ }) {
2363
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2364
+ "nav",
2365
+ {
2366
+ "data-test": dataTest,
2367
+ "aria-label": "Settings sections",
2368
+ className: "-mx-4 px-4 lg:hidden",
2369
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex snap-x gap-2 overflow-x-auto pb-3 pt-1 scrollbar-hide [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden", children: items.map((item) => {
2370
+ const Icon = item.icon;
2371
+ const active = activeHref === item.href || activeHref.startsWith(`${item.href}/`);
2372
+ const iconNode = (0, import_react9.isValidElement)(item.icon) ? item.icon : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2373
+ Icon,
2374
+ {
2375
+ className: cn(
2376
+ "h-4 w-4 shrink-0 transition",
2377
+ active ? "text-ph-surface" : "text-ph-mutedtext"
2378
+ ),
2379
+ "aria-hidden": true
2380
+ }
2381
+ );
2382
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2383
+ "button",
2384
+ {
2385
+ type: "button",
2386
+ "data-test": item.testId,
2387
+ onClick: () => onSelect(item.href),
2388
+ "aria-current": active ? "page" : void 0,
2389
+ className: cn(
2390
+ "group relative flex shrink-0 snap-start items-center gap-2 rounded-full px-3.5 py-2.5 text-sm font-medium transition",
2391
+ "min-h-[44px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
2392
+ active ? "bg-ph-ink text-ph-surface shadow-sm" : "bg-ph-surface text-ph-subtle ring-1 ring-ph-border hover:bg-ph-muted hover:text-ph-ink"
2393
+ ),
2394
+ children: [
2395
+ iconNode,
2396
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "whitespace-nowrap", children: item.label }),
2397
+ active ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "absolute inset-x-0 -bottom-3 h-0.5 rounded-full bg-ph-ink lg:hidden" }) : null
2398
+ ]
2399
+ },
2400
+ item.href
2401
+ );
2402
+ }) })
2403
+ }
2404
+ );
2405
+ }
2406
+
2407
+ // src/suite/components/suite-sidebar.tsx
2408
+ var import_link2 = __toESM(require("next/link"));
2409
+
2410
+ // src/components/ui/Tooltip.tsx
2411
+ var import_react10 = require("react");
2412
+ var import_react_dom = require("react-dom");
2413
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2414
+ var TooltipDelayContext = (0, import_react10.createContext)(0);
2415
+ function tooltipPosition(trigger, side, align, offset) {
2416
+ const horizontal = align === "start" ? trigger.left : align === "end" ? trigger.right : trigger.left + trigger.width / 2;
2417
+ const vertical = align === "start" ? trigger.top : align === "end" ? trigger.bottom : trigger.top + trigger.height / 2;
2418
+ if (side === "top")
2419
+ return {
2420
+ left: horizontal,
2421
+ top: trigger.top - offset,
2422
+ transform: "translate(-50%, -100%)"
2423
+ };
2424
+ if (side === "bottom")
2425
+ return {
2426
+ left: horizontal,
2427
+ top: trigger.bottom + offset,
2428
+ transform: "translate(-50%, 0)"
2429
+ };
2430
+ if (side === "left")
2431
+ return {
2432
+ left: trigger.left - offset,
2433
+ top: vertical,
2434
+ transform: "translate(-100%, -50%)"
2435
+ };
2436
+ return {
2437
+ left: trigger.right + offset,
2438
+ top: vertical,
2439
+ transform: "translate(0, -50%)"
2440
+ };
2441
+ }
2442
+ function Tooltip({
2443
+ content,
2444
+ children,
2445
+ side = "top",
2446
+ align = "center",
2447
+ sideOffset = 7,
2448
+ className,
2449
+ open,
2450
+ defaultOpen = false,
2451
+ onOpenChange,
2452
+ delayDuration
2453
+ }) {
2454
+ const providerDelay = (0, import_react10.useContext)(TooltipDelayContext);
2455
+ const generatedId = (0, import_react10.useId)().replace(/:/g, "");
2456
+ const triggerRef = (0, import_react10.useRef)(null);
2457
+ const timerRef = (0, import_react10.useRef)(null);
2458
+ const [internalOpen, setInternalOpen] = (0, import_react10.useState)(defaultOpen);
2459
+ const [position, setPosition] = (0, import_react10.useState)(null);
2460
+ const isOpen = open != null ? open : internalOpen;
2461
+ const triggerId = children.props.id;
2462
+ const tooltipId = triggerId ? `${triggerId}-tooltip` : `ph-tooltip-${generatedId}`;
2463
+ function setOpen(next) {
2464
+ if (open === void 0) setInternalOpen(next);
2465
+ onOpenChange == null ? void 0 : onOpenChange(next);
2466
+ }
2467
+ function clearTimer() {
2468
+ if (timerRef.current) clearTimeout(timerRef.current);
2469
+ timerRef.current = null;
2470
+ }
2471
+ function updatePosition() {
2472
+ if (!triggerRef.current) return;
2473
+ setPosition(
2474
+ tooltipPosition(
2475
+ triggerRef.current.getBoundingClientRect(),
2476
+ side,
2477
+ align,
2478
+ sideOffset
2479
+ )
2480
+ );
2481
+ }
2482
+ function show(immediate) {
2483
+ clearTimer();
2484
+ updatePosition();
2485
+ const delay = immediate ? 0 : delayDuration != null ? delayDuration : providerDelay;
2486
+ if (!delay) setOpen(true);
2487
+ else timerRef.current = setTimeout(() => setOpen(true), delay);
2488
+ }
2489
+ function hide() {
2490
+ clearTimer();
2491
+ setOpen(false);
2492
+ }
2493
+ (0, import_react10.useEffect)(() => {
2494
+ if (!isOpen) return;
2495
+ const reposition = () => updatePosition();
2496
+ window.addEventListener("resize", reposition);
2497
+ window.addEventListener("scroll", reposition, true);
2498
+ return () => {
2499
+ window.removeEventListener("resize", reposition);
2500
+ window.removeEventListener("scroll", reposition, true);
2501
+ };
2502
+ });
2503
+ (0, import_react10.useEffect)(() => () => clearTimer(), []);
2504
+ const describedBy = [children.props["aria-describedby"], tooltipId].filter(Boolean).join(" ");
2505
+ const trigger = (0, import_react10.cloneElement)(children, { "aria-describedby": describedBy });
2506
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
2507
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2508
+ "span",
2509
+ {
2510
+ ref: triggerRef,
2511
+ className: "inline-flex",
2512
+ onMouseEnter: () => show(false),
2513
+ onMouseLeave: hide,
2514
+ onFocusCapture: () => show(true),
2515
+ onBlurCapture: hide,
2516
+ onKeyDown: (event) => {
2517
+ if (event.key === "Escape") hide();
2518
+ },
2519
+ children: trigger
2520
+ }
2521
+ ),
2522
+ isOpen && position && typeof document !== "undefined" ? (0, import_react_dom.createPortal)(
2523
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2524
+ "span",
2525
+ {
2526
+ style: __spreadValues({ position: "fixed" }, position),
2527
+ className: "pointer-events-none z-[200]",
2528
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2529
+ "span",
2530
+ {
2531
+ id: tooltipId,
2532
+ role: "tooltip",
2533
+ "data-side": side,
2534
+ className: cn2("ph-tooltip-content", className),
2535
+ children: content
2536
+ }
2537
+ )
2538
+ }
2539
+ ),
2540
+ document.body
2541
+ ) : null
2542
+ ] });
2543
+ }
2544
+
2545
+ // src/suite/components/suite-sidebar.tsx
2546
+ var import_jsx_runtime23 = require("react/jsx-runtime");
2547
+ function renderNode(node, collapsed) {
2548
+ if (typeof node === "function") return node(collapsed);
2549
+ return node;
2550
+ }
2551
+ function SuiteSidebar({
2552
+ appSwitcher,
2553
+ contextSwitcher,
2554
+ navItems,
2555
+ secondaryNav,
2556
+ userMenu,
2557
+ notificationBell,
2558
+ footerExtras,
2559
+ collapsed = false,
2560
+ className,
2561
+ surface = false
2562
+ }) {
2563
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2564
+ "div",
2565
+ {
2566
+ className: cn(
2567
+ "flex h-full w-full min-w-0 flex-col text-ph-ink",
2568
+ surface ? "bg-ph-surface" : "bg-ph-canvas",
2569
+ className
2570
+ ),
2571
+ children: [
2572
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2573
+ "div",
2574
+ {
2575
+ className: cn(
2576
+ "flex shrink-0 border-b border-ph-border",
2577
+ surface ? "bg-ph-surface" : "bg-ph-canvas",
2578
+ collapsed ? "flex-col items-center gap-1 px-1 py-2" : "h-14 items-center justify-between gap-2 px-3"
2579
+ ),
2580
+ children: [
2581
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn("min-w-0", collapsed ? "shrink-0" : "flex-1"), children: renderNode(appSwitcher, collapsed) }),
2582
+ collapsed ? null : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "shrink-0", children: renderNode(notificationBell, collapsed) })
2583
+ ]
2584
+ }
2585
+ ),
2586
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2587
+ "div",
2588
+ {
2589
+ className: cn(
2590
+ "flex min-h-0 flex-1 flex-col overflow-y-auto",
2591
+ surface ? "bg-ph-surface" : "bg-ph-canvas",
2592
+ collapsed ? "px-1.5 py-2" : "p-2"
2593
+ ),
2594
+ children: [
2595
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2596
+ "nav",
2597
+ {
2598
+ "aria-label": "Pages",
2599
+ className: "shrink-0 space-y-0.5",
2600
+ children: [
2601
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: cn("mb-3", collapsed && "flex justify-center"), children: renderNode(contextSwitcher, collapsed) }),
2602
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "space-y-0.5", children: navItems.map((item) => {
2603
+ const Icon = item.icon;
2604
+ const active = Boolean(item.active);
2605
+ const link = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2606
+ import_link2.default,
2607
+ {
2608
+ href: item.href,
2609
+ "data-test": "nav-link",
2610
+ "aria-current": active ? "page" : void 0,
2611
+ onClick: item.onClick,
2612
+ className: cn(
2613
+ "group relative flex items-center gap-2.5 rounded-[var(--ph-radius-app)] text-sm font-medium transition-colors",
2614
+ active ? "bg-ph-muted" : "text-ph-subtle hover:bg-ph-muted hover:text-ph-ink",
2615
+ collapsed ? "mx-auto size-10 shrink-0 justify-center gap-0 px-0 py-0" : "w-full px-2.5 py-2"
2616
+ ),
2617
+ children: [
2618
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2619
+ "span",
2620
+ {
2621
+ className: cn(
2622
+ "relative flex h-4 w-4 shrink-0 items-center justify-center",
2623
+ item.iconClassName
2624
+ ),
2625
+ "aria-hidden": true,
2626
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2627
+ Icon,
2628
+ {
2629
+ className: "h-full w-full",
2630
+ strokeWidth: active ? 2 : 1.75
2631
+ }
2632
+ )
2633
+ }
2634
+ ),
2635
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2636
+ "span",
2637
+ {
2638
+ className: cn(
2639
+ "relative truncate",
2640
+ active ? "text-ph-ink" : "text-ph-subtle group-hover:text-ph-ink",
2641
+ collapsed && "sr-only"
2642
+ ),
2643
+ children: item.label
2644
+ }
2645
+ ),
2646
+ !collapsed && item.badge ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "relative ml-auto shrink-0", children: item.badge }) : null
2647
+ ]
2648
+ },
2649
+ item.label
2650
+ );
2651
+ return collapsed ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Tooltip, { content: item.label, side: "right", children: link }, item.label) : link;
2652
+ }) })
2653
+ ]
2654
+ }
2655
+ ),
2656
+ secondaryNav ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2657
+ "div",
2658
+ {
2659
+ className: "min-h-0 flex-1",
2660
+ "data-test": "suite-sidebar-secondary-nav",
2661
+ children: secondaryNav
2662
+ }
2663
+ ) : null
2664
+ ]
2665
+ }
2666
+ ),
2667
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
2668
+ "div",
2669
+ {
2670
+ className: cn(
2671
+ "shrink-0 border-t border-ph-border",
2672
+ surface ? "bg-ph-surface" : "bg-ph-canvas",
2673
+ collapsed ? "p-1.5" : "p-3"
2674
+ ),
2675
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
2676
+ "div",
2677
+ {
2678
+ className: cn(
2679
+ "flex items-center gap-2",
2680
+ collapsed && "justify-center"
2681
+ ),
2682
+ children: [
2683
+ renderNode(userMenu, collapsed),
2684
+ !collapsed && footerExtras ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "ml-auto shrink-0", children: renderNode(footerExtras, collapsed) }) : null
2685
+ ]
2686
+ }
2687
+ )
2688
+ }
2689
+ )
2690
+ ]
2691
+ }
2692
+ );
2693
+ }
2694
+
2695
+ // src/suite/components/suite-app-layout.tsx
2696
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2697
+ function SuiteAppLayout({
2698
+ sidebar,
2699
+ children,
2700
+ mobileHeader,
2701
+ bottomNav,
2702
+ sidebarWidth = 240,
2703
+ collapsed = false,
2704
+ onStartResize,
2705
+ onResetWidth,
2706
+ className
2707
+ }) {
2708
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { "data-test": "app-shell", className: cn("min-h-screen", className), children: [
2709
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "hidden lg:flex h-screen overflow-hidden", children: [
2710
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2711
+ "aside",
2712
+ {
2713
+ className: "relative flex shrink-0 flex-col border-r border-ph-border transition-[width] duration-200 ease-out",
2714
+ style: { width: `${sidebarWidth}px` },
2715
+ children: [
2716
+ sidebar,
2717
+ onStartResize ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2718
+ "div",
2719
+ {
2720
+ role: "separator",
2721
+ "aria-orientation": "vertical",
2722
+ "aria-label": "Resize sidebar",
2723
+ onPointerDown: onStartResize,
2724
+ onDoubleClick: onResetWidth,
2725
+ className: "absolute inset-y-0 right-0 z-10 w-1 cursor-col-resize transition-colors hover:bg-[color-mix(in_oklab,var(--ph-accent)_30%,transparent)]"
2726
+ }
2727
+ ) : null
2728
+ ]
2729
+ }
2730
+ ),
2731
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("main", { className: "min-w-0 flex-1 overflow-y-auto bg-ph-canvas", children })
2732
+ ] }),
2733
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex min-h-screen flex-col lg:hidden", children: [
2734
+ mobileHeader ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "shrink-0", children: mobileHeader }) : null,
2735
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("main", { className: "min-w-0 flex-1 overflow-y-auto bg-ph-canvas", children }),
2736
+ bottomNav ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "shrink-0", children: bottomNav }) : null
2737
+ ] })
2738
+ ] });
2739
+ }
2740
+ // Annotate the CommonJS export names for ESM import in node:
2741
+ 0 && (module.exports = {
2742
+ APP_ACCENTS,
2743
+ APP_GLYPHS,
2744
+ AnimatedMoon,
2745
+ AnimatedSun,
2746
+ AppSwitcher,
2747
+ AppSwitcherChevron,
2748
+ AppSwitcherMark,
2749
+ CommandPaletteHost,
2750
+ DeferredChrome,
2751
+ SUITE_APPS,
2752
+ SUITE_APP_MAP,
2753
+ SUITE_GLYPHS,
2754
+ SUITE_ICON_NAMES,
2755
+ SUITE_SPRINGS,
2756
+ SourceAppGlyph,
2757
+ SuiteAiPanel,
2758
+ SuiteAppIcon,
2759
+ SuiteAppLayout,
2760
+ SuiteAppStrip,
2761
+ SuiteBottomNav,
2762
+ SuiteBreadcrumbs,
2763
+ SuiteEmptyState,
2764
+ SuiteIcon,
2765
+ SuiteMobileDrawer,
2766
+ SuiteMobileHeader,
2767
+ SuiteMotionProvider,
2768
+ SuiteNotificationBell,
2769
+ SuitePage,
2770
+ SuitePageHeader,
2771
+ SuiteSectionHeader,
2772
+ SuiteSettingsMobileNav,
2773
+ SuiteSidebar,
2774
+ SuiteSkeleton,
2775
+ SuiteSkeletonCard,
2776
+ SuiteSkeletonList,
2777
+ SuiteTabList,
2778
+ SuiteThemeProvider,
2779
+ SuiteToolbar,
2780
+ SuiteUserMenu,
2781
+ TodayCalibrating,
2782
+ TodayEmptyState,
2783
+ TodayHero,
2784
+ TodayPageFrame,
2785
+ TodayPanel,
2786
+ TodaySection,
2787
+ TodayTimeIcon,
2788
+ TodayTopBar,
2789
+ appSwitcherMarkClass,
2790
+ appSwitcherMenuItemClass,
2791
+ appSwitcherTriggerClass,
2792
+ cn,
2793
+ getTodayGreeting,
2794
+ m,
2795
+ sourceToSuiteApp,
2796
+ suiteAppBaseUrl,
2797
+ suiteAppLabel,
2798
+ useIdleMount,
2799
+ useSuiteTheme
2800
+ });