@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
@@ -0,0 +1,760 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, ComponentType, ButtonHTMLAttributes, CSSProperties, ElementType, PointerEvent } from 'react';
3
+ import { ClassValue } from 'clsx';
4
+ export { m } from 'motion/react';
5
+
6
+ /** Full-bleed theme wash + soft clouds; uses ph-* tokens from each app's theme. */
7
+ declare function TodayPageFrame({ children }: {
8
+ children: ReactNode;
9
+ }): react.JSX.Element;
10
+
11
+ type LucideIcon = react.ComponentType<react.SVGProps<SVGSVGElement>>;
12
+ type SuiteAppSlug$1 = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
13
+ declare function sourceToSuiteApp(source: string): SuiteAppSlug$1;
14
+ declare function suiteAppLabel(source: string): string;
15
+ /**
16
+ * Small line icon for list rows — the app's suite glyph with its brand
17
+ * accent on a muted base. Not the branded app tile (see SuiteAppIcon).
18
+ */
19
+ declare function SourceAppGlyph({ source, className, }: {
20
+ source: string;
21
+ className?: string;
22
+ }): react.JSX.Element;
23
+ declare function SuiteAppStrip({ className, icons, }: {
24
+ className?: string;
25
+ icons?: Partial<Record<SuiteAppSlug$1, ReactNode>>;
26
+ }): react.JSX.Element;
27
+ declare function AnimatedSun({ className }: {
28
+ className?: string;
29
+ }): react.JSX.Element;
30
+ declare function AnimatedMoon({ className }: {
31
+ className?: string;
32
+ }): react.JSX.Element;
33
+ /** Sun through the day, moon from 18:00 to 06:00. */
34
+ declare function TodayTimeIcon({ className }: {
35
+ className?: string;
36
+ }): react.JSX.Element;
37
+ declare function TodayTopBar({ refreshing, loading, onRefresh, appIcon, appName, }: {
38
+ refreshing?: boolean;
39
+ loading?: boolean;
40
+ onRefresh: () => void;
41
+ appIcon?: ReactNode;
42
+ appName?: string;
43
+ }): react.JSX.Element;
44
+ declare function getTodayGreeting(user?: {
45
+ first_name?: string;
46
+ name?: string;
47
+ email?: string;
48
+ }, stats?: {
49
+ taskCount?: number;
50
+ overdueCount?: number;
51
+ hours?: number;
52
+ }): string;
53
+ declare function TodayHero({ message, }: {
54
+ message?: ReactNode;
55
+ }): react.JSX.Element;
56
+ declare function TodaySection({ icon: Icon, iconClassName, title, description, action, testId, children, }: {
57
+ icon: LucideIcon;
58
+ iconClassName?: string;
59
+ title: string;
60
+ description?: string;
61
+ action?: ReactNode;
62
+ testId?: string;
63
+ children: ReactNode;
64
+ }): react.JSX.Element;
65
+ declare function TodayPanel({ children, className, }: {
66
+ children: ReactNode;
67
+ className?: string;
68
+ }): react.JSX.Element;
69
+ declare function TodayEmptyState({ title, description, }: {
70
+ title: string;
71
+ description?: string;
72
+ }): react.JSX.Element;
73
+
74
+ /**
75
+ * Structural types for theme primitives each app injects from
76
+ * `@xenide-io/the-old-ui-theme/ui` (the shared package must not depend on it).
77
+ * The real components satisfy these shapes, so apps can pass them directly.
78
+ */
79
+ interface SuiteDropdownMenuProps {
80
+ trigger: ReactNode;
81
+ children: ReactNode;
82
+ 'aria-label': string;
83
+ align?: 'start' | 'center' | 'end';
84
+ side?: 'top' | 'right' | 'bottom' | 'left';
85
+ sideOffset?: number;
86
+ className?: string;
87
+ panelClassName?: string;
88
+ open?: boolean;
89
+ onOpenChange?: (open: boolean) => void;
90
+ }
91
+ type SuiteDropdownMenuComponent = ComponentType<SuiteDropdownMenuProps>;
92
+ type SuiteDropdownItemComponent = ComponentType<ButtonHTMLAttributes<HTMLButtonElement>>;
93
+ interface SuiteCommandItem {
94
+ id: string;
95
+ label: string;
96
+ shortcut?: string;
97
+ icon?: ReactNode;
98
+ onSelect: () => void;
99
+ }
100
+ type SuiteCommandPaletteComponent = ComponentType<{
101
+ items: SuiteCommandItem[];
102
+ isOpen: boolean;
103
+ onClose: () => void;
104
+ placeholder?: string;
105
+ className?: string;
106
+ }>;
107
+ /** App-branded spinner (each app has its own AppSpinner design). */
108
+ type SuiteSpinnerComponent = ComponentType<{
109
+ size?: 'sm' | 'md' | 'lg';
110
+ className?: string;
111
+ label?: string;
112
+ }>;
113
+
114
+ declare function appSwitcherTriggerClass(open: boolean, collapsed?: boolean): string;
115
+ declare function appSwitcherMarkClass(): string;
116
+ /** No sticky Radix highlight while waiting for cross-app redirect. */
117
+ declare function appSwitcherMenuItemClass(): string;
118
+ declare function AppSwitcherMark({ children, collapsed, }: {
119
+ children: ReactNode;
120
+ collapsed?: boolean;
121
+ }): react.JSX.Element;
122
+ declare function AppSwitcherChevron({ open, collapsed, }: {
123
+ open: boolean;
124
+ collapsed?: boolean;
125
+ }): react.JSX.Element;
126
+ interface SuiteAppEntry {
127
+ slug: string;
128
+ name: string;
129
+ description: string;
130
+ icon: string;
131
+ }
132
+ /**
133
+ * Suite app switcher dropdown. Apps inject their brand mark/title, the
134
+ * visible app list, the cross-app navigation handler, and the theme
135
+ * dropdown primitives.
136
+ */
137
+ declare function AppSwitcher({ apps, currentApp, onSelect, mark, title, collapsed, dropdownMenu: DropdownMenu, dropdownItem: DropdownItem, }: {
138
+ apps: SuiteAppEntry[];
139
+ currentApp: string;
140
+ onSelect: (app: SuiteAppEntry) => void;
141
+ mark: ReactNode;
142
+ title: ReactNode;
143
+ collapsed?: boolean;
144
+ dropdownMenu: SuiteDropdownMenuComponent;
145
+ dropdownItem: SuiteDropdownItemComponent;
146
+ }): react.JSX.Element;
147
+
148
+ /** Cross-app (suite) notification from `/api/notifications/` — shared across ShellStack apps. */
149
+ interface SuiteNotification {
150
+ id: string;
151
+ title: string;
152
+ body: string;
153
+ href: string;
154
+ kind: string;
155
+ source_app: string;
156
+ read_at: string | null;
157
+ created_at: string;
158
+ workspace: string | null;
159
+ organisation: string | null;
160
+ }
161
+ interface SuiteNotificationsResponse {
162
+ notifications: SuiteNotification[];
163
+ unread_count: number;
164
+ }
165
+ declare function SuiteNotificationBell({ fetchNotifications, markRead, markAllRead, dropdownMenu: DropdownMenu, cacheKey, }: {
166
+ fetchNotifications: () => Promise<SuiteNotificationsResponse>;
167
+ markRead: (id: string) => Promise<unknown>;
168
+ markAllRead: () => Promise<unknown>;
169
+ dropdownMenu: SuiteDropdownMenuComponent;
170
+ /** sessionStorage key for stale-while-revalidate; `null` disables caching. */
171
+ cacheKey?: string | null;
172
+ }): react.JSX.Element;
173
+
174
+ /**
175
+ * Cmd/Ctrl+K command palette host. Apps build the item list and inject the
176
+ * theme CommandPalette primitive.
177
+ */
178
+ declare function CommandPaletteHost({ items, isAuthenticated, placeholder, commandPalette: CommandPalette, }: {
179
+ items: SuiteCommandItem[];
180
+ isAuthenticated?: boolean;
181
+ placeholder?: string;
182
+ commandPalette: SuiteCommandPaletteComponent;
183
+ }): react.JSX.Element | null;
184
+
185
+ /** Command K — mount after idle so first paint stays light. */
186
+ declare function DeferredChrome({ children }: {
187
+ children: ReactNode;
188
+ }): react.JSX.Element | null;
189
+
190
+ /**
191
+ * Suite mobile navigation drawer. Backdrop fade + panel slide, Escape to
192
+ * close, body scroll-lock and focus hand-off while open. Nav content stays
193
+ * app-side via `children`.
194
+ */
195
+ declare function SuiteMobileDrawer({ open, onClose, children, side, showCloseButton, closeLabel, backdropLabel, dialogLabel, durationMs, dataTest, panelDataTest, panelClassName, }: {
196
+ open: boolean;
197
+ onClose: () => void;
198
+ children: ReactNode;
199
+ /** Edge the panel slides in from. */
200
+ side?: 'left' | 'right';
201
+ /** Standard close row (h-14, trailing X button) above the content. */
202
+ showCloseButton?: boolean;
203
+ closeLabel?: string;
204
+ backdropLabel?: string;
205
+ /** Accessible name for the dialog panel. */
206
+ dialogLabel?: string;
207
+ /** Slide/fade duration; also the close-transition unmount delay. */
208
+ durationMs?: number;
209
+ dataTest?: string;
210
+ panelDataTest?: string;
211
+ /** Panel surface overrides (e.g. `bg-ph-canvas`). */
212
+ panelClassName?: string;
213
+ }): react.JSX.Element | null;
214
+
215
+ type SuiteMenuIcon = ComponentType<{
216
+ className?: string;
217
+ 'aria-hidden'?: boolean | 'true' | 'false';
218
+ }>;
219
+ /**
220
+ * Suite mobile app bar — the standardized top of every app on phone/tablet.
221
+ * Fixed slot order across the suite: [menu] [brand/context] … [actions].
222
+ * Translucent + blurred (suite-skin.css `.suite-app-bar`), safe-area aware,
223
+ * 44px minimum touch targets. Desktop chrome (rail/top bar) stays app-side.
224
+ */
225
+ declare function SuiteMobileHeader({ onMenuClick, menuIcon: MenuIcon, title, actions, menuLabel, menuDataTest, dataTest, className, }: {
226
+ /** Optional — omit to drop the hamburger (bottom-nav apps don't need one). */
227
+ onMenuClick?: () => void;
228
+ /** Leading button glyph — defaults to Menu; browse-style drawers can swap it. */
229
+ menuIcon?: SuiteMenuIcon;
230
+ /** Brand/context slot — app mark + title or app switcher. */
231
+ title?: ReactNode;
232
+ /** Right-hand slot — search, bell, avatar (keep this order). */
233
+ actions?: ReactNode;
234
+ menuLabel?: string;
235
+ menuDataTest?: string;
236
+ dataTest?: string;
237
+ className?: string;
238
+ }): react.JSX.Element;
239
+
240
+ interface SuiteUserMenuProps {
241
+ name?: string | null;
242
+ email?: string | null;
243
+ image?: string | null;
244
+ /** Destination for the "Profile settings" item. */
245
+ settingsHref: string;
246
+ onSignOut: () => void;
247
+ /** Fallback letter(s) when there is no avatar image. */
248
+ fallbackInitials?: string;
249
+ dataTest?: string;
250
+ className?: string;
251
+ }
252
+ /**
253
+ * Suite account context menu — one tap on the avatar opens a small menu
254
+ * with profile settings and sign out. Used in the mobile app bar of every
255
+ * app so account actions behave identically across the suite (no dedicated
256
+ * logout icon cluttering the mobile chrome).
257
+ */
258
+ declare function SuiteUserMenu({ name, email, image, settingsHref, onSignOut, fallbackInitials, dataTest, className, }: SuiteUserMenuProps): react.JSX.Element;
259
+
260
+ /** Any icon component that accepts sizing/stroke props (Lucide, theme icons). */
261
+ type SuiteNavIcon = ComponentType<{
262
+ className?: string;
263
+ strokeWidth?: number | string;
264
+ 'aria-hidden'?: boolean | 'true' | 'false';
265
+ }>;
266
+ /**
267
+ * Suite bottom navigation — the mobile/tablet primary nav, identical in
268
+ * every app. Hick's Law: 5 items maximum (dev warning beyond that).
269
+ * Visuals come from suite-skin.css (`suite-bottom-nav`, `__pill`) so the
270
+ * translucent blur, safe-area padding, and press physics stay in sync
271
+ * across the suite; per-item content stays app-side via `items`.
272
+ */
273
+ interface SuiteBottomNavItem {
274
+ href: string;
275
+ label: string;
276
+ icon: SuiteNavIcon;
277
+ active?: boolean;
278
+ dataTest?: string;
279
+ }
280
+ declare function SuiteBottomNav({ items, ariaLabel, dataTest, className, }: {
281
+ items: readonly SuiteBottomNavItem[];
282
+ ariaLabel?: string;
283
+ dataTest?: string;
284
+ className?: string;
285
+ }): react.JSX.Element;
286
+
287
+ /**
288
+ * The single source of truth for the ShellStack app family — used by the
289
+ * app switcher, mobile bottom nav, launchpad, and cross-app navigation.
290
+ * Slugs double as accent keys (see icons/app-accents.ts) and data-test
291
+ * suffixes (`switch-app-${slug}`, `suite-nav-${slug}`).
292
+ */
293
+ type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
294
+ interface SuiteAppDefinition {
295
+ slug: SuiteAppSlug;
296
+ name: string;
297
+ description: string;
298
+ /** Public asset path — every app hosts all four icons. */
299
+ icon: string;
300
+ /** Landing route used after cross-app SSO redirect. */
301
+ landing: string;
302
+ /** Env var carrying the app's base URL (dev fallback included). */
303
+ baseUrlEnv: string;
304
+ baseUrlFallback: string;
305
+ }
306
+ /** Display order — launchpad first, then by daily workflow. */
307
+ declare const SUITE_APPS: readonly SuiteAppDefinition[];
308
+ declare const SUITE_APP_MAP: Readonly<Record<SuiteAppSlug, SuiteAppDefinition>>;
309
+ /** Resolve an app's base URL: env var first, dev fallback second. */
310
+ declare function suiteAppBaseUrl(slug: SuiteAppSlug): string;
311
+
312
+ /**
313
+ * Today-page loading/empty calibrating state. Apps inject their branded
314
+ * AppSpinner; `variant` picks the ornamentation style:
315
+ * - `ring` — large spinner ring + ping behind the sun (TurtleTime/ShellStack)
316
+ * - `pulse` — pulsing sun only (Tides/Kraken)
317
+ */
318
+ declare function TodayCalibrating({ onRetry, retrying, className, spinner: Spinner, variant, retrySpinnerClassName, }: {
319
+ onRetry?: () => void;
320
+ retrying?: boolean;
321
+ className?: string;
322
+ spinner: SuiteSpinnerComponent;
323
+ variant?: 'ring' | 'pulse';
324
+ retrySpinnerClassName?: string;
325
+ }): react.JSX.Element;
326
+
327
+ type SuiteTheme = 'system' | 'light' | 'dark';
328
+ type SuiteResolvedTheme = 'light' | 'dark';
329
+ interface SuiteThemeConfig {
330
+ /** localStorage key holding the stored preference. */
331
+ storageKey: string;
332
+ /** `data-theme` attribute value for the light theme. */
333
+ lightThemeId: string;
334
+ /** `data-theme` attribute value for the dark theme. */
335
+ darkThemeId: string;
336
+ /**
337
+ * Resolved theme to assume when `matchMedia` is unavailable
338
+ * (server render / pre-hydration).
339
+ */
340
+ fallbackTheme: SuiteResolvedTheme;
341
+ /**
342
+ * Optional `<meta id="theme-color-meta">` colours, synced whenever the
343
+ * theme is (re)applied after mount. Omit both to leave the meta tag
344
+ * untouched.
345
+ */
346
+ themeColorLight?: string;
347
+ themeColorDark?: string;
348
+ }
349
+ interface SuiteThemeContextValue {
350
+ theme: SuiteTheme;
351
+ resolvedTheme: SuiteResolvedTheme;
352
+ setTheme: (theme: SuiteTheme) => void;
353
+ toggleTheme: () => void;
354
+ }
355
+ /**
356
+ * Theme provider shared by suite apps. Each app's root layout runs a small
357
+ * boot script that applies `data-theme` before hydration; this provider
358
+ * adopts that DOM state on mount and owns every post-hydration change (user
359
+ * picks a theme, or the OS preference flips while set to `system`).
360
+ *
361
+ * Apps wrap this with their own `ThemeProvider`/`useTheme` module that pins
362
+ * the storage key, theme ids and meta colours.
363
+ */
364
+ declare function SuiteThemeProvider({ config, children, }: {
365
+ config: SuiteThemeConfig;
366
+ children: ReactNode;
367
+ }): react.JSX.Element;
368
+ declare function useSuiteTheme(): SuiteThemeContextValue;
369
+
370
+ interface SuiteAiPreset {
371
+ label: string;
372
+ prompt: string;
373
+ }
374
+ /**
375
+ * Thin ShellStack AI slide-over shared by suite apps.
376
+ *
377
+ * One-shot completions via the shared `/api/ai/complete` endpoint. Apps
378
+ * inject their API helpers, preset prompts, brand icon and spinner. Renders
379
+ * its own floating launcher so it can be mounted once in the authenticated
380
+ * app shell.
381
+ */
382
+ declare function SuiteAiPanel({ presets, emptyState, fetchStatus, complete, brandIcon: BrandIcon, spinner: Spinner, }: {
383
+ presets: SuiteAiPreset[];
384
+ /** Idle-state hint copy shown before the first request. */
385
+ emptyState: ReactNode;
386
+ fetchStatus: () => Promise<{
387
+ configured: boolean;
388
+ }>;
389
+ complete: (params: {
390
+ prompt: string;
391
+ }) => Promise<{
392
+ text: string;
393
+ }>;
394
+ brandIcon: ComponentType<{
395
+ className?: string;
396
+ }>;
397
+ spinner: ComponentType<{
398
+ className?: string;
399
+ }>;
400
+ }): react.JSX.Element;
401
+
402
+ /**
403
+ * Suite icon glyph definitions — pure path data, no React.
404
+ *
405
+ * Shared geometric grammar (lucide-compatible):
406
+ * - 24×24 viewBox
407
+ * - 2px strokes, round caps/joins (set by the renderer)
408
+ * - `fill="none" stroke="currentColor"` unless `filled: true`
409
+ *
410
+ * Elements flagged `accent: true` are rendered inside a group coloured by
411
+ * `var(--suite-icon-accent, currentColor)` — `<SuiteIcon accent="…">` sets the
412
+ * variable from `app-accents.ts`; without an accent they inherit the text
413
+ * colour like any lucide icon.
414
+ */
415
+ type SuiteGlyphElement = {
416
+ kind: 'path';
417
+ d: string;
418
+ accent?: boolean;
419
+ filled?: boolean;
420
+ } | {
421
+ kind: 'circle';
422
+ cx: number;
423
+ cy: number;
424
+ r: number;
425
+ accent?: boolean;
426
+ filled?: boolean;
427
+ } | {
428
+ kind: 'rect';
429
+ x: number;
430
+ y: number;
431
+ width: number;
432
+ height: number;
433
+ rx?: number;
434
+ accent?: boolean;
435
+ filled?: boolean;
436
+ };
437
+ interface SuiteGlyph {
438
+ /** One-line description of what the mark depicts (used in docs/sheets). */
439
+ description: string;
440
+ elements: SuiteGlyphElement[];
441
+ /**
442
+ * The accent group carries a subtle CSS animation (today-sun rays only).
443
+ * Renderers must honour prefers-reduced-motion; see suite-icon.tsx.
444
+ */
445
+ animatedAccent?: boolean;
446
+ }
447
+ type SuiteIconName = 'timer-shell' | 'kanban-wave' | 'squid-doc' | 'stack-hex' | 'crew-bot' | 'workspace' | 'organisation' | 'integration-plug' | 'suite-grid' | 'bell-wave' | 'today-sun';
448
+ declare const SUITE_GLYPHS: Record<SuiteIconName, SuiteGlyph>;
449
+ declare const SUITE_ICON_NAMES: SuiteIconName[];
450
+
451
+ /**
452
+ * Per-app brand colours for the suite icon system — the single place hexes
453
+ * live. `accent` tints a glyph's accent layer; `tile`/`onTile`/
454
+ * `onTileAccent` drive `<SuiteAppIcon>` (the programmatic favicon-style
455
+ * tile + glyph mark).
456
+ */
457
+ type SuiteAccentSlug = 'turtletime' | 'tides' | 'kraken' | 'shellstack' | 'crew';
458
+ interface SuiteAppAccent {
459
+ /** Display name (default aria label for app marks). */
460
+ label: string;
461
+ /** Brand accent — tints glyph accent layers via `--suite-icon-accent`. */
462
+ accent: string;
463
+ /** App tile background. */
464
+ tile: string;
465
+ /** Base glyph colour on the tile. */
466
+ onTile: string;
467
+ /** Accent-layer glyph colour on the tile. */
468
+ onTileAccent: string;
469
+ }
470
+ declare const APP_ACCENTS: Record<SuiteAccentSlug, SuiteAppAccent>;
471
+ /** Which glyph represents each app. */
472
+ declare const APP_GLYPHS: Record<SuiteAccentSlug, SuiteIconName>;
473
+
474
+ interface SuiteIconProps {
475
+ name: SuiteIconName;
476
+ /** px size; a Tailwind sizing class on `className` also works. */
477
+ size?: number | string;
478
+ /** App slug — tints the glyph's accent layer with the app's brand hex. */
479
+ accent?: SuiteAccentSlug;
480
+ strokeWidth?: number;
481
+ /** Only today-sun animates; set false to freeze it. Reduced-motion safe. */
482
+ animated?: boolean;
483
+ /** Accessible label. Without it the icon is `aria-hidden` (decorative). */
484
+ label?: string;
485
+ className?: string;
486
+ style?: CSSProperties;
487
+ }
488
+ /**
489
+ * Suite icon — hand-drawn glyphs on the lucide grammar (24×24, 2px strokes,
490
+ * round caps/joins). Strokes inherit `currentColor`, so ph-* text tokens just
491
+ * work; `accent` adds a per-app brand tint to the glyph's accent layer.
492
+ */
493
+ declare function SuiteIcon({ name, size, accent, strokeWidth, animated, label, className, style, }: SuiteIconProps): react.JSX.Element;
494
+
495
+ declare const SIZE_PRESETS: {
496
+ readonly sm: 28;
497
+ readonly md: 36;
498
+ readonly lg: 44;
499
+ };
500
+ interface SuiteAppIconProps {
501
+ app: SuiteAccentSlug;
502
+ /** px size, or a preset (sm 28 / md 36 / lg 44). */
503
+ size?: number | keyof typeof SIZE_PRESETS;
504
+ /** Accessible label; defaults to decorative (`aria-hidden`). */
505
+ label?: string;
506
+ className?: string;
507
+ style?: CSSProperties;
508
+ }
509
+ /**
510
+ * An app's simplified mark — brand tile + its suite glyph, drawn
511
+ * programmatically at any size (no cross-app <img> favicon lookups).
512
+ * Colours come from app-accents.ts.
513
+ */
514
+ declare function SuiteAppIcon({ app, size, label, className, style, }: SuiteAppIconProps): react.JSX.Element;
515
+
516
+ /** Mount non-critical chrome after the browser is idle (or a short timeout). */
517
+ declare function useIdleMount(timeoutMs?: number): boolean;
518
+
519
+ declare function cn(...inputs: ClassValue[]): string;
520
+
521
+ /**
522
+ * Suite motion foundation.
523
+ *
524
+ * `LazyMotion` + `domAnimation` keeps the animation runtime (~15 kB) out of
525
+ * the initial bundle — features load with the first animated component.
526
+ * `strict` guarantees only the tree-shakeable `m` component is used inside
527
+ * the provider (never the heavier `motion` component).
528
+ *
529
+ * Usage:
530
+ * <SuiteMotionProvider>
531
+ * <m.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
532
+ * </SuiteMotionProvider>
533
+ *
534
+ * Prefer CSS transitions for micro-interactions (hover/press/colour) and
535
+ * reach for `m` only when you need springs, gestures, or layout animation.
536
+ */
537
+ declare function SuiteMotionProvider({ children }: {
538
+ children: ReactNode;
539
+ }): react.JSX.Element;
540
+
541
+ /**
542
+ * Spring presets — the JS counterparts of the `ease-spring-*` timing
543
+ * functions defined in each app's tailwind config. Use these for entrances,
544
+ * sheets, drawers, and press feedback so JS and CSS motion feel identical.
545
+ * All respect `prefers-reduced-motion` when paired with
546
+ * `MotionConfig reducedMotion="user"` or CSS `motion-reduce:` variants.
547
+ */
548
+ declare const SUITE_SPRINGS: {
549
+ /** Snappy panel/sheet entrances — small overshoot. */
550
+ readonly fast: {
551
+ readonly type: "spring";
552
+ readonly stiffness: 400;
553
+ readonly damping: 32;
554
+ readonly mass: 0.8;
555
+ };
556
+ /** Gentle scrims, fades, list staggers. */
557
+ readonly subtle: {
558
+ readonly type: "spring";
559
+ readonly stiffness: 260;
560
+ readonly damping: 26;
561
+ readonly mass: 1;
562
+ };
563
+ /** Press/tap feedback — quick settle, no wobble. */
564
+ readonly press: {
565
+ readonly type: "spring";
566
+ readonly stiffness: 600;
567
+ readonly damping: 35;
568
+ readonly mass: 0.5;
569
+ };
570
+ };
571
+ type SuiteSpringName = keyof typeof SUITE_SPRINGS;
572
+
573
+ declare const WIDTHS: {
574
+ readonly full: "max-w-none";
575
+ readonly wide: "max-w-7xl";
576
+ readonly content: "max-w-6xl";
577
+ readonly narrow: "max-w-4xl";
578
+ };
579
+ type SuitePageWidth = keyof typeof WIDTHS;
580
+ interface SuiteBreadcrumbsProps {
581
+ items: {
582
+ label: ReactNode;
583
+ href?: string;
584
+ }[];
585
+ className?: string;
586
+ }
587
+ /** Minimal slash-separated breadcrumb line. */
588
+ declare function SuiteBreadcrumbs({ items, className }: SuiteBreadcrumbsProps): react.JSX.Element;
589
+ declare function SuitePage({ children, width, inset, className, as: Component, dataTest, id, role, 'aria-live': ariaLive, }: {
590
+ children: ReactNode;
591
+ width?: SuitePageWidth;
592
+ inset?: boolean;
593
+ className?: string;
594
+ as?: ElementType;
595
+ dataTest?: string;
596
+ id?: string;
597
+ role?: string;
598
+ 'aria-live'?: React.AriaAttributes['aria-live'];
599
+ }): react.JSX.Element;
600
+ declare function SuitePageHeader({ title, description, eyebrow, actions, className, titleClassName, dataTest,
601
+ /** Optional icon rendered before the title. */
602
+ icon,
603
+ /** Use the animated sun/moon icon from the Today page. */
604
+ todayIcon, todayIconClassName,
605
+ /** Small badge/pill next to the title (e.g. "Beta", org name). */
606
+ badge,
607
+ /** Breadcrumb line rendered above the title. */
608
+ breadcrumb, }: {
609
+ title: ReactNode;
610
+ description?: ReactNode;
611
+ eyebrow?: ReactNode;
612
+ actions?: ReactNode;
613
+ className?: string;
614
+ titleClassName?: string;
615
+ dataTest?: string;
616
+ icon?: ReactNode;
617
+ todayIcon?: boolean;
618
+ todayIconClassName?: string;
619
+ badge?: ReactNode;
620
+ breadcrumb?: ReactNode;
621
+ }): react.JSX.Element;
622
+ declare function SuiteToolbar({ children, className, label, dataTest, }: {
623
+ children: ReactNode;
624
+ className?: string;
625
+ label?: string;
626
+ dataTest?: string;
627
+ }): react.JSX.Element;
628
+ declare function SuiteTabList({ children, className, label, dataTest, }: {
629
+ children: ReactNode;
630
+ className?: string;
631
+ label: string;
632
+ dataTest?: string;
633
+ }): react.JSX.Element;
634
+ declare function SuiteSectionHeader({ title, description, action, className, }: {
635
+ title: ReactNode;
636
+ description?: ReactNode;
637
+ action?: ReactNode;
638
+ className?: string;
639
+ }): react.JSX.Element;
640
+
641
+ interface SuiteSkeletonProps {
642
+ className?: string;
643
+ lines?: number;
644
+ circle?: boolean;
645
+ width?: string;
646
+ }
647
+ /**
648
+ * Shared skeleton using the-old-ui-theme `ph-skeleton` shimmer class.
649
+ * The shimmer animation is defined in the theme, so this component is
650
+ * framework-agnostic and works in any app that imports the theme.
651
+ */
652
+ declare function SuiteSkeleton({ className, lines, circle, width, }: SuiteSkeletonProps): react.JSX.Element;
653
+ declare function SuiteSkeletonCard({ className, rows, header, }: {
654
+ className?: string;
655
+ rows?: number;
656
+ header?: boolean;
657
+ }): react.JSX.Element;
658
+ declare function SuiteSkeletonList({ className, items, }: {
659
+ className?: string;
660
+ items?: number;
661
+ }): react.JSX.Element;
662
+ interface SuiteEmptyStateProps {
663
+ icon?: ReactNode;
664
+ title: string;
665
+ description?: ReactNode;
666
+ action?: ReactNode;
667
+ className?: string;
668
+ size?: 'sm' | 'md';
669
+ }
670
+ /**
671
+ * Branded empty state with a soft gradient orb and clear action affordance.
672
+ * Uses the app icon when available so empty states feel native to each app.
673
+ */
674
+ declare function SuiteEmptyState({ icon, title, description, action, className, size, }: SuiteEmptyStateProps): react.JSX.Element;
675
+
676
+ interface SuiteSettingsNavItem {
677
+ label: string;
678
+ href: string;
679
+ icon: ComponentType<{
680
+ className?: string;
681
+ 'aria-hidden'?: boolean;
682
+ }> | ReactNode;
683
+ testId?: string;
684
+ }
685
+ interface SuiteSettingsMobileNavProps {
686
+ items: SuiteSettingsNavItem[];
687
+ activeHref: string;
688
+ onSelect: (href: string) => void;
689
+ dataTest?: string;
690
+ }
691
+ /**
692
+ * Horizontal scrollable settings nav for mobile.
693
+ * Replaces a native <select> with icon+label pills that are easier to scan,
694
+ * meet 44px touch targets, and preserve visual hierarchy (Law of UX: Hick's Law,
695
+ * Recognition over Recall).
696
+ */
697
+ declare function SuiteSettingsMobileNav({ items, activeHref, onSelect, dataTest, }: SuiteSettingsMobileNavProps): react.JSX.Element;
698
+
699
+ type CollapsedNode = ReactNode | ((collapsed: boolean) => ReactNode);
700
+ interface SuiteSidebarNavItem {
701
+ href: string;
702
+ label: string;
703
+ icon: SuiteNavIcon;
704
+ active?: boolean;
705
+ onClick?: () => void;
706
+ badge?: ReactNode;
707
+ /** Optional colour class for the nav icon. */
708
+ iconClassName?: string;
709
+ }
710
+ interface SuiteSidebarProps {
711
+ /** Optional custom app switcher node, or a function of collapsed state. */
712
+ appSwitcher?: CollapsedNode;
713
+ /** Workspace/org/project switcher rendered below the app switcher. */
714
+ contextSwitcher: CollapsedNode;
715
+ navItems: SuiteSidebarNavItem[];
716
+ /** Optional secondary nav / tree rendered below primary nav. */
717
+ secondaryNav?: ReactNode;
718
+ userMenu: CollapsedNode;
719
+ notificationBell?: CollapsedNode;
720
+ /** Optional extra footer content placed next to the user menu. */
721
+ footerExtras?: CollapsedNode;
722
+ collapsed?: boolean;
723
+ className?: string;
724
+ /** Use the richer surface background instead of canvas. */
725
+ surface?: boolean;
726
+ }
727
+ /**
728
+ * Standardised ShellStack sidebar. Matches the original TurtleTime layout
729
+ * so the chrome feels familiar across all apps:
730
+ * - app switcher + notification bell in a compact header
731
+ * - workspace switcher at the top of the nav body
732
+ * - primary nav items styled like the original NavLink
733
+ * - avatar + optional logout in the footer
734
+ */
735
+ declare function SuiteSidebar({ appSwitcher, contextSwitcher, navItems, secondaryNav, userMenu, notificationBell, footerExtras, collapsed, className, surface, }: SuiteSidebarProps): react.JSX.Element;
736
+
737
+ interface SuiteAppLayoutProps {
738
+ sidebar: ReactNode;
739
+ children: ReactNode;
740
+ mobileHeader?: ReactNode;
741
+ bottomNav?: ReactNode;
742
+ /** Desktop sidebar width in px. */
743
+ sidebarWidth?: number;
744
+ /** True when the sidebar is collapsed to the icon rail. */
745
+ collapsed?: boolean;
746
+ /** Drag this to resize the sidebar. */
747
+ onStartResize?: (e: PointerEvent<HTMLDivElement>) => void;
748
+ /** Double-click to reset sidebar width. */
749
+ onResetWidth?: () => void;
750
+ className?: string;
751
+ }
752
+ /**
753
+ * Responsive ShellStack app shell. Desktop: resizable fixed sidebar +
754
+ * scrollable content with an optional drag handle. Mobile/tablet: mobile
755
+ * header + content + bottom nav. This is the single layout primitive every
756
+ * app should use.
757
+ */
758
+ declare function SuiteAppLayout({ sidebar, children, mobileHeader, bottomNav, sidebarWidth, collapsed, onStartResize, onResetWidth, className, }: SuiteAppLayoutProps): react.JSX.Element;
759
+
760
+ export { APP_ACCENTS, APP_GLYPHS, AnimatedMoon, AnimatedSun, AppSwitcher, AppSwitcherChevron, AppSwitcherMark, CommandPaletteHost, DeferredChrome, SUITE_APPS, SUITE_APP_MAP, SUITE_GLYPHS, SUITE_ICON_NAMES, SUITE_SPRINGS, SourceAppGlyph, type SuiteAccentSlug, SuiteAiPanel, type SuiteAiPreset, type SuiteAppAccent, type SuiteAppDefinition, type SuiteAppEntry, SuiteAppIcon, type SuiteAppIconProps, SuiteAppLayout, type SuiteAppLayoutProps, type SuiteAppSlug, SuiteAppStrip, SuiteBottomNav, type SuiteBottomNavItem, SuiteBreadcrumbs, type SuiteBreadcrumbsProps, type SuiteCommandItem, type SuiteCommandPaletteComponent, type SuiteDropdownItemComponent, type SuiteDropdownMenuComponent, type SuiteDropdownMenuProps, SuiteEmptyState, type SuiteGlyph, type SuiteGlyphElement, SuiteIcon, type SuiteIconName, type SuiteIconProps, SuiteMobileDrawer, SuiteMobileHeader, SuiteMotionProvider, type SuiteNotification, SuiteNotificationBell, type SuiteNotificationsResponse, SuitePage, SuitePageHeader, type SuitePageWidth, type SuiteResolvedTheme, SuiteSectionHeader, SuiteSettingsMobileNav, type SuiteSettingsNavItem, SuiteSidebar, type SuiteSidebarNavItem, SuiteSkeleton, SuiteSkeletonCard, SuiteSkeletonList, type SuiteSpinnerComponent, type SuiteSpringName, SuiteTabList, type SuiteTheme, type SuiteThemeConfig, type SuiteThemeContextValue, SuiteThemeProvider, SuiteToolbar, SuiteUserMenu, type SuiteUserMenuProps, TodayCalibrating, TodayEmptyState, TodayHero, TodayPageFrame, TodayPanel, TodaySection, TodayTimeIcon, TodayTopBar, appSwitcherMarkClass, appSwitcherMenuItemClass, appSwitcherTriggerClass, cn, getTodayGreeting, sourceToSuiteApp, suiteAppBaseUrl, suiteAppLabel, useIdleMount, useSuiteTheme };