@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,203 @@
1
+ /**
2
+ * Suite icon glyph definitions — pure path data, no React.
3
+ *
4
+ * Shared geometric grammar (lucide-compatible):
5
+ * - 24×24 viewBox
6
+ * - 2px strokes, round caps/joins (set by the renderer)
7
+ * - `fill="none" stroke="currentColor"` unless `filled: true`
8
+ *
9
+ * Elements flagged `accent: true` are rendered inside a group coloured by
10
+ * `var(--suite-icon-accent, currentColor)` — `<SuiteIcon accent="…">` sets the
11
+ * variable from `app-accents.ts`; without an accent they inherit the text
12
+ * colour like any lucide icon.
13
+ */
14
+
15
+ export type SuiteGlyphElement =
16
+ | {
17
+ kind: 'path';
18
+ d: string;
19
+ accent?: boolean;
20
+ filled?: boolean;
21
+ }
22
+ | {
23
+ kind: 'circle';
24
+ cx: number;
25
+ cy: number;
26
+ r: number;
27
+ accent?: boolean;
28
+ filled?: boolean;
29
+ }
30
+ | {
31
+ kind: 'rect';
32
+ x: number;
33
+ y: number;
34
+ width: number;
35
+ height: number;
36
+ rx?: number;
37
+ accent?: boolean;
38
+ filled?: boolean;
39
+ };
40
+
41
+ export interface SuiteGlyph {
42
+ /** One-line description of what the mark depicts (used in docs/sheets). */
43
+ description: string;
44
+ elements: SuiteGlyphElement[];
45
+ /**
46
+ * The accent group carries a subtle CSS animation (today-sun rays only).
47
+ * Renderers must honour prefers-reduced-motion; see suite-icon.tsx.
48
+ */
49
+ animatedAccent?: boolean;
50
+ }
51
+
52
+ export type SuiteIconName =
53
+ | 'timer-shell'
54
+ | 'kanban-wave'
55
+ | 'squid-doc'
56
+ | 'stack-hex'
57
+ | 'crew-bot'
58
+ | 'workspace'
59
+ | 'organisation'
60
+ | 'integration-plug'
61
+ | 'suite-grid'
62
+ | 'bell-wave'
63
+ | 'today-sun';
64
+
65
+ export const SUITE_GLYPHS: Record<SuiteIconName, SuiteGlyph> = {
66
+ 'timer-shell': {
67
+ description: 'TurtleTime — turtle-shell dome holding clock hands',
68
+ elements: [
69
+ { kind: 'path', d: 'M3 17h18' },
70
+ { kind: 'path', d: 'M6.5 17a5.5 5.5 0 0 1 11 0' },
71
+ { kind: 'circle', cx: 4, cy: 15.3, r: 1.5 },
72
+ { kind: 'path', d: 'M12 17v-3.4', accent: true },
73
+ { kind: 'path', d: 'M12 17l2.4-1.4', accent: true },
74
+ ],
75
+ },
76
+ 'kanban-wave': {
77
+ description: 'Tides — three kanban bars riding a wave',
78
+ elements: [
79
+ { kind: 'rect', x: 5.5, y: 8, width: 3, height: 8, rx: 1.5 },
80
+ { kind: 'rect', x: 10.5, y: 5.5, width: 3, height: 10.5, rx: 1.5 },
81
+ { kind: 'rect', x: 15.5, y: 9.5, width: 3, height: 6.5, rx: 1.5 },
82
+ {
83
+ kind: 'path',
84
+ d: 'M3 19.5q2.25-2.4 4.5 0t4.5 0t4.5 0t4.5 0',
85
+ accent: true,
86
+ },
87
+ ],
88
+ },
89
+ 'squid-doc': {
90
+ description: 'Kraken — squid with a pen-nib centre tentacle',
91
+ elements: [
92
+ {
93
+ kind: 'path',
94
+ d: 'M12 3.2c2.7 2.3 4.2 5.3 4.2 9.3H7.8c0-4 1.5-7 4.2-9.3Z',
95
+ },
96
+ { kind: 'circle', cx: 10.1, cy: 9, r: 0.9, filled: true },
97
+ { kind: 'circle', cx: 13.9, cy: 9, r: 0.9, filled: true },
98
+ { kind: 'path', d: 'M9.4 12.4v4.8q0 2.3-2.3 2.3' },
99
+ { kind: 'path', d: 'M14.6 12.4v4.8q0 2.3 2.3 2.3' },
100
+ { kind: 'path', d: 'M12 12.4v7.1', accent: true },
101
+ ],
102
+ },
103
+ 'stack-hex': {
104
+ description: 'ShellStack — hexagon with the resolving spiral',
105
+ elements: [
106
+ { kind: 'path', d: 'M18 12l-3 5.2H9l-3-5.2 3-5.2h6Z' },
107
+ { kind: 'path', d: 'M14.2 9.3H9.8v5.4h4.4v-2.7h-2.2', accent: true },
108
+ ],
109
+ },
110
+ 'crew-bot': {
111
+ description: 'Crew — friendly robot head with antenna and smile',
112
+ elements: [
113
+ { kind: 'path', d: 'M12 7.5V5' },
114
+ { kind: 'circle', cx: 12, cy: 4, r: 1, filled: true },
115
+ { kind: 'rect', x: 5, y: 8, width: 14, height: 9.5, rx: 3 },
116
+ { kind: 'path', d: 'M5 11.8H3.4' },
117
+ { kind: 'path', d: 'M19 11.8h1.6' },
118
+ { kind: 'circle', cx: 9.2, cy: 11.8, r: 1, filled: true },
119
+ { kind: 'circle', cx: 14.8, cy: 11.8, r: 1, filled: true },
120
+ { kind: 'path', d: 'M9.6 14.6q2.4 1.7 4.8 0', accent: true },
121
+ ],
122
+ },
123
+ workspace: {
124
+ description: 'Workspace container — panel with header bar and content',
125
+ elements: [
126
+ { kind: 'rect', x: 3.5, y: 4.5, width: 17, height: 15, rx: 2.5 },
127
+ { kind: 'path', d: 'M3.5 8.7h17' },
128
+ { kind: 'circle', cx: 6.2, cy: 6.6, r: 0.9, accent: true, filled: true },
129
+ { kind: 'path', d: 'M6.4 12.2h7' },
130
+ { kind: 'path', d: 'M6.4 15.4h10.5' },
131
+ ],
132
+ },
133
+ organisation: {
134
+ description: 'Organisation chart — root node with two members',
135
+ elements: [
136
+ { kind: 'rect', x: 9.5, y: 3.5, width: 5, height: 5, rx: 1.4, accent: true },
137
+ { kind: 'path', d: 'M12 8.5v2.5' },
138
+ { kind: 'path', d: 'M6.75 13.5v-2.5h10.5v2.5' },
139
+ { kind: 'rect', x: 4.25, y: 13.5, width: 5, height: 5, rx: 1.4 },
140
+ { kind: 'rect', x: 14.75, y: 13.5, width: 5, height: 5, rx: 1.4 },
141
+ ],
142
+ },
143
+ 'integration-plug': {
144
+ description: 'Integration — two plug halves meeting under a spark',
145
+ elements: [
146
+ { kind: 'rect', x: 3.2, y: 9.8, width: 5.6, height: 4.6, rx: 1.4 },
147
+ { kind: 'path', d: 'M8.8 11h2.4' },
148
+ { kind: 'path', d: 'M8.8 13.2h2.4' },
149
+ { kind: 'rect', x: 15.2, y: 9.8, width: 5.6, height: 4.6, rx: 1.4 },
150
+ { kind: 'path', d: 'M15.2 11h-2.4' },
151
+ { kind: 'path', d: 'M15.2 13.2h-2.4' },
152
+ { kind: 'path', d: 'M6 14.4v2q0 1.4-1.4 1.4H3.4' },
153
+ { kind: 'path', d: 'M18 14.4v2q0 1.4 1.4 1.4h1.2' },
154
+ { kind: 'path', d: 'M12 4.4v2.4', accent: true },
155
+ { kind: 'path', d: 'M10.4 5l3.2 1.2', accent: true },
156
+ { kind: 'path', d: 'M13.6 5l-3.2 1.2', accent: true },
157
+ ],
158
+ },
159
+ 'suite-grid': {
160
+ description: 'Suite app switcher — four tiles, the active app filled',
161
+ elements: [
162
+ { kind: 'rect', x: 4, y: 4, width: 7, height: 7, rx: 2 },
163
+ { kind: 'rect', x: 13, y: 4, width: 7, height: 7, rx: 2 },
164
+ { kind: 'rect', x: 4, y: 13, width: 7, height: 7, rx: 2 },
165
+ {
166
+ kind: 'rect',
167
+ x: 13,
168
+ y: 13,
169
+ width: 7,
170
+ height: 7,
171
+ rx: 2,
172
+ accent: true,
173
+ filled: true,
174
+ },
175
+ ],
176
+ },
177
+ 'bell-wave': {
178
+ description: 'Notifications — bell with sound waves',
179
+ elements: [
180
+ { kind: 'path', d: 'M7 9.2a5 5 0 0 1 10 0c0 5.3 2.4 7.3 2.4 7.3H4.6S7 14.5 7 9.2' },
181
+ { kind: 'path', d: 'M10.7 19.3a1.5 1.5 0 0 0 2.6 0' },
182
+ { kind: 'path', d: 'M17.9 6.4a5.5 5.5 0 0 1 1.4 3.7', accent: true },
183
+ { kind: 'path', d: 'M15.7 4a9.4 9.4 0 0 1 2.4 6', accent: true },
184
+ ],
185
+ },
186
+ 'today-sun': {
187
+ description: 'Today — sun with slowly turning rays (motion-safe)',
188
+ animatedAccent: true,
189
+ elements: [
190
+ { kind: 'circle', cx: 12, cy: 12, r: 4 },
191
+ { kind: 'path', d: 'M12 2.8v2.4', accent: true },
192
+ { kind: 'path', d: 'M12 18.8v2.4', accent: true },
193
+ { kind: 'path', d: 'M2.8 12h2.4', accent: true },
194
+ { kind: 'path', d: 'M18.8 12h2.4', accent: true },
195
+ { kind: 'path', d: 'M5.5 5.5l1.7 1.7', accent: true },
196
+ { kind: 'path', d: 'M16.8 16.8l1.7 1.7', accent: true },
197
+ { kind: 'path', d: 'M5.5 18.5l1.7-1.7', accent: true },
198
+ { kind: 'path', d: 'M16.8 7.2l1.7-1.7', accent: true },
199
+ ],
200
+ },
201
+ };
202
+
203
+ export const SUITE_ICON_NAMES = Object.keys(SUITE_GLYPHS) as SuiteIconName[];
@@ -0,0 +1,12 @@
1
+ export { APP_ACCENTS, APP_GLYPHS } from './app-accents';
2
+ export type { SuiteAccentSlug, SuiteAppAccent } from './app-accents';
3
+ export { SUITE_GLYPHS, SUITE_ICON_NAMES } from './glyphs';
4
+ export type {
5
+ SuiteGlyph,
6
+ SuiteGlyphElement,
7
+ SuiteIconName,
8
+ } from './glyphs';
9
+ export { SuiteIcon } from './suite-icon';
10
+ export type { SuiteIconProps } from './suite-icon';
11
+ export { SuiteAppIcon } from './suite-app-icon';
12
+ export type { SuiteAppIconProps } from './suite-app-icon';
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import type { CSSProperties } from 'react';
4
+
5
+ import { cn } from '../lib/cn';
6
+ import { APP_ACCENTS, APP_GLYPHS, type SuiteAccentSlug } from './app-accents';
7
+ import { GlyphParts } from './glyph-parts';
8
+ import { SUITE_GLYPHS } from './glyphs';
9
+
10
+ const SIZE_PRESETS = { sm: 28, md: 36, lg: 44 } as const;
11
+
12
+ export interface SuiteAppIconProps {
13
+ app: SuiteAccentSlug;
14
+ /** px size, or a preset (sm 28 / md 36 / lg 44). */
15
+ size?: number | keyof typeof SIZE_PRESETS;
16
+ /** Accessible label; defaults to decorative (`aria-hidden`). */
17
+ label?: string;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ }
21
+
22
+ /**
23
+ * An app's simplified mark — brand tile + its suite glyph, drawn
24
+ * programmatically at any size (no cross-app <img> favicon lookups).
25
+ * Colours come from app-accents.ts.
26
+ */
27
+ export function SuiteAppIcon({
28
+ app,
29
+ size = 'md',
30
+ label,
31
+ className,
32
+ style,
33
+ }: SuiteAppIconProps) {
34
+ const px = typeof size === 'number' ? size : SIZE_PRESETS[size];
35
+ const brand = APP_ACCENTS[app];
36
+ const glyph = SUITE_GLYPHS[APP_GLYPHS[app]];
37
+
38
+ return (
39
+ <svg
40
+ width={px}
41
+ height={px}
42
+ viewBox="0 0 24 24"
43
+ className={cn('shrink-0', className)}
44
+ style={style}
45
+ role={label ? 'img' : undefined}
46
+ aria-label={label}
47
+ aria-hidden={label ? undefined : true}
48
+ >
49
+ <rect x="1" y="1" width="22" height="22" rx="5.5" fill={brand.tile} />
50
+ <g
51
+ transform="translate(4.4 4.4) scale(0.6333)"
52
+ fill="none"
53
+ stroke="currentColor"
54
+ strokeWidth={2.7}
55
+ strokeLinecap="round"
56
+ strokeLinejoin="round"
57
+ style={
58
+ {
59
+ color: brand.onTile,
60
+ '--suite-icon-accent': brand.onTileAccent,
61
+ } as CSSProperties
62
+ }
63
+ >
64
+ <GlyphParts elements={glyph.elements} />
65
+ </g>
66
+ </svg>
67
+ );
68
+ }
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+
3
+ import type { CSSProperties } from 'react';
4
+
5
+ import { cn } from '../lib/cn';
6
+ import { APP_ACCENTS, type SuiteAccentSlug } from './app-accents';
7
+ import { GlyphParts } from './glyph-parts';
8
+ import { SUITE_GLYPHS, type SuiteIconName } from './glyphs';
9
+
10
+ /**
11
+ * Motion for the only animated glyph (today-sun): rays turn slowly, and only
12
+ * when the user has not asked for reduced motion. Mirrors the injected-style
13
+ * pattern used by the app logo components.
14
+ */
15
+ const TODAY_SUN_STYLE = `
16
+ @media (prefers-reduced-motion: no-preference) {
17
+ .suite-icon-today-sun-rays {
18
+ animation: suite-icon-today-sun-spin 18s linear infinite;
19
+ transform-box: fill-box;
20
+ transform-origin: center;
21
+ }
22
+ }
23
+ @keyframes suite-icon-today-sun-spin {
24
+ to { transform: rotate(360deg); }
25
+ }
26
+ `;
27
+
28
+ export interface SuiteIconProps {
29
+ name: SuiteIconName;
30
+ /** px size; a Tailwind sizing class on `className` also works. */
31
+ size?: number | string;
32
+ /** App slug — tints the glyph's accent layer with the app's brand hex. */
33
+ accent?: SuiteAccentSlug;
34
+ strokeWidth?: number;
35
+ /** Only today-sun animates; set false to freeze it. Reduced-motion safe. */
36
+ animated?: boolean;
37
+ /** Accessible label. Without it the icon is `aria-hidden` (decorative). */
38
+ label?: string;
39
+ className?: string;
40
+ style?: CSSProperties;
41
+ }
42
+
43
+ /**
44
+ * Suite icon — hand-drawn glyphs on the lucide grammar (24×24, 2px strokes,
45
+ * round caps/joins). Strokes inherit `currentColor`, so ph-* text tokens just
46
+ * work; `accent` adds a per-app brand tint to the glyph's accent layer.
47
+ */
48
+ export function SuiteIcon({
49
+ name,
50
+ size = 24,
51
+ accent,
52
+ strokeWidth = 2,
53
+ animated = true,
54
+ label,
55
+ className,
56
+ style,
57
+ }: SuiteIconProps) {
58
+ const glyph = SUITE_GLYPHS[name];
59
+ const accentHex = accent ? APP_ACCENTS[accent].accent : undefined;
60
+ const mergedStyle = accentHex
61
+ ? ({ ...style, '--suite-icon-accent': accentHex } as CSSProperties)
62
+ : style;
63
+
64
+ return (
65
+ <svg
66
+ width={size}
67
+ height={size}
68
+ viewBox="0 0 24 24"
69
+ fill="none"
70
+ stroke="currentColor"
71
+ strokeWidth={strokeWidth}
72
+ strokeLinecap="round"
73
+ strokeLinejoin="round"
74
+ className={cn('shrink-0', className)}
75
+ style={mergedStyle}
76
+ role={label ? 'img' : undefined}
77
+ aria-label={label}
78
+ aria-hidden={label ? undefined : true}
79
+ >
80
+ {glyph.animatedAccent && animated ? (
81
+ <style>{TODAY_SUN_STYLE}</style>
82
+ ) : null}
83
+ <GlyphParts
84
+ elements={glyph.elements}
85
+ accentClassName={
86
+ glyph.animatedAccent && animated
87
+ ? 'suite-icon-today-sun-rays'
88
+ : undefined
89
+ }
90
+ />
91
+ </svg>
92
+ );
93
+ }
@@ -0,0 +1,108 @@
1
+ export { TodayPageFrame } from './components/today-page-frame';
2
+ export * from './components/today-ui';
3
+ export {
4
+ AppSwitcher,
5
+ AppSwitcherChevron,
6
+ AppSwitcherMark,
7
+ appSwitcherMarkClass,
8
+ appSwitcherMenuItemClass,
9
+ appSwitcherTriggerClass,
10
+ type SuiteAppEntry,
11
+ } from './components/app-switcher';
12
+ export {
13
+ SuiteNotificationBell,
14
+ type SuiteNotification,
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';
21
+ export {
22
+ SuiteUserMenu,
23
+ type SuiteUserMenuProps,
24
+ } from './components/suite-user-menu';
25
+ export {
26
+ SuiteBottomNav,
27
+ type SuiteBottomNavItem,
28
+ } from './components/suite-bottom-nav';
29
+ export {
30
+ SUITE_APPS,
31
+ SUITE_APP_MAP,
32
+ suiteAppBaseUrl,
33
+ type SuiteAppDefinition,
34
+ type SuiteAppSlug,
35
+ } from './lib/apps';
36
+ export { TodayCalibrating } from './components/today-calibrating';
37
+ export {
38
+ SuiteThemeProvider,
39
+ useSuiteTheme,
40
+ type SuiteResolvedTheme,
41
+ type SuiteTheme,
42
+ type SuiteThemeConfig,
43
+ type SuiteThemeContextValue,
44
+ } from './components/theme-provider';
45
+ export {
46
+ SuiteAiPanel,
47
+ type SuiteAiPreset,
48
+ } from './components/ai-panel';
49
+ export {
50
+ APP_ACCENTS,
51
+ APP_GLYPHS,
52
+ SUITE_GLYPHS,
53
+ SUITE_ICON_NAMES,
54
+ SuiteAppIcon,
55
+ SuiteIcon,
56
+ type SuiteAccentSlug,
57
+ type SuiteAppAccent,
58
+ type SuiteAppIconProps,
59
+ type SuiteGlyph,
60
+ type SuiteGlyphElement,
61
+ type SuiteIconName,
62
+ type SuiteIconProps,
63
+ } from './icons';
64
+ export type {
65
+ SuiteCommandItem,
66
+ SuiteCommandPaletteComponent,
67
+ SuiteDropdownItemComponent,
68
+ SuiteDropdownMenuComponent,
69
+ SuiteDropdownMenuProps,
70
+ SuiteSpinnerComponent,
71
+ } from './lib/injected';
72
+ export { useIdleMount } from './lib/use-idle-mount';
73
+ export { cn } from './lib/cn';
74
+ export {
75
+ SuiteMotionProvider,
76
+ m,
77
+ SUITE_SPRINGS,
78
+ type SuiteSpringName,
79
+ } from './lib/motion';
80
+ export {
81
+ SuitePage,
82
+ SuitePageHeader,
83
+ SuiteBreadcrumbs,
84
+ SuiteToolbar,
85
+ SuiteTabList,
86
+ SuiteSectionHeader,
87
+ type SuitePageWidth,
88
+ type SuiteBreadcrumbsProps,
89
+ } from './components/suite-layout';
90
+ export {
91
+ SuiteSkeleton,
92
+ SuiteSkeletonCard,
93
+ SuiteSkeletonList,
94
+ SuiteEmptyState,
95
+ } from './components/suite-skeleton';
96
+ export {
97
+ SuiteSettingsMobileNav,
98
+ type SuiteSettingsNavItem,
99
+ } from './components/suite-settings-mobile-nav';
100
+ export {
101
+ SuiteSidebar,
102
+ type SuiteSidebarNavItem,
103
+ } from './components/suite-sidebar';
104
+ export {
105
+ SuiteAppLayout,
106
+ type SuiteAppLayoutProps,
107
+ } from './components/suite-app-layout';
108
+
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The single source of truth for the ShellStack app family — used by the
3
+ * app switcher, mobile bottom nav, launchpad, and cross-app navigation.
4
+ * Slugs double as accent keys (see icons/app-accents.ts) and data-test
5
+ * suffixes (`switch-app-${slug}`, `suite-nav-${slug}`).
6
+ */
7
+
8
+ export type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
9
+
10
+ export interface SuiteAppDefinition {
11
+ slug: SuiteAppSlug;
12
+ name: string;
13
+ description: string;
14
+ /** Public asset path — every app hosts all four icons. */
15
+ icon: string;
16
+ /** Landing route used after cross-app SSO redirect. */
17
+ landing: string;
18
+ /** Env var carrying the app's base URL (dev fallback included). */
19
+ baseUrlEnv: string;
20
+ baseUrlFallback: string;
21
+ }
22
+
23
+ /** Display order — launchpad first, then by daily workflow. */
24
+ export const SUITE_APPS: readonly SuiteAppDefinition[] = [
25
+ {
26
+ slug: 'shellstack',
27
+ name: 'ShellStack',
28
+ description: 'Organisation and billing',
29
+ icon: '/icon-shellstack.svg',
30
+ landing: '/home',
31
+ baseUrlEnv: 'NEXT_PUBLIC_SHELLSTACK_URL',
32
+ baseUrlFallback: 'http://localhost:3002',
33
+ },
34
+ {
35
+ slug: 'tides',
36
+ name: 'Tides',
37
+ description: 'Projects and tasks',
38
+ icon: '/tides-icon.svg',
39
+ landing: '/today',
40
+ baseUrlEnv: 'NEXT_PUBLIC_TIDES_URL',
41
+ baseUrlFallback: 'http://localhost:3001',
42
+ },
43
+ {
44
+ slug: 'turtletime',
45
+ name: 'TurtleTime',
46
+ description: 'Time tracking',
47
+ icon: '/turtletime-icon.svg',
48
+ landing: '/tracker',
49
+ baseUrlEnv: 'NEXT_PUBLIC_TURTLETIME_URL',
50
+ baseUrlFallback: 'http://localhost:3000',
51
+ },
52
+ {
53
+ slug: 'kraken',
54
+ name: 'Kraken',
55
+ description: 'Documents and knowledge',
56
+ icon: '/kraken-icon.svg',
57
+ landing: '/today',
58
+ baseUrlEnv: 'NEXT_PUBLIC_KRAKEN_URL',
59
+ baseUrlFallback: 'http://localhost:3003',
60
+ },
61
+ ] as const;
62
+
63
+ export const SUITE_APP_MAP: Readonly<Record<SuiteAppSlug, SuiteAppDefinition>> =
64
+ Object.fromEntries(SUITE_APPS.map((app) => [app.slug, app])) as Record<
65
+ SuiteAppSlug,
66
+ SuiteAppDefinition
67
+ >;
68
+
69
+ /** Resolve an app's base URL: env var first, dev fallback second. */
70
+ export function suiteAppBaseUrl(slug: SuiteAppSlug): string {
71
+ const def = SUITE_APP_MAP[slug];
72
+ const fromEnv =
73
+ typeof process !== 'undefined' ? process.env[def.baseUrlEnv] : undefined;
74
+ return (fromEnv || def.baseUrlFallback).replace(/\/$/, '');
75
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,54 @@
1
+ import type {
2
+ ButtonHTMLAttributes,
3
+ ComponentType,
4
+ ReactNode,
5
+ } from 'react';
6
+
7
+ /**
8
+ * Structural types for theme primitives each app injects from
9
+ * `@xenide-io/the-old-ui-theme/ui` (the shared package must not depend on it).
10
+ * The real components satisfy these shapes, so apps can pass them directly.
11
+ */
12
+
13
+ export interface SuiteDropdownMenuProps {
14
+ trigger: ReactNode;
15
+ children: ReactNode;
16
+ 'aria-label': string;
17
+ align?: 'start' | 'center' | 'end';
18
+ side?: 'top' | 'right' | 'bottom' | 'left';
19
+ sideOffset?: number;
20
+ className?: string;
21
+ panelClassName?: string;
22
+ open?: boolean;
23
+ onOpenChange?: (open: boolean) => void;
24
+ }
25
+
26
+ export type SuiteDropdownMenuComponent =
27
+ ComponentType<SuiteDropdownMenuProps>;
28
+
29
+ export type SuiteDropdownItemComponent = ComponentType<
30
+ ButtonHTMLAttributes<HTMLButtonElement>
31
+ >;
32
+
33
+ export interface SuiteCommandItem {
34
+ id: string;
35
+ label: string;
36
+ shortcut?: string;
37
+ icon?: ReactNode;
38
+ onSelect: () => void;
39
+ }
40
+
41
+ export type SuiteCommandPaletteComponent = ComponentType<{
42
+ items: SuiteCommandItem[];
43
+ isOpen: boolean;
44
+ onClose: () => void;
45
+ placeholder?: string;
46
+ className?: string;
47
+ }>;
48
+
49
+ /** App-branded spinner (each app has its own AppSpinner design). */
50
+ export type SuiteSpinnerComponent = ComponentType<{
51
+ size?: 'sm' | 'md' | 'lg';
52
+ className?: string;
53
+ label?: string;
54
+ }>;
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { LazyMotion, domAnimation, m } from 'motion/react';
4
+ import type { ReactNode } from 'react';
5
+
6
+ /**
7
+ * Suite motion foundation.
8
+ *
9
+ * `LazyMotion` + `domAnimation` keeps the animation runtime (~15 kB) out of
10
+ * the initial bundle — features load with the first animated component.
11
+ * `strict` guarantees only the tree-shakeable `m` component is used inside
12
+ * the provider (never the heavier `motion` component).
13
+ *
14
+ * Usage:
15
+ * <SuiteMotionProvider>
16
+ * <m.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
17
+ * </SuiteMotionProvider>
18
+ *
19
+ * Prefer CSS transitions for micro-interactions (hover/press/colour) and
20
+ * reach for `m` only when you need springs, gestures, or layout animation.
21
+ */
22
+ export function SuiteMotionProvider({ children }: { children: ReactNode }) {
23
+ return (
24
+ <LazyMotion features={domAnimation} strict>
25
+ {children}
26
+ </LazyMotion>
27
+ );
28
+ }
29
+
30
+ export { m };
31
+
32
+ /**
33
+ * Spring presets — the JS counterparts of the `ease-spring-*` timing
34
+ * functions defined in each app's tailwind config. Use these for entrances,
35
+ * sheets, drawers, and press feedback so JS and CSS motion feel identical.
36
+ * All respect `prefers-reduced-motion` when paired with
37
+ * `MotionConfig reducedMotion="user"` or CSS `motion-reduce:` variants.
38
+ */
39
+ export const SUITE_SPRINGS = {
40
+ /** Snappy panel/sheet entrances — small overshoot. */
41
+ fast: { type: 'spring', stiffness: 400, damping: 32, mass: 0.8 },
42
+ /** Gentle scrims, fades, list staggers. */
43
+ subtle: { type: 'spring', stiffness: 260, damping: 26, mass: 1 },
44
+ /** Press/tap feedback — quick settle, no wobble. */
45
+ press: { type: 'spring', stiffness: 600, damping: 35, mass: 0.5 },
46
+ } as const;
47
+
48
+ export type SuiteSpringName = keyof typeof SUITE_SPRINGS;
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+
5
+ /** Mount non-critical chrome after the browser is idle (or a short timeout). */
6
+ export function useIdleMount(timeoutMs = 1200): boolean {
7
+ const [ready, setReady] = useState(false);
8
+
9
+ useEffect(() => {
10
+ let timeoutId = 0;
11
+ let idleId = 0;
12
+
13
+ const activate = () => setReady(true);
14
+
15
+ if (typeof window.requestIdleCallback === 'function') {
16
+ idleId = window.requestIdleCallback(activate, { timeout: timeoutMs });
17
+ return () => window.cancelIdleCallback(idleId);
18
+ }
19
+
20
+ timeoutId = window.setTimeout(activate, Math.min(timeoutMs, 200));
21
+ return () => window.clearTimeout(timeoutId);
22
+ }, [timeoutMs]);
23
+
24
+ return ready;
25
+ }