@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.
- package/dist/chunk-54ZR4VL5.mjs +4641 -0
- package/dist/chunk-G53YLHVE.mjs +1023 -0
- package/dist/{index-BgG8Homu.d.mts → index-B5NOyoKS.d.mts} +100 -100
- package/dist/{index-BgG8Homu.d.ts → index-B5NOyoKS.d.ts} +100 -100
- package/dist/index.d.mts +9 -145
- package/dist/index.d.ts +9 -145
- package/dist/index.js +653 -1111
- package/dist/index.mjs +86 -342
- package/dist/suite.d.mts +760 -0
- package/dist/suite.d.ts +760 -0
- package/dist/suite.js +2800 -0
- package/dist/suite.mjs +2441 -0
- package/dist/ui.d.mts +1 -2
- package/dist/ui.d.ts +1 -2
- package/dist/ui.js +14 -10
- package/dist/ui.mjs +13 -11
- package/package.json +43 -24
- package/src/components/icons/icons.tsx +0 -2
- package/src/components/icons/index.ts +0 -3
- package/src/components/sections/IconShowcase.tsx +1 -110
- package/src/components/sections/NewComponentsShowcase.tsx +4 -4
- package/src/components/sections/SidebarShowcase.tsx +3 -3
- package/src/components/sections/SuiteShowcase.tsx +669 -0
- package/src/components/ui/ComponentDocs.tsx +2 -2
- package/src/components/ui/ContextMenu.tsx +1 -1
- package/src/components/ui/Menubar.tsx +1 -1
- package/src/components/ui/Sidebar.tsx +10 -8
- package/src/styles/suite-skin.css +255 -0
- package/src/suite/components/ai-panel.tsx +202 -0
- package/src/suite/components/app-switcher.tsx +196 -0
- package/src/suite/components/command-palette-host.tsx +62 -0
- package/src/suite/components/deferred-chrome.tsx +12 -0
- package/src/suite/components/suite-app-layout.tsx +69 -0
- package/src/suite/components/suite-bottom-nav.tsx +113 -0
- package/src/suite/components/suite-layout.tsx +285 -0
- package/src/suite/components/suite-mobile-drawer.tsx +150 -0
- package/src/suite/components/suite-mobile-header.tsx +71 -0
- package/src/suite/components/suite-notification-bell.tsx +227 -0
- package/src/suite/components/suite-settings-mobile-nav.tsx +83 -0
- package/src/suite/components/suite-sidebar.tsx +198 -0
- package/src/suite/components/suite-skeleton.tsx +191 -0
- package/src/suite/components/suite-user-menu.tsx +109 -0
- package/src/suite/components/theme-provider.tsx +174 -0
- package/src/suite/components/today-calibrating.tsx +95 -0
- package/src/suite/components/today-page-frame.tsx +28 -0
- package/src/suite/components/today-ui.tsx +370 -0
- package/src/suite/icons/app-accents.ts +75 -0
- package/src/suite/icons/glyph-parts.tsx +67 -0
- package/src/suite/icons/glyphs.ts +203 -0
- package/src/suite/icons/index.ts +12 -0
- package/src/suite/icons/suite-app-icon.tsx +68 -0
- package/src/suite/icons/suite-icon.tsx +93 -0
- package/src/suite/index.ts +108 -0
- package/src/suite/lib/apps.ts +75 -0
- package/src/suite/lib/cn.ts +6 -0
- package/src/suite/lib/injected.ts +54 -0
- package/src/suite/lib/motion.tsx +48 -0
- package/src/suite/lib/use-idle-mount.ts +25 -0
- package/dist/chunk-5HSOBJ4F.mjs +0 -5968
- package/src/components/icons/lemon-brand-icons.tsx +0 -16
- package/src/components/icons/lemon-category-icons.tsx +0 -72
- 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,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
|
+
}
|