@xenide-io/the-old-ui-theme 0.9.18 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{Chip-BzuNgJIa.d.mts → Chip-Dw2g2t8D.d.mts} +18 -4
  2. package/dist/{Chip-BzuNgJIa.d.ts → Chip-Dw2g2t8D.d.ts} +18 -4
  3. package/dist/{chunk-6SVJAU5K.mjs → chunk-RIPGN6FG.mjs} +1333 -526
  4. package/dist/index.d.mts +3 -2
  5. package/dist/index.d.ts +3 -2
  6. package/dist/index.js +490 -119
  7. package/dist/index.mjs +11 -3
  8. package/dist/suite-analytics--Mf1RZbw.d.mts +29 -0
  9. package/dist/suite-analytics--Mf1RZbw.d.ts +29 -0
  10. package/dist/suite.d.mts +36 -4
  11. package/dist/suite.d.ts +36 -4
  12. package/dist/suite.js +4617 -4001
  13. package/dist/suite.mjs +1456 -868
  14. package/dist/tailwind-preset.js +10 -1
  15. package/dist/tailwind-preset.mjs +10 -1
  16. package/dist/ui.d.mts +33 -4
  17. package/dist/ui.d.ts +33 -4
  18. package/dist/ui.js +1364 -571
  19. package/dist/ui.mjs +15 -1
  20. package/docs/macos-identity-audit.md +279 -0
  21. package/package.json +1 -1
  22. package/src/app/globals.css +390 -44
  23. package/src/components/ui/Accordion.tsx +4 -7
  24. package/src/components/ui/Avatar.tsx +5 -5
  25. package/src/components/ui/BrandSpinner.tsx +256 -0
  26. package/src/components/ui/Calendar.tsx +7 -7
  27. package/src/components/ui/CodeBlock.tsx +14 -11
  28. package/src/components/ui/CollapsibleSection.tsx +4 -4
  29. package/src/components/ui/CommandPalette.tsx +7 -10
  30. package/src/components/ui/EmptyState.tsx +2 -2
  31. package/src/components/ui/Input.tsx +1 -1
  32. package/src/components/ui/Marketing.tsx +144 -0
  33. package/src/components/ui/MarketingIllustrations.tsx +287 -0
  34. package/src/components/ui/Panel.tsx +1 -1
  35. package/src/components/ui/SegmentedControl.tsx +59 -13
  36. package/src/components/ui/SettingsLayout.tsx +4 -0
  37. package/src/components/ui/ShowcaseWrapper.tsx +3 -3
  38. package/src/components/ui/Table.tsx +45 -10
  39. package/src/components/ui/Typography.tsx +4 -4
  40. package/src/components/ui/avatar-stat.test.tsx +14 -1
  41. package/src/components/ui/brand-spinner.test.tsx +62 -0
  42. package/src/components/ui/index.ts +19 -11
  43. package/src/components/ui/interaction-primitives.test.tsx +12 -6
  44. package/src/components/ui/marketing.test.tsx +39 -0
  45. package/src/components/ui/table.test.tsx +47 -0
  46. package/src/styles/suite-skin.css +471 -74
  47. package/src/styles/themes.css +45 -45
  48. package/src/styles/typography.css +25 -1
  49. package/src/suite/components/ai-draft-editor.tsx +184 -0
  50. package/src/suite/components/ai-panel-workflows.test.tsx +179 -0
  51. package/src/suite/components/ai-panel.tsx +201 -31
  52. package/src/suite/components/app-switcher.tsx +22 -9
  53. package/src/suite/components/command-palette-host.tsx +20 -2
  54. package/src/suite/components/suite-app-layout.tsx +7 -4
  55. package/src/suite/components/suite-bottom-nav.tsx +1 -1
  56. package/src/suite/components/suite-clients-projects-directory.tsx +6 -6
  57. package/src/suite/components/suite-integration-picker.tsx +7 -7
  58. package/src/suite/components/suite-integration-rules.tsx +5 -5
  59. package/src/suite/components/suite-layout.test.tsx +32 -3
  60. package/src/suite/components/suite-layout.tsx +4 -4
  61. package/src/suite/components/suite-mobile-drawer.tsx +2 -2
  62. package/src/suite/components/suite-mobile-header.tsx +1 -1
  63. package/src/suite/components/suite-notification-bell.tsx +27 -6
  64. package/src/suite/components/suite-notification-settings.tsx +182 -0
  65. package/src/suite/components/suite-palette-library.test.tsx +46 -0
  66. package/src/suite/components/suite-palette-library.tsx +82 -15
  67. package/src/suite/components/suite-settings-layout.tsx +2 -2
  68. package/src/suite/components/suite-settings-mobile-nav.tsx +10 -10
  69. package/src/suite/components/suite-sidebar.test.tsx +39 -0
  70. package/src/suite/components/suite-sidebar.tsx +41 -19
  71. package/src/suite/components/suite-skeleton.tsx +6 -6
  72. package/src/suite/components/suite-user-menu.tsx +2 -2
  73. package/src/suite/components/theme-provider.tsx +2 -0
  74. package/src/suite/components/today-calibrating.tsx +3 -3
  75. package/src/suite/components/today-ui.tsx +42 -37
  76. package/src/suite/components/workspace-switcher.tsx +3 -3
  77. package/src/suite/index.ts +14 -0
  78. package/src/suite/lib/suite-notifications.tsx +5 -0
  79. package/tailwind-preset.ts +9 -0
@@ -3,10 +3,12 @@
3
3
  import {
4
4
  useCallback,
5
5
  useEffect,
6
+ useId,
6
7
  useMemo,
7
8
  useRef,
8
9
  useState,
9
10
  type ReactNode,
11
+ type KeyboardEvent as ReactKeyboardEvent,
10
12
  } from 'react';
11
13
  import { Filter, FilterList, NavArrowDown, Search } from 'iconoir-react';
12
14
  import {
@@ -58,7 +60,7 @@ function FilterSelect({
58
60
  aria-label={label}
59
61
  value={value}
60
62
  onChange={(event) => onChange(event.target.value)}
61
- className="h-full w-full appearance-none rounded-lg border border-ph-border bg-ph-surface py-2 pl-8 pr-7 text-sm text-ph-ink focus:border-ph-brand focus:outline-none"
63
+ className="ph-select h-full w-full py-2 pl-8 pr-7"
62
64
  >
63
65
  {children}
64
66
  </select>
@@ -86,6 +88,8 @@ export function SuitePaletteLibrary({
86
88
  const [libraryOpen, setLibraryOpen] = useState(false);
87
89
  const [libraryQuery, setLibraryQuery] = useState('');
88
90
  const libraryRef = useRef<HTMLDivElement>(null);
91
+ const libraryTriggerRef = useRef<HTMLButtonElement>(null);
92
+ const libraryListboxId = useId();
89
93
  const [icons, setIcons] = useState<PaletteIcon[]>([]);
90
94
  const [total, setTotal] = useState(0);
91
95
  const [page, setPage] = useState(1);
@@ -245,6 +249,45 @@ export function SuitePaletteLibrary({
245
249
  return () => document.removeEventListener('pointerdown', onPointerDown);
246
250
  }, [libraryOpen]);
247
251
 
252
+ const closeLibrary = (restoreFocus = false) => {
253
+ setLibraryOpen(false);
254
+ if (restoreFocus) requestAnimationFrame(() => libraryTriggerRef.current?.focus());
255
+ };
256
+
257
+ const handleLibraryOptionKeyDown = (
258
+ event: ReactKeyboardEvent<HTMLButtonElement>,
259
+ ) => {
260
+ const options = Array.from(
261
+ libraryRef.current?.querySelectorAll<HTMLButtonElement>('[role="option"]') ?? [],
262
+ );
263
+ const index = options.indexOf(event.currentTarget);
264
+ let nextIndex: number;
265
+
266
+ switch (event.key) {
267
+ case 'ArrowDown':
268
+ nextIndex = (index + 1) % options.length;
269
+ break;
270
+ case 'ArrowUp':
271
+ nextIndex = (index - 1 + options.length) % options.length;
272
+ break;
273
+ case 'Home':
274
+ nextIndex = 0;
275
+ break;
276
+ case 'End':
277
+ nextIndex = options.length - 1;
278
+ break;
279
+ case 'Escape':
280
+ event.preventDefault();
281
+ closeLibrary(true);
282
+ return;
283
+ default:
284
+ return;
285
+ }
286
+
287
+ event.preventDefault();
288
+ options[nextIndex]?.focus();
289
+ };
290
+
248
291
  return (
249
292
  <div className="space-y-3" data-test="palette-icon-library">
250
293
  <div ref={libraryRef} className="space-y-2">
@@ -260,15 +303,23 @@ export function SuitePaletteLibrary({
260
303
  onChange={(event) => setQuery(event.target.value)}
261
304
  placeholder="Search icons — handshake, rocket, briefcase…"
262
305
  aria-label="Search icons"
263
- className="w-full rounded-lg border border-ph-border bg-ph-surface py-2 pl-8 pr-3 text-sm text-ph-ink placeholder:text-ph-mutedtext focus:border-ph-brand focus:outline-none"
306
+ className="ph-input w-full py-2 pl-8 pr-3"
264
307
  />
265
308
  </div>
266
309
  <button
310
+ ref={libraryTriggerRef}
267
311
  type="button"
268
312
  aria-haspopup="listbox"
269
313
  aria-expanded={libraryOpen}
314
+ aria-controls={libraryOpen ? libraryListboxId : undefined}
270
315
  onClick={() => setLibraryOpen((open) => !open)}
271
- className="flex items-center gap-2 rounded-lg border border-ph-border bg-ph-surface px-2.5 py-2 text-sm text-ph-ink hover:border-ph-brand focus:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
316
+ onKeyDown={(event) => {
317
+ if (event.key === 'Escape' && libraryOpen) {
318
+ event.preventDefault();
319
+ closeLibrary();
320
+ }
321
+ }}
322
+ className="ph-input flex items-center gap-2 px-2.5 py-2 text-sm text-ph-ink"
272
323
  >
273
324
  <FilterList className="h-4 w-4 shrink-0 text-ph-mutedtext" aria-hidden />
274
325
  <span className="max-w-[10rem] truncate">
@@ -297,7 +348,7 @@ export function SuitePaletteLibrary({
297
348
  </div>
298
349
 
299
350
  {libraryOpen ? (
300
- <div className="rounded-lg border border-ph-border bg-ph-surface p-2">
351
+ <div className="rounded-[var(--ph-radius-overlay)] border border-[var(--ph-border-control)] bg-[var(--ph-surface-overlay)] p-2 shadow-[var(--ph-shadow-floating)]">
301
352
  <div className="relative mb-2">
302
353
  <Search
303
354
  className="pointer-events-none absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ph-mutedtext"
@@ -310,11 +361,23 @@ export function SuitePaletteLibrary({
310
361
  onChange={(event) => setLibraryQuery(event.target.value)}
311
362
  placeholder="Search libraries…"
312
363
  aria-label="Search libraries"
313
- className="w-full rounded-md border border-ph-border bg-ph-muted py-1.5 pl-8 pr-3 text-sm text-ph-ink placeholder:text-ph-mutedtext focus:border-ph-brand focus:outline-none"
364
+ className="ph-input w-full bg-[var(--ph-surface-inset)] py-1.5 pl-8 pr-3"
365
+ onKeyDown={(event) => {
366
+ if (event.key === 'ArrowDown') {
367
+ event.preventDefault();
368
+ libraryRef.current
369
+ ?.querySelector<HTMLButtonElement>('[role="option"]')
370
+ ?.focus();
371
+ } else if (event.key === 'Escape') {
372
+ event.preventDefault();
373
+ closeLibrary(true);
374
+ }
375
+ }}
314
376
  />
315
377
  </div>
316
378
  <ul
317
379
  role="listbox"
380
+ id={libraryListboxId}
318
381
  aria-label="Libraries"
319
382
  className="max-h-56 overflow-y-auto"
320
383
  >
@@ -323,15 +386,17 @@ export function SuitePaletteLibrary({
323
386
  type="button"
324
387
  role="option"
325
388
  aria-selected={!collectionFilter}
389
+ tabIndex={-1}
390
+ onKeyDown={handleLibraryOptionKeyDown}
326
391
  onClick={() => {
327
392
  setCollectionFilter("");
328
- setLibraryOpen(false);
393
+ closeLibrary(true);
329
394
  }}
330
395
  className={cn(
331
- "flex w-full items-center rounded-md px-2 py-1.5 text-left text-sm",
396
+ "flex w-full items-center rounded-[var(--ph-radius-control)] px-2 py-1.5 text-left text-sm focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)]",
332
397
  !collectionFilter
333
- ? "bg-ph-muted text-ph-ink"
334
- : "text-ph-ink hover:bg-ph-muted",
398
+ ? "bg-[var(--ph-selected-bg)] text-[var(--ph-selected-text)]"
399
+ : "text-ph-ink hover:bg-[var(--ph-surface-hover)]",
335
400
  )}
336
401
  >
337
402
  All libraries
@@ -343,15 +408,17 @@ export function SuitePaletteLibrary({
343
408
  type="button"
344
409
  role="option"
345
410
  aria-selected={collectionFilter === collection.slug}
411
+ tabIndex={-1}
412
+ onKeyDown={handleLibraryOptionKeyDown}
346
413
  onClick={() => {
347
414
  setCollectionFilter(collection.slug);
348
- setLibraryOpen(false);
415
+ closeLibrary(true);
349
416
  }}
350
417
  className={cn(
351
- "flex w-full items-center justify-between gap-2 rounded-md px-2 py-1.5 text-left text-sm",
418
+ "flex w-full items-center justify-between gap-2 rounded-[var(--ph-radius-control)] px-2 py-1.5 text-left text-sm focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)]",
352
419
  collectionFilter === collection.slug
353
- ? "bg-ph-muted text-ph-ink"
354
- : "text-ph-ink hover:bg-ph-muted",
420
+ ? "bg-[var(--ph-selected-bg)] text-[var(--ph-selected-text)]"
421
+ : "text-ph-ink hover:bg-[var(--ph-surface-hover)]",
355
422
  )}
356
423
  >
357
424
  <span className="truncate">{collection.name}</span>
@@ -381,7 +448,7 @@ export function SuitePaletteLibrary({
381
448
  </p>
382
449
  ) : (
383
450
  <>
384
- <div className="max-h-[320px] overflow-y-auto rounded-lg border border-ph-border p-2">
451
+ <div className="max-h-[320px] overflow-y-auto rounded-[var(--ph-radius-surface)] border border-ph-border p-2 focus-within:border-[var(--ph-selected-border)]">
385
452
  <div className="grid grid-cols-6 gap-1.5 sm:grid-cols-8">
386
453
  {icons.map((icon) => (
387
454
  <button
@@ -390,7 +457,7 @@ export function SuitePaletteLibrary({
390
457
  onClick={() => onPick({ url: icon.url, name: icon.name })}
391
458
  title={icon.name}
392
459
  aria-label={`Use ${icon.name}`}
393
- className="flex h-10 items-center justify-center rounded-md border border-transparent p-1.5 hover:border-ph-brand hover:bg-ph-muted"
460
+ className="flex h-10 items-center justify-center rounded-[var(--ph-radius-control)] border border-transparent p-1.5 hover:border-[var(--ph-selected-border)] hover:bg-[var(--ph-surface-hover)] focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)]"
394
461
  >
395
462
  {/* eslint-disable-next-line @next/next/no-img-element */}
396
463
  <img
@@ -53,7 +53,7 @@ export function SuiteSettingsLayout({
53
53
  {loading ?? (
54
54
  <div className="suite-page-body mt-6 flex min-h-0 flex-1 flex-col">
55
55
  {mobileNav ? (
56
- <div className="sticky top-0 z-20 mb-2 shrink-0 bg-ph-canvas/95 backdrop-blur lg:hidden">
56
+ <div className="suite-settings-mobile-bar sticky top-0 z-20 mb-2 shrink-0 lg:hidden">
57
57
  {mobileNav}
58
58
  </div>
59
59
  ) : null}
@@ -61,7 +61,7 @@ export function SuiteSettingsLayout({
61
61
  <div className="flex min-h-0 flex-1 flex-col gap-6 lg:flex-row lg:gap-8">
62
62
  <aside
63
63
  aria-label="Settings"
64
- className="hidden w-56 shrink-0 bg-transparent lg:block lg:self-stretch lg:overflow-y-auto lg:overscroll-contain"
64
+ className="suite-settings-sidebar hidden w-56 shrink-0 lg:block lg:self-start lg:max-h-full lg:overflow-y-auto lg:overscroll-contain"
65
65
  >
66
66
  {sidebar}
67
67
  </aside>
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { isValidElement, type ElementType, type ReactNode } from "react";
4
+ import { trackSuiteEvent } from "../../lib/suite-analytics";
4
5
  import { cn } from "../lib/cn";
5
6
 
6
7
  export interface SuiteSettingsNavItem {
@@ -50,8 +51,8 @@ export function SuiteSettingsMobileNav({
50
51
  ) : (
51
52
  <Icon
52
53
  className={cn(
53
- "h-4 w-4 shrink-0 transition",
54
- active ? "text-ph-surface" : "text-ph-mutedtext",
54
+ "h-4 w-4 shrink-0 transition-colors",
55
+ active ? "text-[var(--ph-selected-text)]" : "text-ph-mutedtext",
55
56
  )}
56
57
  aria-hidden
57
58
  />
@@ -62,21 +63,20 @@ export function SuiteSettingsMobileNav({
62
63
  type="button"
63
64
  id={item.id}
64
65
  data-test={item.testId}
65
- onClick={() => onSelect(item.href)}
66
+ onClick={() => {
67
+ trackSuiteEvent("Tab Viewed", { tab: item.id ?? item.href });
68
+ onSelect(item.href);
69
+ }}
66
70
  aria-current={active ? "page" : undefined}
67
71
  className={cn(
68
- "group relative flex shrink-0 snap-start items-center gap-2 rounded-full px-3.5 py-2.5 text-sm font-medium transition-colors",
69
- "min-h-[44px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand",
72
+ "group relative flex min-h-[44px] shrink-0 snap-start items-center gap-2 rounded-[var(--ph-radius-control)] border px-3.5 py-2.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:shadow-[var(--ph-focus-halo)]",
70
73
  active
71
- ? "bg-ph-ink text-ph-surface shadow-sm"
72
- : "bg-ph-surface text-ph-subtle ring-1 ring-ph-border hover:bg-ph-muted hover:text-ph-ink",
74
+ ? "border-[var(--ph-selected-border)] bg-[var(--ph-selected-bg)] text-[var(--ph-selected-text)]"
75
+ : "border-ph-border bg-ph-surface text-ph-subtle hover:bg-[var(--ph-surface-hover)] hover:text-ph-ink",
73
76
  )}
74
77
  >
75
78
  {iconNode}
76
79
  <span className="whitespace-nowrap">{item.label}</span>
77
- {active ? (
78
- <span className="absolute inset-x-0 -bottom-3 h-0.5 rounded-full bg-ph-ink lg:hidden" />
79
- ) : null}
80
80
  </button>
81
81
  );
82
82
  })}
@@ -0,0 +1,39 @@
1
+ import { useState } from "react";
2
+ import { cleanup, render, screen, waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+
6
+ import { SuiteSidebar } from "./suite-sidebar";
7
+
8
+ afterEach(cleanup);
9
+
10
+ describe("SuiteSidebar Ask AI", () => {
11
+ it("moves focus into chat and restores it after Escape", async () => {
12
+ const user = userEvent.setup();
13
+
14
+ function Fixture() {
15
+ const [open, setOpen] = useState(false);
16
+ return (
17
+ <SuiteSidebar
18
+ navItems={[]}
19
+ userMenu={<span>Account</span>}
20
+ askAiOpen={open}
21
+ onAskAiOpenChange={setOpen}
22
+ askAi={<div>Chat</div>}
23
+ />
24
+ );
25
+ }
26
+
27
+ render(<Fixture />);
28
+ const trigger = screen.getByRole("button", { name: "Shelly AI" });
29
+ await user.click(trigger);
30
+
31
+ const back = screen.getByRole("button", { name: "Back to navigation" });
32
+ await waitFor(() => expect(back).toHaveFocus());
33
+ await user.keyboard("{Escape}");
34
+
35
+ await waitFor(() =>
36
+ expect(screen.getByRole("button", { name: "Shelly AI" })).toHaveFocus(),
37
+ );
38
+ });
39
+ });
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { useCallback, useEffect, type ReactNode } from "react";
4
+ import { useCallback, useEffect, useRef, type ReactNode } from "react";
5
5
  import { NavArrowLeft, Sparks } from "iconoir-react";
6
6
 
7
7
  import { Tooltip } from "../../components/ui/Tooltip";
@@ -83,6 +83,9 @@ export function SuiteSidebar({
83
83
  }: SuiteSidebarProps) {
84
84
  const showAskAi = Boolean(onAskAiOpenChange && askAi);
85
85
  const chatOpen = showAskAi && askAiOpen && !collapsed;
86
+ const askAiButtonRef = useRef<HTMLButtonElement>(null);
87
+ const askAiBackRef = useRef<HTMLButtonElement>(null);
88
+ const restoreAskAiFocusRef = useRef(false);
86
89
 
87
90
  const setAskAi = useCallback(
88
91
  (next: boolean) => {
@@ -105,15 +108,35 @@ export function SuiteSidebar({
105
108
  useEffect(() => {
106
109
  if (!chatOpen) return;
107
110
  function onKey(event: KeyboardEvent) {
108
- if (event.key === "Escape") setAskAi(false);
111
+ if (event.key === "Escape") {
112
+ restoreAskAiFocusRef.current = true;
113
+ setAskAi(false);
114
+ }
109
115
  }
110
116
  window.addEventListener("keydown", onKey);
111
117
  return () => window.removeEventListener("keydown", onKey);
112
118
  }, [chatOpen, setAskAi]);
113
119
 
120
+ useEffect(() => {
121
+ const frame = requestAnimationFrame(() => {
122
+ if (chatOpen) {
123
+ askAiBackRef.current?.focus();
124
+ } else if (restoreAskAiFocusRef.current) {
125
+ restoreAskAiFocusRef.current = false;
126
+ askAiButtonRef.current?.focus();
127
+ }
128
+ });
129
+ return () => cancelAnimationFrame(frame);
130
+ }, [chatOpen]);
131
+
132
+ function closeAskAi() {
133
+ restoreAskAiFocusRef.current = true;
134
+ setAskAi(false);
135
+ }
136
+
114
137
  function toggleAskAi() {
115
138
  if (askAiOpen) {
116
- setAskAi(false);
139
+ closeAskAi();
117
140
  return;
118
141
  }
119
142
  if (collapsed) onRequestExpand?.();
@@ -122,13 +145,14 @@ export function SuiteSidebar({
122
145
 
123
146
  const askAiButton = (
124
147
  <button
148
+ ref={askAiButtonRef}
125
149
  type="button"
126
150
  data-test="suite-ask-ai-toggle"
127
151
  aria-pressed={askAiOpen}
128
152
  aria-label={askAiOpen ? "Back to navigation" : "Shelly AI"}
129
153
  onClick={toggleAskAi}
130
154
  className={cn(
131
- "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ph-focus-ring",
155
+ "ph-btn ph-btn-ghost ph-btn-square h-10 w-10 shrink-0",
132
156
  askAiOpen
133
157
  ? "bg-ph-brand/10 text-ph-brand"
134
158
  : "text-ph-mutedtext hover:bg-ph-muted hover:text-ph-ink",
@@ -142,9 +166,9 @@ export function SuiteSidebar({
142
166
  return (
143
167
  <div
144
168
  data-suite-chat-open=""
169
+ data-surface={surface ? "true" : "false"}
145
170
  className={cn(
146
- "flex h-full w-full min-w-0 flex-col text-ph-ink",
147
- surface ? "bg-ph-surface" : "bg-ph-canvas",
171
+ "suite-sidebar flex h-full w-full min-w-0 flex-col text-ph-ink",
148
172
  className,
149
173
  )}
150
174
  >
@@ -155,11 +179,12 @@ export function SuiteSidebar({
155
179
  )}
156
180
  >
157
181
  <button
182
+ ref={askAiBackRef}
158
183
  type="button"
159
184
  data-test="suite-ask-ai-back"
160
185
  aria-label="Back to navigation"
161
- onClick={() => setAskAi(false)}
162
- className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
186
+ onClick={closeAskAi}
187
+ className="ph-btn ph-btn-ghost ph-btn-square h-10 w-10 shrink-0 text-ph-subtle"
163
188
  >
164
189
  <NavArrowLeft className="h-4 w-4" aria-hidden />
165
190
  </button>
@@ -174,9 +199,9 @@ export function SuiteSidebar({
174
199
 
175
200
  return (
176
201
  <div
202
+ data-surface={surface ? "true" : "false"}
177
203
  className={cn(
178
- "flex h-full w-full min-w-0 flex-col text-ph-ink",
179
- surface ? "bg-ph-surface" : "bg-ph-canvas",
204
+ "suite-sidebar flex h-full w-full min-w-0 flex-col text-ph-ink",
180
205
  className,
181
206
  )}
182
207
  >
@@ -184,7 +209,6 @@ export function SuiteSidebar({
184
209
  <div
185
210
  className={cn(
186
211
  "flex shrink-0 border-b border-ph-border",
187
- surface ? "bg-ph-surface" : "bg-ph-canvas",
188
212
  collapsed
189
213
  ? "flex-col items-center gap-1 px-1 py-2"
190
214
  : "h-14 items-center justify-between gap-2 px-3",
@@ -204,7 +228,6 @@ export function SuiteSidebar({
204
228
  <div
205
229
  className={cn(
206
230
  "flex min-h-0 flex-1 flex-col",
207
- surface ? "bg-ph-surface" : "bg-ph-canvas",
208
231
  chatOpen ? "min-h-0" : "overflow-y-auto",
209
232
  collapsed ? "px-1.5 py-2" : "p-3",
210
233
  )}
@@ -226,8 +249,8 @@ export function SuiteSidebar({
226
249
  <button
227
250
  type="button"
228
251
  data-test="suite-ask-ai-back"
229
- onClick={() => setAskAi(false)}
230
- className="inline-flex h-9 items-center gap-1 rounded-lg px-2 text-sm font-medium text-ph-subtle transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ph-brand"
252
+ onClick={closeAskAi}
253
+ className="ph-btn ph-btn-ghost ph-btn-sm h-9 text-ph-subtle"
231
254
  >
232
255
  <NavArrowLeft className="h-4 w-4" aria-hidden />
233
256
  Nav
@@ -254,10 +277,10 @@ export function SuiteSidebar({
254
277
  aria-current={active ? "page" : undefined}
255
278
  onClick={item.onClick}
256
279
  className={cn(
257
- "group relative flex min-h-11 touch-manipulation items-center gap-2.5 rounded-[var(--ph-radius-app)] text-sm font-medium transition-colors",
280
+ "suite-sidebar-link group relative flex min-h-11 touch-manipulation items-center gap-2.5 rounded-[var(--ph-radius-control)] text-sm font-medium transition-colors",
258
281
  active
259
- ? "bg-ph-muted"
260
- : "text-ph-subtle hover:bg-ph-muted hover:text-ph-ink",
282
+ ? "bg-[var(--ph-selected-bg)] text-[var(--ph-selected-text)]"
283
+ : "text-ph-subtle hover:bg-[var(--ph-surface-hover)] hover:text-ph-ink",
261
284
  collapsed
262
285
  ? "mx-auto size-11 shrink-0 justify-center gap-0 px-0 py-0"
263
286
  : "w-full px-2.5 py-2",
@@ -279,7 +302,7 @@ export function SuiteSidebar({
279
302
  className={cn(
280
303
  "relative truncate",
281
304
  active
282
- ? "text-ph-ink"
305
+ ? "text-[var(--ph-selected-text)]"
283
306
  : "text-ph-subtle group-hover:text-ph-ink",
284
307
  collapsed && "sr-only",
285
308
  )}
@@ -319,7 +342,6 @@ export function SuiteSidebar({
319
342
  <div
320
343
  className={cn(
321
344
  "shrink-0 border-t border-ph-border",
322
- surface ? "bg-ph-surface" : "bg-ph-canvas",
323
345
  collapsed ? "p-1.5" : "p-3",
324
346
  )}
325
347
  >
@@ -65,7 +65,7 @@ export function SuiteSkeletonCard({
65
65
  return (
66
66
  <div
67
67
  className={cn(
68
- 'ph-skeleton-card animate-[skeleton-stagger_0.5s_ease-out_both] rounded-2xl border border-ph-border bg-ph-surface p-6 shadow-sm',
68
+ 'ph-skeleton-card animate-[skeleton-stagger_0.5s_ease-out_both] rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface p-6',
69
69
  className,
70
70
  )}
71
71
  aria-hidden
@@ -75,7 +75,7 @@ export function SuiteSkeletonCard({
75
75
  className="mb-5 flex items-center gap-3 animate-[skeleton-stagger_0.5s_ease-out_both]"
76
76
  style={{ animationDelay: '100ms' }}
77
77
  >
78
- <div className="ph-skeleton h-10 w-10 rounded-xl" />
78
+ <div className="ph-skeleton h-10 w-10 rounded-[var(--ph-radius-control)]" />
79
79
  <div className="flex-1 space-y-2">
80
80
  <div className="ph-skeleton h-4 w-1/3" />
81
81
  <div className="ph-skeleton h-3 w-1/2" />
@@ -115,8 +115,8 @@ export function SuiteSkeletonList({
115
115
  className="animate-[skeleton-stagger_0.6s_ease-out_both]"
116
116
  style={{ animationDelay: `${index * 100}ms` }}
117
117
  >
118
- <div className="flex items-center gap-3 rounded-xl border border-ph-border bg-ph-surface p-4">
119
- <div className="ph-skeleton h-10 w-10 rounded-lg" />
118
+ <div className="flex items-center gap-3 rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface p-4">
119
+ <div className="ph-skeleton h-10 w-10 rounded-[var(--ph-radius-control)]" />
120
120
  <div className="min-w-0 flex-1 space-y-2">
121
121
  <div className="ph-skeleton h-3.5 w-3/4" />
122
122
  <div className="ph-skeleton h-3 w-1/2" />
@@ -152,7 +152,7 @@ export function SuiteEmptyState({
152
152
  return (
153
153
  <div
154
154
  className={cn(
155
- 'relative overflow-hidden rounded-2xl border border-ph-border bg-ph-surface text-center',
155
+ 'relative overflow-hidden rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface text-center',
156
156
  size === 'sm' ? 'px-5 py-8' : 'px-6 py-12 sm:px-10 sm:py-16',
157
157
  className,
158
158
  )}
@@ -163,7 +163,7 @@ export function SuiteEmptyState({
163
163
  </div>
164
164
  <div className="relative">
165
165
  {icon ? (
166
- <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]">
166
+ <div className="mx-auto mb-4 inline-flex items-center justify-center rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-muted p-3 shadow-[var(--ph-shadow-control)]">
167
167
  {icon}
168
168
  </div>
169
169
  ) : null}
@@ -148,7 +148,7 @@ export function SuiteUserMenu({
148
148
  className={cn(
149
149
  "[&_.ph-dropdown-trigger]:rounded-full",
150
150
  showDetails &&
151
- "min-w-0 flex-1 [&_.ph-dropdown-trigger]:w-full [&_.ph-dropdown-trigger]:justify-start [&_.ph-dropdown-trigger]:rounded-lg",
151
+ "min-w-0 flex-1 [&_.ph-dropdown-trigger]:w-full [&_.ph-dropdown-trigger]:justify-start [&_.ph-dropdown-trigger]:rounded-[var(--ph-radius-control)]",
152
152
  showSignOutAction ? undefined : className,
153
153
  )}
154
154
  data-test={dataTest}
@@ -193,7 +193,7 @@ export function SuiteUserMenu({
193
193
  data-test={`${dataTest}-visible-sign-out`}
194
194
  title="Sign out"
195
195
  aria-label="Sign out"
196
- className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-ph-mutedtext transition-colors hover:bg-ph-muted hover:text-ph-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ph-focus-ring"
196
+ className="ph-btn ph-btn-ghost ph-btn-square h-10 w-10 shrink-0 text-ph-mutedtext"
197
197
  onClick={onSignOut}
198
198
  >
199
199
  <LogOut className="h-4 w-4" aria-hidden />
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  import { readCookie, writeCookie } from '../lib/cookies';
15
15
  import { SUITE_THEME_CHANNEL, SUITE_THEME_COOKIE } from '../lib/theme-cookie';
16
+ import { trackSuiteEvent } from '../../lib/suite-analytics';
16
17
 
17
18
  export type SuiteTheme = 'system' | 'light' | 'dark';
18
19
  export type SuiteResolvedTheme = 'light' | 'dark';
@@ -271,6 +272,7 @@ export function SuiteThemeProvider({
271
272
  }
272
273
  writeSharedTheme(next);
273
274
  broadcastTheme(next);
275
+ trackSuiteEvent('Theme Updated', { theme: next, scope: 'suite' });
274
276
  },
275
277
  [config],
276
278
  );
@@ -30,7 +30,7 @@ export function TodayCalibrating({
30
30
  return (
31
31
  <div
32
32
  className={cn(
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
+ 'suite-auth-handoff relative flex min-h-[14rem] flex-col items-center justify-center overflow-hidden rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface px-5 py-8 text-center',
34
34
  className,
35
35
  )}
36
36
  data-test="today-calibrating"
@@ -39,7 +39,7 @@ export function TodayCalibrating({
39
39
  className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_50%_35%,rgba(251,191,36,0.1),transparent_55%)]"
40
40
  aria-hidden
41
41
  />
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">
42
+ <div className="relative mb-5 flex h-14 w-14 items-center justify-center rounded-[var(--ph-radius-surface)] border border-ph-border bg-ph-surface text-ph-ink shadow-[var(--ph-shadow-control)]">
43
43
  <Sun className="h-8 w-8" strokeWidth={2} aria-hidden />
44
44
  </div>
45
45
  <h2 className="relative text-base font-semibold tracking-tight text-ph-ink sm:text-lg md:text-xl">
@@ -56,7 +56,7 @@ export function TodayCalibrating({
56
56
  type="button"
57
57
  onClick={onRetry}
58
58
  disabled={retrying}
59
- 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"
59
+ className="ph-btn ph-btn-secondary relative mt-6"
60
60
  >
61
61
  {retrying ? (
62
62
  <Spinner size="sm" className={retrySpinnerClassName} />