@xenide-io/the-old-ui-theme 0.7.0 → 0.9.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.
- package/README.md +9 -10
- package/dist/{Chip-Cq3AiAji.d.mts → Chip-BzuNgJIa.d.mts} +81 -23
- package/dist/{Chip-Cq3AiAji.d.ts → Chip-BzuNgJIa.d.ts} +81 -23
- package/dist/{ai-message-blocknote-WABTHFY7.mjs → ai-message-blocknote-D4Z6DUCJ.mjs} +9 -3
- package/dist/chunk-2EO5VCXP.mjs +3912 -0
- package/dist/chunk-APKRZQRO.mjs +251 -0
- package/dist/chunk-BS7BZI5W.mjs +19 -0
- package/dist/chunk-IULORI3A.mjs +8 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +997 -576
- package/dist/index.mjs +49 -45
- package/dist/suite.d.mts +157 -55
- package/dist/suite.d.ts +157 -55
- package/dist/suite.js +4249 -801
- package/dist/suite.mjs +2578 -594
- package/dist/theme-boot-P-oqKVZF.d.mts +74 -0
- package/dist/theme-boot-P-oqKVZF.d.ts +74 -0
- package/dist/theme-boot.d.mts +2 -0
- package/dist/theme-boot.d.ts +2 -0
- package/dist/theme-boot.js +44 -0
- package/dist/theme-boot.mjs +8 -0
- package/dist/ui.d.mts +5 -29
- package/dist/ui.d.ts +5 -29
- package/dist/ui.js +647 -352
- package/dist/ui.mjs +10 -12
- package/docs/README.md +34 -0
- package/docs/ai-quick-reference.md +56 -0
- package/docs/component-library-improvement-plan.md +751 -0
- package/docs/component-reference.md +81 -0
- package/docs/components.md +48 -0
- package/docs/demos.md +19 -0
- package/docs/installation.md +117 -0
- package/docs/suite-audit.md +34 -0
- package/docs/theme-token-reference.md +67 -0
- package/docs/themes.md +164 -0
- package/package.json +22 -21
- package/public/fonts/OpenRunde-Bold.woff2 +0 -0
- package/public/fonts/OpenRunde-Medium.woff2 +0 -0
- package/public/fonts/OpenRunde-Regular.woff2 +0 -0
- package/public/fonts/OpenRunde-Semibold.woff2 +0 -0
- package/scripts/package-smoke.mjs +43 -0
- package/src/app/globals.css +2 -1
- package/src/components/ui/Accordion.tsx +20 -6
- package/src/components/ui/Avatar.tsx +5 -3
- package/src/components/ui/Calendar.tsx +187 -63
- package/src/components/ui/Card.test.tsx +38 -0
- package/src/components/ui/Card.tsx +77 -14
- package/src/components/ui/CollapsibleSection.tsx +12 -4
- package/src/components/ui/CommandPalette.test.tsx +91 -4
- package/src/components/ui/CommandPalette.tsx +138 -128
- package/src/components/ui/EmptyState.tsx +2 -2
- package/src/components/ui/FormField.tsx +1 -1
- package/src/components/ui/Input.tsx +42 -18
- package/src/components/ui/Kbd.tsx +18 -3
- package/src/components/ui/Modal.tsx +3 -1
- package/src/components/ui/Progress.tsx +14 -5
- package/src/components/ui/SegmentedControl.tsx +2 -2
- package/src/components/ui/SettingsLayout.tsx +1 -1
- package/src/components/ui/Stat.tsx +3 -2
- package/src/components/ui/Table.tsx +8 -3
- package/src/components/ui/Tooltip.test.tsx +70 -1
- package/src/components/ui/Tooltip.tsx +53 -29
- package/src/components/ui/Typography.tsx +103 -49
- package/src/components/ui/avatar-stat.test.tsx +23 -0
- package/src/components/ui/calendar.test.tsx +88 -0
- package/src/components/ui/disclosure.test.tsx +39 -0
- package/src/components/ui/index.ts +44 -6
- package/src/components/ui/progress.test.tsx +15 -0
- package/src/components/ui/typography.test.tsx +26 -1
- package/src/styles/suite-skin.css +8 -2
- package/src/styles/themes.css +184 -68
- package/src/suite/components/ai-message-blocknote.tsx +13 -2
- package/src/suite/components/ai-panel.tsx +41 -10
- package/src/suite/components/app-switcher.test.tsx +21 -6
- package/src/suite/components/app-switcher.tsx +7 -11
- package/src/suite/components/suite-app-layout.tsx +12 -3
- package/src/suite/components/suite-bottom-nav.tsx +0 -1
- package/src/suite/components/suite-clients-projects-directory.test.tsx +71 -0
- package/src/suite/components/suite-clients-projects-directory.tsx +943 -0
- package/src/suite/components/suite-integration-picker.test.tsx +224 -0
- package/src/suite/components/suite-integration-picker.tsx +939 -0
- package/src/suite/components/suite-integration-rules.test.tsx +157 -0
- package/src/suite/components/suite-integration-rules.tsx +471 -0
- package/src/suite/components/suite-mobile-drawer.test.tsx +18 -2
- package/src/suite/components/suite-mobile-drawer.tsx +65 -98
- package/src/suite/components/suite-notification-bell.tsx +29 -4
- package/src/suite/components/suite-sidebar.tsx +25 -25
- package/src/suite/components/suite-skeleton.tsx +2 -2
- package/src/suite/components/suite-user-menu.tsx +9 -8
- package/src/suite/components/theme-provider.test.tsx +87 -3
- package/src/suite/components/theme-provider.tsx +144 -23
- package/src/suite/components/today-ui.tsx +79 -68
- package/src/suite/components/workspace-switcher.test.tsx +56 -0
- package/src/suite/components/workspace-switcher.tsx +3 -3
- package/src/suite/icons/app-accents.ts +12 -12
- package/src/suite/icons/glyphs.ts +6 -12
- package/src/suite/index.ts +27 -0
- package/src/suite/lib/apps.test.ts +9 -1
- package/src/suite/lib/apps.ts +14 -8
- package/src/suite/lib/cookies.ts +53 -0
- package/src/suite/lib/theme-boot.ts +32 -0
- package/src/suite/lib/theme-cookie.ts +7 -0
- package/src/suite/lib/use-sidebar-width.ts +5 -51
- package/dist/chunk-KDR6HI6F.mjs +0 -1075
- package/dist/chunk-NOI42JNZ.mjs +0 -151
- package/dist/chunk-VXISSXTB.mjs +0 -2564
- package/src/suite/lib/use-lock-mobile-page-zoom.test.tsx +0 -65
- package/src/suite/lib/use-lock-mobile-page-zoom.ts +0 -51
|
@@ -56,7 +56,7 @@ export type SuiteIconName =
|
|
|
56
56
|
| "squid-doc"
|
|
57
57
|
| "stack-hex"
|
|
58
58
|
| "crew-bot"
|
|
59
|
-
| "
|
|
59
|
+
| "shelly-snail"
|
|
60
60
|
| "workspace"
|
|
61
61
|
| "organisation"
|
|
62
62
|
| "integration-plug"
|
|
@@ -122,24 +122,18 @@ export const SUITE_GLYPHS: Record<SuiteIconName, SuiteGlyph> = {
|
|
|
122
122
|
{ kind: "path", d: "M9.6 14.6q2.4 1.7 4.8 0", accent: true },
|
|
123
123
|
],
|
|
124
124
|
},
|
|
125
|
-
"
|
|
126
|
-
description: "
|
|
125
|
+
"shelly-snail": {
|
|
126
|
+
description: "Shelly — smiling snail with spiral shell and ball-tipped antennae",
|
|
127
127
|
elements: [
|
|
128
128
|
{
|
|
129
129
|
kind: "path",
|
|
130
|
-
d: "
|
|
130
|
+
d: "M2931 37263 c-1542 -737 -1730 -2870 -339 -3838 427 -297 339 1 1730 -5802 102 -425 101 -430 -391 -950 -1003 -1062 -1175 -1507 -1834 -4748 -1525 -7505 -1398 -10142 668 -13875 2061 -3724 4835 -5715 9185 -6592 2869 -579 4928 -582 8750 -15 1293 192 3408 506 4700 697 6839 1012 8768 1422 9385 1993 1103 1021 425 2357 -1733 3414 l-944 462 916 620 c5469 3705 5358 13657 -209 18536 -2592 2272 -5133 3185 -8865 3185 -3361 0 -5965 -948 -8226 -2994 -501 -453 -824 -546 -824 -235 0 70 301 1416 668 2991 668 2863 668 2863 1053 3101 1156 713 1317 2506 305 3395 -1583 1390 -3787 234 -3592 -1885 61 -673 61 -673 -1165 -3013 l-1227 -2340 -1303 -10 -1303 -10 -1267 2463 c-1202 2337 -1261 2480 -1155 2784 610 1750 -1309 3465 -2983 2666z m1743 -1216 c309 -354 379 -713 232 -1191 -205 -669 -183 -731 1300 -3583 1821 -3503 1488 -3127 2631 -2970 816 112 1053 109 1729 -25 587 -117 813 -127 901 -42 64 63 778 1394 1586 2958 1470 2844 1470 2844 1345 3365 -208 868 106 1458 852 1598 1380 258 1829 -1605 545 -2260 -501 -255 -427 -36 -1292 -3813 l-725 -3166 513 -617 c1210 -1453 1328 -2014 1709 -8068 224 -3562 368 -4603 790 -5688 820 -2113 2775 -3739 5643 -4695 2847 -948 5589 -1186 7567 -654 l850 228 1070 -471 c2352 -1034 2946 -1824 1719 -2288 -1707 -647 -14950 -2665 -17489 -2665 -5500 0 -9506 1872 -11922 5573 -2564 3926 -2753 6473 -1075 14490 542 2586 754 3104 1682 4102 778 837 757 677 322 2415 -188 754 -529 2180 -757 3170 -228 990 -436 1878 -463 1973 -27 95 -246 260 -486 366 -685 303 -961 1115 -610 1795 335 647 1332 736 1833 163z m21523 -6801 c6385 -1036 10732 -7090 9753 -13583 -628 -4167 -2948 -6812 -6688 -7626 -6108 -1329 -10963 4878 -8256 10556 1608 3373 6202 4320 8566 1766 2055 -2219 1699 -6037 -662 -7096 -2082 -935 -4058 98 -4184 2187 -93 1553 1143 2685 2287 2093 607 -313 709 -1340 148 -1481 -564 -142 -462 -962 120 -962 1033 2 1610 1420 1015 2496 -814 1471 -2971 1452 -3972 -37 -1601 -2380 159 -5650 3046 -5658 2778 -8 4680 2269 4503 5390 -265 4668 -5206 7028 -9332 4457 -3911 -2438 -4439 -8515 -1057 -12167 l561 -607 -647 327 c-2556 1290 -3718 2796 -4088 5301 -61 411 -201 2166 -311 3898 -312 4898 -436 5802 -952 6962 l-261 588 532 493 c1419 1314 3729 2427 5782 2786 639 112 3218 59 4097 -83z M5574 23846 c-476 -475 -431 -1229 97 -1644 835 -658 1687 739 970 1590 -326 388 -715 407 -1067 54z M12075 23992 c-669 -390 -584 -1598 134 -1896 608 -252 1135 289 1057 1085 -72 728 -654 1124 -1191 811z M7801 22451 c-626 -754 1585 -1648 2460 -995 485 362 676 688 546 932 -155 288 -335 267 -719 -84 -549 -502 -1237 -469 -1728 82 -224 251 -389 271 -559 65z",
|
|
131
131
|
filled: true,
|
|
132
|
-
transform: "translate(
|
|
132
|
+
transform: "translate(0.88 22.8) scale(0.0005714 -0.0005714)",
|
|
133
133
|
},
|
|
134
|
-
{ kind: "path", d: "M7 18h3.5M12.2 18h3M8.5 19.5h6", accent: true },
|
|
135
|
-
{
|
|
136
|
-
kind: "path",
|
|
137
|
-
d: "M4.8 5.8h2.7M4.8 7.4h1.4M18.5 17.1h1.7",
|
|
138
|
-
accent: true,
|
|
139
|
-
},
|
|
140
|
-
{ kind: "circle", cx: 18.5, cy: 4.5, r: 0.8, accent: true, filled: true },
|
|
141
134
|
],
|
|
142
135
|
},
|
|
136
|
+
|
|
143
137
|
workspace: {
|
|
144
138
|
description: "Workspace container — panel with header bar and content",
|
|
145
139
|
elements: [
|
package/src/suite/index.ts
CHANGED
|
@@ -45,11 +45,16 @@ export { TodayCalibrating } from "./components/today-calibrating";
|
|
|
45
45
|
export {
|
|
46
46
|
SuiteThemeProvider,
|
|
47
47
|
useSuiteTheme,
|
|
48
|
+
SUITE_THEME_COOKIE,
|
|
48
49
|
type SuiteResolvedTheme,
|
|
49
50
|
type SuiteTheme,
|
|
50
51
|
type SuiteThemeConfig,
|
|
51
52
|
type SuiteThemeContextValue,
|
|
52
53
|
} from "./components/theme-provider";
|
|
54
|
+
export {
|
|
55
|
+
suiteThemeBootScript,
|
|
56
|
+
type SuiteThemeBootOptions,
|
|
57
|
+
} from "./lib/theme-boot";
|
|
53
58
|
export {
|
|
54
59
|
SuiteAiPanel,
|
|
55
60
|
openSuiteAskAi,
|
|
@@ -147,3 +152,25 @@ export {
|
|
|
147
152
|
SuiteAppLayout,
|
|
148
153
|
type SuiteAppLayoutProps,
|
|
149
154
|
} from "./components/suite-app-layout";
|
|
155
|
+
export {
|
|
156
|
+
SuiteIntegrationPicker,
|
|
157
|
+
type SuiteIntegrationPickerItem,
|
|
158
|
+
type SuiteIntegrationPickerProps,
|
|
159
|
+
} from "./components/suite-integration-picker";
|
|
160
|
+
export {
|
|
161
|
+
SuiteIntegrationRules,
|
|
162
|
+
type SuiteIntegrationEvent,
|
|
163
|
+
type SuiteIntegrationRule,
|
|
164
|
+
type SuiteIntegrationRuleInput,
|
|
165
|
+
type SuiteIntegrationRuleProject,
|
|
166
|
+
type SuiteIntegrationRuleActionType,
|
|
167
|
+
type SuiteIntegrationRulesProps,
|
|
168
|
+
} from "./components/suite-integration-rules";
|
|
169
|
+
export {
|
|
170
|
+
SuiteClientsProjectsDirectory,
|
|
171
|
+
type SuiteClientsProjectsDirectoryProps,
|
|
172
|
+
type SuiteDirectoryClient,
|
|
173
|
+
type SuiteDirectoryFields,
|
|
174
|
+
type SuiteDirectoryProject,
|
|
175
|
+
type SuiteDirectoryProjectFields,
|
|
176
|
+
} from "./components/suite-clients-projects-directory";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { classifySuiteHref } from "./apps";
|
|
3
|
+
import { classifySuiteHref, suiteAppSlugForNotificationSource } from "./apps";
|
|
4
4
|
|
|
5
5
|
const BASES = {
|
|
6
6
|
tides: "http://localhost:3001",
|
|
@@ -38,3 +38,11 @@ describe("classifySuiteHref", () => {
|
|
|
38
38
|
).toBeNull();
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
|
+
|
|
42
|
+
describe("suiteAppSlugForNotificationSource", () => {
|
|
43
|
+
it("maps notification source names to suite apps", () => {
|
|
44
|
+
expect(suiteAppSlugForNotificationSource("portal")).toBe("shellstack");
|
|
45
|
+
expect(suiteAppSlugForNotificationSource("kraken")).toBe("kraken");
|
|
46
|
+
expect(suiteAppSlugForNotificationSource("unknown")).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/suite/lib/apps.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type SuiteAppSlug =
|
|
|
10
10
|
| 'tides'
|
|
11
11
|
| 'turtletime'
|
|
12
12
|
| 'kraken'
|
|
13
|
-
| '
|
|
13
|
+
| 'shelly';
|
|
14
14
|
|
|
15
15
|
export interface SuiteAppDefinition {
|
|
16
16
|
slug: SuiteAppSlug;
|
|
@@ -33,7 +33,7 @@ export const SUITE_APPS: readonly SuiteAppDefinition[] = [
|
|
|
33
33
|
description: 'Organisation and billing',
|
|
34
34
|
icon: '/icon-shellstack.svg',
|
|
35
35
|
landing: '/home',
|
|
36
|
-
baseUrlEnv: '
|
|
36
|
+
baseUrlEnv: 'NEXT_PUBLIC_PORTAL_URL',
|
|
37
37
|
baseUrlFallback: 'http://localhost:3002',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
@@ -64,12 +64,12 @@ export const SUITE_APPS: readonly SuiteAppDefinition[] = [
|
|
|
64
64
|
baseUrlFallback: 'http://localhost:3003',
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
slug: '
|
|
68
|
-
name: '
|
|
67
|
+
slug: 'shelly',
|
|
68
|
+
name: 'Shelly',
|
|
69
69
|
description: 'AI teammates',
|
|
70
|
-
icon: '/
|
|
70
|
+
icon: '/shelly-icon.svg',
|
|
71
71
|
landing: '/today',
|
|
72
|
-
baseUrlEnv: '
|
|
72
|
+
baseUrlEnv: 'NEXT_PUBLIC_SHELLY_URL',
|
|
73
73
|
baseUrlFallback: 'http://localhost:3004',
|
|
74
74
|
},
|
|
75
75
|
] as const;
|
|
@@ -92,11 +92,17 @@ const SOURCE_APP_SLUG: Record<string, SuiteAppSlug> = {
|
|
|
92
92
|
turtletime: 'turtletime',
|
|
93
93
|
tides: 'tides',
|
|
94
94
|
kraken: 'kraken',
|
|
95
|
-
|
|
95
|
+
shelly: 'shelly',
|
|
96
96
|
portal: 'shellstack',
|
|
97
97
|
shellstack: 'shellstack',
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
export function suiteAppSlugForNotificationSource(
|
|
101
|
+
sourceApp: string | null | undefined,
|
|
102
|
+
): SuiteAppSlug | null {
|
|
103
|
+
return SOURCE_APP_SLUG[(sourceApp || '').toLowerCase()] ?? null;
|
|
104
|
+
}
|
|
105
|
+
|
|
100
106
|
/**
|
|
101
107
|
* Suite notification links are often stored as `/dashboard/...` relative to the
|
|
102
108
|
* *source* app. Resolving them with the current product origin opens the wrong
|
|
@@ -110,7 +116,7 @@ export function resolveSuiteNotificationHref(
|
|
|
110
116
|
if (!value) return '';
|
|
111
117
|
if (/^(https?:|mailto:|\/\/)/i.test(value)) return value;
|
|
112
118
|
const path = value.startsWith('/') ? value : `/${value}`;
|
|
113
|
-
const slug =
|
|
119
|
+
const slug = suiteAppSlugForNotificationSource(sourceApp);
|
|
114
120
|
if (!slug) return path;
|
|
115
121
|
return `${suiteAppBaseUrl(slug)}${path}`;
|
|
116
122
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie helpers for suite-wide preferences. Suite apps run on different
|
|
3
|
+
* origins (localhost ports or subdomains), so shared state crosses apps via
|
|
4
|
+
* one host-wide cookie.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function suiteCookieDomainAttr(): string {
|
|
8
|
+
const host = window.location.hostname;
|
|
9
|
+
if (host === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
// tides.localhost / turtletime.localhost must share one cookie.
|
|
13
|
+
if (host.endsWith(".localhost")) {
|
|
14
|
+
return "; Domain=.localhost";
|
|
15
|
+
}
|
|
16
|
+
const known = new Set([
|
|
17
|
+
"tides",
|
|
18
|
+
"kraken",
|
|
19
|
+
"turtletime",
|
|
20
|
+
"time",
|
|
21
|
+
"shelly",
|
|
22
|
+
"app",
|
|
23
|
+
"shellstack",
|
|
24
|
+
"portal",
|
|
25
|
+
]);
|
|
26
|
+
const parts = host.split(".");
|
|
27
|
+
if (parts.length >= 3 && known.has(parts[0] ?? "")) {
|
|
28
|
+
return `; Domain=.${parts.slice(1).join(".")}`;
|
|
29
|
+
}
|
|
30
|
+
if (parts.length >= 2) return `; Domain=.${parts.slice(1).join(".")}`;
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readCookie(name: string): string | null {
|
|
35
|
+
const prefix = `${name}=`;
|
|
36
|
+
for (const part of document.cookie.split(";")) {
|
|
37
|
+
const trimmed = part.trim();
|
|
38
|
+
if (trimmed.startsWith(prefix)) {
|
|
39
|
+
return decodeURIComponent(trimmed.slice(prefix.length));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function writeCookie(name: string, value: string): void {
|
|
46
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
47
|
+
document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax${suiteCookieDomainAttr()}${secure}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function clearCookie(name: string): void {
|
|
51
|
+
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
52
|
+
document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax${suiteCookieDomainAttr()}${secure}`;
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SUITE_THEME_COOKIE } from "./theme-cookie";
|
|
2
|
+
import type { SuiteResolvedTheme } from "../components/theme-provider";
|
|
3
|
+
|
|
4
|
+
export interface SuiteThemeBootOptions {
|
|
5
|
+
/** `data-theme` attribute value for the light theme. */
|
|
6
|
+
lightThemeId: string;
|
|
7
|
+
/** `data-theme` attribute value for the dark theme. */
|
|
8
|
+
darkThemeId: string;
|
|
9
|
+
/** Per-app legacy localStorage key (e.g. `bb-theme`) read before falling back to the OS preference. */
|
|
10
|
+
storageKey?: string;
|
|
11
|
+
/** Optional `<meta id="theme-color-meta">` colours, set alongside the theme. */
|
|
12
|
+
themeColorLight?: string;
|
|
13
|
+
themeColorDark?: string;
|
|
14
|
+
/** Resolved mode when `matchMedia` is unavailable. Defaults to `light`. */
|
|
15
|
+
fallback?: SuiteResolvedTheme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Inline `<head>` script that applies the suite theme before first paint.
|
|
20
|
+
* Mode resolution order: the shared `xenide-suite-theme` cookie (one
|
|
21
|
+
* preference across all suite apps), then the per-app legacy storage key,
|
|
22
|
+
* then the OS `prefers-color-scheme`, then the fallback.
|
|
23
|
+
*/
|
|
24
|
+
export function suiteThemeBootScript(options: SuiteThemeBootOptions): string {
|
|
25
|
+
const light = JSON.stringify(options.lightThemeId);
|
|
26
|
+
const dark = JSON.stringify(options.darkThemeId);
|
|
27
|
+
const storageKey = JSON.stringify(options.storageKey ?? null);
|
|
28
|
+
const metaLight = JSON.stringify(options.themeColorLight ?? null);
|
|
29
|
+
const metaDark = JSON.stringify(options.themeColorDark ?? null);
|
|
30
|
+
const fallback = JSON.stringify(options.fallback ?? "light");
|
|
31
|
+
return `(function(){try{var m=null;var c=document.cookie.match(/(?:^|;\\s*)${SUITE_THEME_COOKIE}=(light|dark|system)/);if(c)m=c[1];var k=${storageKey};if(!m&&k){try{var t=localStorage.getItem(k);if(t==='light'||t==='dark')m=t;}catch(e){}}var d=null;try{d=window.matchMedia('(prefers-color-scheme: dark)').matches;}catch(e){d=${fallback}==='dark';}if(!m)m=d?'dark':'light';else if(m==='system')m=d?'dark':'light';var th=m==='dark'?${dark}:${light};var de=document.documentElement;de.setAttribute('data-theme',th);de.classList.toggle('dark',m==='dark');var meta=document.getElementById('theme-color-meta');if(meta&&(${metaLight})&&(${metaDark}))meta.setAttribute('content',m==='dark'?${metaDark}:${metaLight});}catch(e){document.documentElement.setAttribute('data-theme',${light});}})();`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared suite theme cookie names. Lives in its own module so non-React
|
|
3
|
+
* consumers (boot scripts) can import it without pulling in client code.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SUITE_THEME_COOKIE = 'xenide-suite-theme';
|
|
7
|
+
export const SUITE_THEME_CHANNEL = 'xenide-suite-theme';
|
|
@@ -7,13 +7,15 @@ import {
|
|
|
7
7
|
type PointerEvent as ReactPointerEvent,
|
|
8
8
|
} from "react";
|
|
9
9
|
|
|
10
|
+
import { clearCookie, readCookie, writeCookie } from "./cookies";
|
|
11
|
+
|
|
10
12
|
export const SIDEBAR_RAIL_WIDTH = 56;
|
|
11
13
|
export const SIDEBAR_COLLAPSE_THRESHOLD = 80;
|
|
12
14
|
export const SIDEBAR_MIN_EXPANDED_WIDTH = 180;
|
|
13
15
|
export const SIDEBAR_MAX_WIDTH = 640;
|
|
14
16
|
export const SIDEBAR_DEFAULT_WIDTH = 320;
|
|
15
17
|
|
|
16
|
-
/** Shared across Tides / TurtleTime / Kraken / ShellStack /
|
|
18
|
+
/** Shared across Tides / TurtleTime / Kraken / ShellStack / Shelly. */
|
|
17
19
|
export const SUITE_SIDEBAR_WIDTH_KEY = "shellstack:sidebar-width";
|
|
18
20
|
export const SUITE_SIDEBAR_WIDTH_COOKIE = "shellstack_sidebar_width";
|
|
19
21
|
|
|
@@ -22,7 +24,6 @@ const LEGACY_SIDEBAR_WIDTH_KEYS = [
|
|
|
22
24
|
"tt-sidebar-width",
|
|
23
25
|
"kraken-sidebar-width",
|
|
24
26
|
"shellstack-sidebar-width",
|
|
25
|
-
"nakama-sidebar-width",
|
|
26
27
|
] as const;
|
|
27
28
|
|
|
28
29
|
function isSuiteSidebarKey(storageKey: string): boolean {
|
|
@@ -40,6 +41,7 @@ export function sidebarColumnWidth(
|
|
|
40
41
|
collapsed: boolean,
|
|
41
42
|
_chatOpen = false,
|
|
42
43
|
): number {
|
|
44
|
+
void _chatOpen;
|
|
43
45
|
if (collapsed) return SIDEBAR_RAIL_WIDTH;
|
|
44
46
|
return width;
|
|
45
47
|
}
|
|
@@ -60,54 +62,6 @@ function parseWidth(raw: string | null | undefined): number | null {
|
|
|
60
62
|
return Number.isNaN(parsed) ? null : parsed;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
function suiteCookieDomainAttr(): string {
|
|
64
|
-
const host = window.location.hostname;
|
|
65
|
-
if (host === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) {
|
|
66
|
-
return "";
|
|
67
|
-
}
|
|
68
|
-
// tides.localhost / turtletime.localhost must share one cookie.
|
|
69
|
-
if (host.endsWith(".localhost")) {
|
|
70
|
-
return "; Domain=.localhost";
|
|
71
|
-
}
|
|
72
|
-
const known = new Set([
|
|
73
|
-
"tides",
|
|
74
|
-
"kraken",
|
|
75
|
-
"turtletime",
|
|
76
|
-
"time",
|
|
77
|
-
"nakama",
|
|
78
|
-
"app",
|
|
79
|
-
"shellstack",
|
|
80
|
-
"portal",
|
|
81
|
-
]);
|
|
82
|
-
const parts = host.split(".");
|
|
83
|
-
if (parts.length >= 3 && known.has(parts[0] ?? "")) {
|
|
84
|
-
return `; Domain=.${parts.slice(1).join(".")}`;
|
|
85
|
-
}
|
|
86
|
-
if (parts.length >= 2) return `; Domain=.${parts.slice(1).join(".")}`;
|
|
87
|
-
return "";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function readCookie(name: string): string | null {
|
|
91
|
-
const prefix = `${name}=`;
|
|
92
|
-
for (const part of document.cookie.split(";")) {
|
|
93
|
-
const trimmed = part.trim();
|
|
94
|
-
if (trimmed.startsWith(prefix)) {
|
|
95
|
-
return decodeURIComponent(trimmed.slice(prefix.length));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function writeCookie(name: string, value: string): void {
|
|
102
|
-
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
103
|
-
document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax${suiteCookieDomainAttr()}${secure}`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function clearCookie(name: string): void {
|
|
107
|
-
const secure = window.location.protocol === "https:" ? "; Secure" : "";
|
|
108
|
-
document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax${suiteCookieDomainAttr()}${secure}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
65
|
export function readSidebarWidth(storageKey: string): number | null {
|
|
112
66
|
try {
|
|
113
67
|
if (isSuiteSidebarKey(storageKey)) {
|
|
@@ -173,7 +127,7 @@ export function useSidebarWidth(
|
|
|
173
127
|
const fallbackWidth = snapWidth(clampWidth(defaultWidth));
|
|
174
128
|
// ponytail: always start at the default so SSR and the first client render
|
|
175
129
|
// match. Reading the cookie in useState() hydrates 320 vs 412 and React
|
|
176
|
-
// keeps the server width — TurtleTime / ShellStack /
|
|
130
|
+
// keeps the server width — TurtleTime / ShellStack / Shelly stay stuck.
|
|
177
131
|
const [width, setWidth] = useState(fallbackWidth);
|
|
178
132
|
const [narrowViewport, setNarrowViewport] = useState(false);
|
|
179
133
|
const collapsed = narrowViewport || width <= SIDEBAR_COLLAPSE_THRESHOLD;
|