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

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