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