@xenide-io/the-old-ui-theme 0.4.4 → 0.4.8

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 (76) hide show
  1. package/dist/{chunk-54ZR4VL5.mjs → chunk-RZXHZWUB.mjs} +44 -17
  2. package/dist/{index-B5NOyoKS.d.mts → index-Od4pIP4F.d.mts} +7 -1
  3. package/dist/{index-B5NOyoKS.d.ts → index-Od4pIP4F.d.ts} +7 -1
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +37 -10
  7. package/dist/index.mjs +1 -1
  8. package/dist/suite.d.mts +152 -41
  9. package/dist/suite.d.ts +152 -41
  10. package/dist/suite.js +1366 -671
  11. package/dist/suite.mjs +1304 -610
  12. package/dist/ui.d.mts +1 -1
  13. package/dist/ui.d.ts +1 -1
  14. package/dist/ui.js +37 -10
  15. package/dist/ui.mjs +1 -1
  16. package/package.json +7 -3
  17. package/src/components/ui/Modal.tsx +70 -26
  18. package/src/styles/suite-skin.css +284 -24
  19. package/src/suite/components/ai-panel.test.ts +20 -0
  20. package/src/suite/components/ai-panel.tsx +494 -103
  21. package/src/suite/components/suite-app-layout.tsx +48 -28
  22. package/src/suite/components/suite-layout.tsx +84 -40
  23. package/src/suite/components/suite-mobile-drawer.tsx +35 -22
  24. package/src/suite/components/suite-notification-bell.tsx +9 -3
  25. package/src/suite/components/suite-settings-layout.tsx +82 -0
  26. package/src/suite/components/suite-settings-mobile-nav.tsx +18 -17
  27. package/src/suite/components/suite-skeleton.tsx +3 -3
  28. package/src/suite/components/today-calibrating.tsx +11 -35
  29. package/src/suite/components/today-page-frame.tsx +19 -11
  30. package/src/suite/components/today-ui.tsx +276 -19
  31. package/src/suite/index.ts +41 -25
  32. package/src/suite/lib/apps.ts +32 -2
  33. package/src/suite/lib/use-sidebar-width.ts +114 -0
  34. package/src/components/sections/AccordionShowcase.tsx +0 -45
  35. package/src/components/sections/AlertShowcase.tsx +0 -40
  36. package/src/components/sections/AvatarShowcase.tsx +0 -80
  37. package/src/components/sections/BadgeShowcase.tsx +0 -65
  38. package/src/components/sections/ButtonShowcase.tsx +0 -308
  39. package/src/components/sections/CalendarShowcase.tsx +0 -35
  40. package/src/components/sections/CardShowcase.tsx +0 -159
  41. package/src/components/sections/CodeMockupShowcase.tsx +0 -56
  42. package/src/components/sections/ColorPalette.tsx +0 -117
  43. package/src/components/sections/CommandPaletteShowcase.tsx +0 -48
  44. package/src/components/sections/CountdownShowcase.tsx +0 -43
  45. package/src/components/sections/DiffShowcase.tsx +0 -70
  46. package/src/components/sections/DrawerShowcase.tsx +0 -97
  47. package/src/components/sections/DropdownShowcase.tsx +0 -98
  48. package/src/components/sections/EmptyStateShowcase.tsx +0 -50
  49. package/src/components/sections/FilterChipsShowcase.tsx +0 -98
  50. package/src/components/sections/FormShowcase.tsx +0 -127
  51. package/src/components/sections/HoverCardShowcase.tsx +0 -50
  52. package/src/components/sections/IconShowcase.tsx +0 -121
  53. package/src/components/sections/IndicatorShowcase.tsx +0 -52
  54. package/src/components/sections/KbdShowcase.tsx +0 -57
  55. package/src/components/sections/ModalShowcase.tsx +0 -211
  56. package/src/components/sections/NavigationShowcase.tsx +0 -199
  57. package/src/components/sections/NewComponentsShowcase.tsx +0 -1052
  58. package/src/components/sections/ProductLayoutsShowcase.tsx +0 -78
  59. package/src/components/sections/ProgressShowcase.tsx +0 -82
  60. package/src/components/sections/QuillChartShowcase.tsx +0 -92
  61. package/src/components/sections/QuillDashboardShowcase.tsx +0 -149
  62. package/src/components/sections/SegmentedControlShowcase.tsx +0 -49
  63. package/src/components/sections/SetupThemeShowcase.tsx +0 -262
  64. package/src/components/sections/SidebarShowcase.tsx +0 -152
  65. package/src/components/sections/StackShowcase.tsx +0 -33
  66. package/src/components/sections/StepperShowcase.tsx +0 -37
  67. package/src/components/sections/SuiteShowcase.tsx +0 -669
  68. package/src/components/sections/SwapShowcase.tsx +0 -99
  69. package/src/components/sections/TabShowcase.tsx +0 -84
  70. package/src/components/sections/TableShowcase.tsx +0 -142
  71. package/src/components/sections/TimelineShowcase.tsx +0 -83
  72. package/src/components/sections/ToastShowcase.tsx +0 -108
  73. package/src/components/sections/TooltipShowcase.tsx +0 -38
  74. package/src/components/sections/UtilityShowcase.tsx +0 -81
  75. package/src/styles/excel-themes.css +0 -110
  76. package/src/styles/lemon-primitives.css +0 -550
@@ -7,58 +7,40 @@ import type { SuiteSpinnerComponent } from '../lib/injected';
7
7
 
8
8
  /**
9
9
  * Today-page loading/empty calibrating state. Apps inject their branded
10
- * AppSpinner; `variant` picks the ornamentation style:
11
- * - `ring` — large spinner ring + ping behind the sun (TurtleTime/ShellStack)
12
- * - `pulse` — pulsing sun only (Tides/Kraken)
10
+ * AppSpinner. Kept deliberately quiet — single soft fade + one spinner,
11
+ * no ping/pulse stacks that compete with OAuth/launch handoffs.
13
12
  */
14
13
  export function TodayCalibrating({
15
14
  onRetry,
16
15
  retrying = false,
17
16
  className,
18
17
  spinner: Spinner,
19
- variant = 'ring',
18
+ variant: _variant = 'ring',
20
19
  retrySpinnerClassName = 'text-ph-brand',
21
20
  }: {
22
21
  onRetry?: () => void;
23
22
  retrying?: boolean;
24
23
  className?: string;
25
24
  spinner: SuiteSpinnerComponent;
25
+ /** @deprecated Kept for call-site compat; ornamentation is always calm. */
26
26
  variant?: 'ring' | 'pulse';
27
27
  retrySpinnerClassName?: string;
28
28
  }) {
29
+ void _variant;
29
30
  return (
30
31
  <div
31
32
  className={cn(
32
- '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',
33
+ 'suite-auth-handoff 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',
33
34
  className,
34
35
  )}
35
36
  data-test="today-calibrating"
36
37
  >
37
38
  <div
38
- className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.12),transparent_55%)]"
39
+ className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.1),transparent_55%)]"
39
40
  aria-hidden
40
41
  />
41
- <div className="relative mb-6 flex h-24 w-24 items-center justify-center">
42
- {variant === 'ring' ? (
43
- <>
44
- <Spinner
45
- size="lg"
46
- className="absolute inset-0 m-auto opacity-40"
47
- label="Loading today"
48
- />
49
- <span
50
- className="bg-ph-muted/30 absolute inset-2 animate-ping rounded-full"
51
- aria-hidden
52
- />
53
- </>
54
- ) : null}
55
- <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">
56
- <Sun
57
- className={cn('h-9 w-9', variant === 'pulse' && 'animate-pulse')}
58
- strokeWidth={2}
59
- aria-hidden
60
- />
61
- </span>
42
+ <div className="relative mb-5 flex h-14 w-14 items-center justify-center rounded-2xl border border-ph-border bg-ph-surface text-ph-ink shadow-sm">
43
+ <Sun className="h-8 w-8" strokeWidth={2} aria-hidden />
62
44
  </div>
63
45
  <h2 className="relative text-base font-semibold tracking-tight text-ph-ink sm:text-lg md:text-xl">
64
46
  Calibrating your day
@@ -66,14 +48,8 @@ export function TodayCalibrating({
66
48
  <p className="relative mt-1.5 max-w-sm text-sm sm:text-[15px] leading-relaxed text-ph-subtle">
67
49
  Pulling together tasks, time and updates from your workspace…
68
50
  </p>
69
- <div className="relative mt-5 flex gap-1.5" aria-hidden>
70
- {[0, 1, 2, 3, 4].map((i) => (
71
- <span
72
- key={i}
73
- className="h-1.5 w-5 animate-pulse rounded-full bg-ph-muted"
74
- style={{ animationDelay: `${i * 120}ms` }}
75
- />
76
- ))}
51
+ <div className="relative mt-5 flex justify-center">
52
+ <Spinner size="sm" className="opacity-70" label="Loading today" />
77
53
  </div>
78
54
  {onRetry ? (
79
55
  <button
@@ -1,26 +1,34 @@
1
- 'use client';
1
+ "use client";
2
2
 
3
- import type { ReactNode } from 'react';
3
+ import type { ReactNode } from "react";
4
4
 
5
- /** Full-bleed theme wash + soft clouds; uses ph-* tokens from each app's theme. */
5
+ /**
6
+ * Today page content wrapper with soft atmosphere.
7
+ * Does not own scrolling — `#main-content` is the scrollport so sticky headers work.
8
+ * Glow/clouds are absolute and do not create a nested scrollport.
9
+ */
6
10
  export function TodayPageFrame({ children }: { children: ReactNode }) {
7
11
  return (
8
12
  <div
9
13
  data-test="today-page"
10
- className="today-page-frame relative flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden"
14
+ className="today-page-frame relative flex min-h-0 w-full flex-1 flex-col overflow-x-hidden"
11
15
  >
12
16
  <div
13
- className="pointer-events-none absolute inset-x-0 top-0 h-[34rem] overflow-hidden"
17
+ className="pointer-events-none absolute inset-0 overflow-hidden"
14
18
  aria-hidden
15
19
  >
16
20
  <div className="today-page-frame__glow absolute inset-0" />
17
- <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" />
18
- <div className="today-page-frame__top-fade absolute inset-x-0 top-0 h-64" />
19
- <div className="today-page-frame__cloud today-page-frame__cloud--1 absolute -left-24 top-28 h-80 w-[26rem] rounded-[45%]" />
20
- <div className="today-page-frame__cloud today-page-frame__cloud--2 absolute -right-20 top-40 h-72 w-80 rounded-[40%]" />
21
- <div className="today-page-frame__cloud today-page-frame__cloud--3 absolute left-[28%] top-[58%] h-64 w-[22rem] rounded-[50%]" />
21
+ <div className="today-page-frame__top-fade absolute inset-x-0 top-0 h-[28rem]" />
22
+ <div className="today-page-frame__cloud today-page-frame__cloud--1 absolute -left-28 top-16 h-96 w-[30rem] rounded-[45%]" />
23
+ <div className="today-page-frame__cloud today-page-frame__cloud--2 absolute -right-24 top-24 h-80 w-[22rem] rounded-[40%]" />
24
+ <div className="today-page-frame__cloud today-page-frame__cloud--3 absolute left-[22%] top-48 h-72 w-[24rem] rounded-[50%]" />
25
+ <div className="today-page-frame__cloud today-page-frame__cloud--4 absolute right-[18%] top-[42%] h-64 w-[20rem] rounded-[48%]" />
26
+ <div className="today-page-frame__cloud today-page-frame__cloud--5 absolute left-[8%] top-[68%] h-56 w-[18rem] rounded-[42%]" />
22
27
  </div>
23
- <div className="relative flex-1 px-4 pb-5 pt-2 sm:px-6 sm:pb-6 sm:pt-3 lg:px-8 [&_.list-row]:min-h-[52px] [&_.list-row]:sm:min-h-[44px]">
28
+ <div
29
+ data-width="full"
30
+ className="suite-page-stage suite-page-inset relative z-[1] flex w-full min-w-0 flex-1 flex-col [&_.list-row]:min-h-[52px] [&_.list-row]:sm:min-h-[44px]"
31
+ >
24
32
  {children}
25
33
  </div>
26
34
  </div>
@@ -6,7 +6,9 @@ import { cn } from '../lib/cn';
6
6
  import { SuiteAppIcon } from '../icons/suite-app-icon';
7
7
  import { SuiteIcon } from '../icons/suite-icon';
8
8
  import type { SuiteIconName } from '../icons/glyphs';
9
- type LucideIcon = import('react').ComponentType<import('react').SVGProps<SVGSVGElement>>;
9
+ type LucideIcon = import('react').ComponentType<
10
+ import('react').SVGProps<SVGSVGElement>
11
+ >;
10
12
 
11
13
  export type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
12
14
 
@@ -117,16 +119,17 @@ export function AnimatedSun({ className }: { className?: string }) {
117
119
  <stop offset="1" stopColor="#fb923c" />
118
120
  </linearGradient>
119
121
  </defs>
120
- <g className="origin-center motion-safe:animate-[spin_20s_linear_infinite] motion-reduce:animate-none">
122
+ {/* ponytail: static rays + soft opacity pulse; spin was too busy for a sticky header */}
123
+ <g className="origin-center opacity-80 motion-safe:animate-[pulse_8s_ease-in-out_infinite] motion-reduce:animate-none">
121
124
  {[0, 45, 90, 135, 180, 225, 270, 315].map((angle) => (
122
125
  <line
123
126
  key={angle}
124
127
  x1="20"
125
- y1="4"
128
+ y1="5"
126
129
  x2="20"
127
130
  y2="9"
128
131
  stroke="#fbbf24"
129
- strokeWidth="2"
132
+ strokeWidth="1.75"
130
133
  strokeLinecap="round"
131
134
  transform={`rotate(${angle} 20 20)`}
132
135
  />
@@ -158,10 +161,9 @@ export function AnimatedMoon({ className }: { className?: string }) {
158
161
  d="M27.5 25.5A11 11 0 1 1 14.5 9.6a9 9 0 1 0 13 15.9Z"
159
162
  fill="url(#animatedMoonBody)"
160
163
  />
161
- <g className="motion-safe:animate-[pulse_4s_ease-in-out_infinite] motion-reduce:animate-none">
162
- <circle cx="29" cy="10" r="1.4" fill="#c7d2fe" />
163
- <circle cx="34" cy="16" r="1" fill="#c7d2fe" />
164
- <circle cx="25" cy="5.5" r="0.9" fill="#c7d2fe" />
164
+ <g className="opacity-70 motion-safe:animate-[pulse_10s_ease-in-out_infinite] motion-reduce:animate-none">
165
+ <circle cx="29" cy="10" r="1.2" fill="#c7d2fe" />
166
+ <circle cx="34" cy="16" r="0.9" fill="#c7d2fe" />
165
167
  </g>
166
168
  </svg>
167
169
  );
@@ -190,7 +192,9 @@ export function TodayTopBar({
190
192
  appIcon?: ReactNode;
191
193
  appName?: string;
192
194
  }) {
193
- const icon = appIcon ?? <TodayTimeIcon className="h-6 w-6 sm:h-7 sm:w-7 text-amber-500" />;
195
+ const icon = appIcon ?? (
196
+ <TodayTimeIcon className="h-6 w-6 sm:h-7 sm:w-7 text-amber-500" />
197
+ );
194
198
  return (
195
199
  <header className="flex items-center justify-between gap-4">
196
200
  <div className="flex min-w-0 items-center gap-3">
@@ -252,33 +256,287 @@ export function getTodayGreeting(
252
256
  const hours = stats?.hours ?? 0;
253
257
 
254
258
  if (overdueCount > 0) {
255
- return `${greeting}. You have ${taskCount} task${taskCount === 1 ? '' : 's'} due today, ${overdueCount} overdue.`;
259
+ return `${greeting}. ${taskCount} task${taskCount === 1 ? '' : 's'} due today, ${overdueCount} overdue.`;
256
260
  }
257
261
  if (taskCount > 0) {
258
- return `${greeting}. You have ${taskCount} task${taskCount === 1 ? '' : 's'} due today.`;
262
+ return `${greeting}. ${taskCount} task${taskCount === 1 ? '' : 's'} due today.`;
259
263
  }
260
264
  if (hours > 0) {
261
- return `${greeting}. You logged ${hours} hour${hours === 1 ? '' : 's'} today.`;
265
+ return `${greeting}. ${hours} hour${hours === 1 ? '' : 's'} logged today.`;
262
266
  }
263
267
  if (period === 'evening' || period === 'night') {
264
- return `${greeting}. Time to wrap up.`;
268
+ return `${greeting}. Nice work — time to wrap up.`;
265
269
  }
266
- return `${greeting}. Nothing overdue — take a nice, deep breath.`;
270
+ return `${greeting}. Clear queue — take a nice, deep breath.`;
267
271
  }
268
272
 
269
273
  export function TodayHero({
270
274
  message = 'Take a nice, deep breath.',
275
+ brief,
276
+ briefMeta,
271
277
  }: {
272
278
  message?: ReactNode;
279
+ /** AI / local focus brief under the greeting. */
280
+ brief?: ReactNode;
281
+ briefMeta?: ReactNode;
273
282
  }) {
274
283
  return (
275
- <div className="mt-8 text-center sm:mt-10">
284
+ <div className="mt-4 sm:mt-6">
276
285
  <p
277
286
  key={typeof message === 'string' ? message : 'hero'}
278
- className="today-hero font-display text-2xl font-bold tracking-tight text-ph-ink sm:text-[1.75rem] sm:leading-tight"
287
+ className="today-hero max-w-2xl font-display text-lg font-semibold tracking-tight text-ph-ink sm:text-xl sm:leading-snug"
279
288
  >
280
289
  {message}
281
290
  </p>
291
+ {brief ? (
292
+ <div className="mt-4 max-w-2xl" data-test="today-focus-brief">
293
+ <p className="text-[15px] leading-[1.65] text-ph-ink">{brief}</p>
294
+ {briefMeta ? (
295
+ <p className="mt-1.5 text-xs text-ph-mutedtext">{briefMeta}</p>
296
+ ) : null}
297
+ </div>
298
+ ) : null}
299
+ </div>
300
+ );
301
+ }
302
+
303
+ export type TodayPrimaryAccent =
304
+ 'tides' | 'turtletime' | 'kraken' | 'shellstack' | 'neutral';
305
+
306
+ /**
307
+ * Compact primary CTA for Today — one action per app.
308
+ * Colours live in suite-skin (.today-primary-action); no idle animation.
309
+ */
310
+ export function TodayPrimaryAction({
311
+ label,
312
+ description,
313
+ icon,
314
+ accent = 'neutral',
315
+ onClick,
316
+ href,
317
+ loading = false,
318
+ active = false,
319
+ className,
320
+ dataTest = 'today-primary-action',
321
+ }: {
322
+ label: string;
323
+ description?: string;
324
+ icon?: ReactNode;
325
+ accent?: TodayPrimaryAccent;
326
+ onClick?: () => void;
327
+ href?: string;
328
+ loading?: boolean;
329
+ /** e.g. timer already running */
330
+ active?: boolean;
331
+ className?: string;
332
+ dataTest?: string;
333
+ }) {
334
+ const classes = cn(
335
+ 'today-primary-action group relative flex w-full max-w-md items-center gap-3 overflow-hidden rounded-xl px-3.5 py-2.5 text-left transition sm:px-4 sm:py-3',
336
+ `today-primary-action--${accent}`,
337
+ active && 'today-primary-action--active',
338
+ loading && 'pointer-events-none opacity-70',
339
+ className,
340
+ );
341
+
342
+ const body = (
343
+ <>
344
+ <span className="today-primary-action__icon inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg sm:h-10 sm:w-10">
345
+ {icon}
346
+ </span>
347
+ <span className="min-w-0 flex-1">
348
+ <span className="block text-sm font-semibold tracking-tight sm:text-base">
349
+ {label}
350
+ </span>
351
+ {description ? (
352
+ <span className="mt-0.5 block text-xs opacity-80 sm:text-sm">
353
+ {description}
354
+ </span>
355
+ ) : null}
356
+ </span>
357
+ <span
358
+ className="today-primary-action__chevron shrink-0 text-base opacity-60 transition group-hover:translate-x-0.5 group-hover:opacity-100"
359
+ aria-hidden
360
+ >
361
+ →
362
+ </span>
363
+ </>
364
+ );
365
+
366
+ if (href && !onClick) {
367
+ return (
368
+ <a
369
+ href={href}
370
+ className={classes}
371
+ data-test={dataTest}
372
+ aria-busy={loading}
373
+ >
374
+ {body}
375
+ </a>
376
+ );
377
+ }
378
+
379
+ return (
380
+ <button
381
+ type="button"
382
+ onClick={onClick}
383
+ disabled={loading}
384
+ className={classes}
385
+ data-test={dataTest}
386
+ aria-busy={loading}
387
+ >
388
+ {body}
389
+ </button>
390
+ );
391
+ }
392
+
393
+ export function TodayEmptyAction({
394
+ title,
395
+ description,
396
+ actionLabel,
397
+ href,
398
+ onClick,
399
+ dataTest,
400
+ }: {
401
+ title: string;
402
+ description?: string;
403
+ actionLabel?: string;
404
+ href?: string;
405
+ onClick?: () => void;
406
+ dataTest?: string;
407
+ }) {
408
+ return (
409
+ <div
410
+ className="rounded-lg border border-dashed border-ph-border/80 bg-ph-muted/30 px-4 py-4 text-center"
411
+ data-test={dataTest}
412
+ >
413
+ <p className="text-sm font-semibold text-ph-ink">{title}</p>
414
+ {description ? (
415
+ <p className="mx-auto mt-1 max-w-sm text-xs leading-relaxed text-ph-subtle sm:text-sm">
416
+ {description}
417
+ </p>
418
+ ) : null}
419
+ {actionLabel && (href || onClick) ? (
420
+ href ? (
421
+ <a
422
+ href={href}
423
+ className="mt-2.5 inline-flex text-sm font-semibold text-ph-brand hover:underline"
424
+ >
425
+ {actionLabel}
426
+ </a>
427
+ ) : (
428
+ <button
429
+ type="button"
430
+ onClick={onClick}
431
+ className="mt-2.5 inline-flex text-sm font-semibold text-ph-brand hover:underline"
432
+ >
433
+ {actionLabel}
434
+ </button>
435
+ )
436
+ ) : null}
437
+ </div>
438
+ );
439
+ }
440
+
441
+ /** Desktop Today shell: main column + sticky Ask AI / brief aside. */
442
+ export function TodayLayout({
443
+ primary,
444
+ main,
445
+ aside,
446
+ className,
447
+ }: {
448
+ primary?: ReactNode;
449
+ main: ReactNode;
450
+ aside?: ReactNode;
451
+ className?: string;
452
+ }) {
453
+ return (
454
+ <div
455
+ className={cn('mt-5 space-y-6 sm:mt-6', className)}
456
+ data-test="today-layout"
457
+ >
458
+ {primary ? <div className="max-w-md">{primary}</div> : null}
459
+ <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(16rem,20rem)] lg:items-start lg:gap-8">
460
+ <div className="min-w-0 space-y-8">{main}</div>
461
+ {aside ? (
462
+ <aside className="space-y-3 lg:sticky lg:top-6 lg:self-start">
463
+ {aside}
464
+ </aside>
465
+ ) : null}
466
+ </div>
467
+ </div>
468
+ );
469
+ }
470
+
471
+ /**
472
+ * Today Ask AI CTA — opens the suite assistant (not Kraken Deep Research).
473
+ * Prefer onClick → openSuiteAskAi(); href is a fallback only.
474
+ */
475
+ export function TodayAskAiCard({
476
+ title = 'Ask AI',
477
+ description = 'Ask about your workspace, draft something, or look a fact up online.',
478
+ cta = 'Ask',
479
+ className,
480
+ onClick,
481
+ href,
482
+ }: {
483
+ title?: string;
484
+ description?: string;
485
+ cta?: string;
486
+ className?: string;
487
+ onClick?: () => void;
488
+ href?: string;
489
+ }) {
490
+ const classes = cn(
491
+ 'group flex w-full items-start gap-3.5 rounded-2xl border border-ph-border/70 bg-ph-surface px-4 py-3.5 text-left shadow-[0_1px_12px_-8px_rgba(15,23,42,0.1)] transition hover:border-ph-brand/35 hover:bg-[color-mix(in_oklab,var(--ph-accent)_6%,var(--ph-surface))] sm:px-5',
492
+ className,
493
+ );
494
+
495
+ const body = (
496
+ <>
497
+ <span className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-ph-brand/15 text-ph-brand shadow-sm ring-1 ring-black/[0.06]">
498
+ <SuiteIcon name="stack-hex" accent="shellstack" className="h-5 w-5" />
499
+ </span>
500
+ <span className="min-w-0 flex-1">
501
+ <span className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1">
502
+ <span className="font-display text-sm font-bold tracking-tight text-ph-ink">
503
+ {title}
504
+ </span>
505
+ <span className="text-sm font-medium text-ph-brand transition group-hover:underline">
506
+ {cta}
507
+ </span>
508
+ </span>
509
+ <span className="mt-1 block text-sm leading-relaxed text-ph-subtle">
510
+ {description}
511
+ </span>
512
+ </span>
513
+ </>
514
+ );
515
+
516
+ if (onClick) {
517
+ return (
518
+ <button
519
+ type="button"
520
+ onClick={onClick}
521
+ className={classes}
522
+ data-test="today-ask-ai"
523
+ >
524
+ {body}
525
+ </button>
526
+ );
527
+ }
528
+
529
+ if (href) {
530
+ return (
531
+ <a href={href} className={classes} data-test="today-ask-ai">
532
+ {body}
533
+ </a>
534
+ );
535
+ }
536
+
537
+ return (
538
+ <div className={classes} data-test="today-ask-ai">
539
+ {body}
282
540
  </div>
283
541
  );
284
542
  }
@@ -301,7 +559,7 @@ export function TodaySection({
301
559
  children: ReactNode;
302
560
  }) {
303
561
  return (
304
- <section data-test={testId} className="space-y-4">
562
+ <section data-test={testId} className="space-y-5">
305
563
  <div className="flex items-start justify-between gap-4">
306
564
  <div className="flex min-w-0 items-start gap-3.5">
307
565
  <span
@@ -340,8 +598,7 @@ export function TodayPanel({
340
598
  return (
341
599
  <div
342
600
  className={cn(
343
- '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',
344
- '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',
601
+ 'border-ph-border/70 rounded-2xl border bg-ph-surface p-5 shadow-[0_1px_16px_-8px_rgba(15,23,42,0.12)] sm:p-5',
345
602
  className,
346
603
  )}
347
604
  >
@@ -1,5 +1,5 @@
1
- export { TodayPageFrame } from './components/today-page-frame';
2
- export * from './components/today-ui';
1
+ export { TodayPageFrame } from "./components/today-page-frame";
2
+ export * from "./components/today-ui";
3
3
  export {
4
4
  AppSwitcher,
5
5
  AppSwitcherChevron,
@@ -8,32 +8,37 @@ export {
8
8
  appSwitcherMenuItemClass,
9
9
  appSwitcherTriggerClass,
10
10
  type SuiteAppEntry,
11
- } from './components/app-switcher';
11
+ } from "./components/app-switcher";
12
12
  export {
13
13
  SuiteNotificationBell,
14
14
  type SuiteNotification,
15
15
  type SuiteNotificationsResponse,
16
- } from './components/suite-notification-bell';
17
- export { CommandPaletteHost } from './components/command-palette-host';
18
- export { DeferredChrome } from './components/deferred-chrome';
19
- export { SuiteMobileDrawer } from './components/suite-mobile-drawer';
20
- export { SuiteMobileHeader } from './components/suite-mobile-header';
16
+ } from "./components/suite-notification-bell";
17
+ export { CommandPaletteHost } from "./components/command-palette-host";
18
+ export { DeferredChrome } from "./components/deferred-chrome";
19
+ export { SuiteMobileDrawer } from "./components/suite-mobile-drawer";
20
+ export { SuiteMobileHeader } from "./components/suite-mobile-header";
21
+ export {
22
+ SuiteSettingsLayout,
23
+ type SuiteSettingsLayoutProps,
24
+ } from "./components/suite-settings-layout";
21
25
  export {
22
26
  SuiteUserMenu,
23
27
  type SuiteUserMenuProps,
24
- } from './components/suite-user-menu';
28
+ } from "./components/suite-user-menu";
25
29
  export {
26
30
  SuiteBottomNav,
27
31
  type SuiteBottomNavItem,
28
- } from './components/suite-bottom-nav';
32
+ } from "./components/suite-bottom-nav";
29
33
  export {
30
34
  SUITE_APPS,
31
35
  SUITE_APP_MAP,
32
36
  suiteAppBaseUrl,
37
+ resolveSuiteNotificationHref,
33
38
  type SuiteAppDefinition,
34
39
  type SuiteAppSlug,
35
- } from './lib/apps';
36
- export { TodayCalibrating } from './components/today-calibrating';
40
+ } from "./lib/apps";
41
+ export { TodayCalibrating } from "./components/today-calibrating";
37
42
  export {
38
43
  SuiteThemeProvider,
39
44
  useSuiteTheme,
@@ -41,11 +46,15 @@ export {
41
46
  type SuiteTheme,
42
47
  type SuiteThemeConfig,
43
48
  type SuiteThemeContextValue,
44
- } from './components/theme-provider';
49
+ } from "./components/theme-provider";
45
50
  export {
46
51
  SuiteAiPanel,
52
+ openSuiteAskAi,
53
+ SUITE_OPEN_ASK_AI_EVENT,
47
54
  type SuiteAiPreset,
48
- } from './components/ai-panel';
55
+ type SuiteAiChatMessage,
56
+ type SuiteAskAiOpenDetail,
57
+ } from "./components/ai-panel";
49
58
  export {
50
59
  APP_ACCENTS,
51
60
  APP_GLYPHS,
@@ -60,7 +69,7 @@ export {
60
69
  type SuiteGlyphElement,
61
70
  type SuiteIconName,
62
71
  type SuiteIconProps,
63
- } from './icons';
72
+ } from "./icons";
64
73
  export type {
65
74
  SuiteCommandItem,
66
75
  SuiteCommandPaletteComponent,
@@ -68,15 +77,23 @@ export type {
68
77
  SuiteDropdownMenuComponent,
69
78
  SuiteDropdownMenuProps,
70
79
  SuiteSpinnerComponent,
71
- } from './lib/injected';
72
- export { useIdleMount } from './lib/use-idle-mount';
73
- export { cn } from './lib/cn';
80
+ } from "./lib/injected";
81
+ export { useIdleMount } from "./lib/use-idle-mount";
82
+ export {
83
+ SIDEBAR_COLLAPSE_THRESHOLD,
84
+ SIDEBAR_DEFAULT_WIDTH,
85
+ SIDEBAR_MAX_WIDTH,
86
+ SIDEBAR_MIN_EXPANDED_WIDTH,
87
+ SIDEBAR_RAIL_WIDTH,
88
+ useSidebarWidth,
89
+ } from "./lib/use-sidebar-width";
90
+ export { cn } from "./lib/cn";
74
91
  export {
75
92
  SuiteMotionProvider,
76
93
  m,
77
94
  SUITE_SPRINGS,
78
95
  type SuiteSpringName,
79
- } from './lib/motion';
96
+ } from "./lib/motion";
80
97
  export {
81
98
  SuitePage,
82
99
  SuitePageHeader,
@@ -86,23 +103,22 @@ export {
86
103
  SuiteSectionHeader,
87
104
  type SuitePageWidth,
88
105
  type SuiteBreadcrumbsProps,
89
- } from './components/suite-layout';
106
+ } from "./components/suite-layout";
90
107
  export {
91
108
  SuiteSkeleton,
92
109
  SuiteSkeletonCard,
93
110
  SuiteSkeletonList,
94
111
  SuiteEmptyState,
95
- } from './components/suite-skeleton';
112
+ } from "./components/suite-skeleton";
96
113
  export {
97
114
  SuiteSettingsMobileNav,
98
115
  type SuiteSettingsNavItem,
99
- } from './components/suite-settings-mobile-nav';
116
+ } from "./components/suite-settings-mobile-nav";
100
117
  export {
101
118
  SuiteSidebar,
102
119
  type SuiteSidebarNavItem,
103
- } from './components/suite-sidebar';
120
+ } from "./components/suite-sidebar";
104
121
  export {
105
122
  SuiteAppLayout,
106
123
  type SuiteAppLayoutProps,
107
- } from './components/suite-app-layout';
108
-
124
+ } from "./components/suite-app-layout";