@multiplatform.one/theme 6.0.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/CHANGELOG.md +18 -0
- package/LICENSE +201 -0
- package/package.json +85 -0
- package/src/audit/baselines/home-login.json +171 -0
- package/src/audit/baselines/home-login.png +0 -0
- package/src/audit/baselines/home-pokemon.json +355 -0
- package/src/audit/baselines/home-pokemon.png +0 -0
- package/src/audit/baselines/home.json +194 -0
- package/src/audit/baselines/home.png +0 -0
- package/src/audit/baselines/pokemon.json +251 -0
- package/src/audit/baselines/pokemon.png +0 -0
- package/src/audit/constraintAudit.ts +874 -0
- package/src/audit/diffReport.spec.ts +191 -0
- package/src/audit/diffReport.ts +344 -0
- package/src/audit/index.ts +10 -0
- package/src/font.ts +49 -0
- package/src/fonts/createFontLoader.ts +15 -0
- package/src/fonts/createFontLoader.web.ts +9 -0
- package/src/fonts/index.ts +2 -0
- package/src/fonts/types.ts +13 -0
- package/src/index.ts +3 -0
- package/src/theme/Intent.tsx +30 -0
- package/src/theme/Preset.tsx +93 -0
- package/src/theme/PresetContext.ts +27 -0
- package/src/theme/Tint.tsx +44 -0
- package/src/theme/__snapshots__/resolveKnobs.spec.ts.snap +69 -0
- package/src/theme/animations/css.ts +25 -0
- package/src/theme/animations/index.ts +1 -0
- package/src/theme/animations/index.web.ts +1 -0
- package/src/theme/animations/reactNative.ts +54 -0
- package/src/theme/colorRules.spec.ts +195 -0
- package/src/theme/colorRules.ts +166 -0
- package/src/theme/cookies.spec.ts +185 -0
- package/src/theme/cookies.ts +209 -0
- package/src/theme/createDefaultThemeConfig.ts +86 -0
- package/src/theme/createThemes.ts +286 -0
- package/src/theme/devtools/ColorLineVisualizer.tsx +261 -0
- package/src/theme/devtools/ThemeDevtoolsPanel.tsx +1657 -0
- package/src/theme/devtools/buildSnapshot.spec.ts +113 -0
- package/src/theme/devtools/buildSnapshot.ts +103 -0
- package/src/theme/devtools/knobIcons.tsx +217 -0
- package/src/theme/devtools/vizConstants.spec.ts +58 -0
- package/src/theme/devtools/vizConstants.ts +28 -0
- package/src/theme/index.ts +21 -0
- package/src/theme/intent.spec.tsx +276 -0
- package/src/theme/intents.ts +22 -0
- package/src/theme/knobs.ts +147 -0
- package/src/theme/pokemonScreen.spec.ts +109 -0
- package/src/theme/preset.spec.tsx +242 -0
- package/src/theme/preset.types.ts +22 -0
- package/src/theme/presets.spec.ts +252 -0
- package/src/theme/presets.ts +49 -0
- package/src/theme/recipes.ts +69 -0
- package/src/theme/resolveKnobs.spec.ts +571 -0
- package/src/theme/resolveKnobs.ts +226 -0
- package/src/theme/shared.spec.ts +84 -0
- package/src/theme/shared.tsx +131 -0
- package/src/theme/theme.native.tsx +65 -0
- package/src/theme/theme.tsx +190 -0
- package/src/theme/tint.spec.tsx +179 -0
- package/src/theme/useColorScale.native.ts +7 -0
- package/src/theme/useColorScale.ts +29 -0
- package/src/theme/useResolvedKnobs.ts +121 -0
- package/src/theme/useTheme.native.ts +49 -0
- package/src/theme/useTheme.ts +213 -0
- package/tsconfig.json +11 -0
- package/vitest.config.mjs +9 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach } from "vitest";
|
|
2
|
+
import { defaultKnobs, type Knobs } from "./knobs";
|
|
3
|
+
import {
|
|
4
|
+
splitCookieValue,
|
|
5
|
+
joinSplitCookieValues,
|
|
6
|
+
readOverridesCookie,
|
|
7
|
+
readPresetCookie,
|
|
8
|
+
writePresetCookie,
|
|
9
|
+
writeOverridesCookie,
|
|
10
|
+
computeOverrides,
|
|
11
|
+
getCookieWatchList,
|
|
12
|
+
cookiePreset,
|
|
13
|
+
cookieOverridesPrefix,
|
|
14
|
+
} from "./cookies";
|
|
15
|
+
|
|
16
|
+
describe("splitCookieValue", () => {
|
|
17
|
+
it("returns a single chunk for small values", () => {
|
|
18
|
+
const result = splitCookieValue("small");
|
|
19
|
+
expect(result).toEqual(["small"]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("splits values exceeding 3500 chars into chunks", () => {
|
|
23
|
+
const large = "x".repeat(7500);
|
|
24
|
+
const chunks = splitCookieValue(large);
|
|
25
|
+
expect(chunks.length).toBe(3);
|
|
26
|
+
expect(chunks[0].length).toBe(3500);
|
|
27
|
+
expect(chunks[1].length).toBe(3500);
|
|
28
|
+
expect(chunks[2].length).toBe(500);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("handles value at exact threshold boundary", () => {
|
|
32
|
+
const exact = "a".repeat(3500);
|
|
33
|
+
expect(splitCookieValue(exact)).toEqual([exact]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("handles value one over threshold", () => {
|
|
37
|
+
const over = "b".repeat(3501);
|
|
38
|
+
const chunks = splitCookieValue(over);
|
|
39
|
+
expect(chunks.length).toBe(2);
|
|
40
|
+
expect(chunks[0].length).toBe(3500);
|
|
41
|
+
expect(chunks[1].length).toBe(1);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("joinSplitCookieValues", () => {
|
|
46
|
+
it("joins chunks back together", () => {
|
|
47
|
+
expect(joinSplitCookieValues(["abc", "def", "ghi"])).toBe("abcdefghi");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("handles single chunk", () => {
|
|
51
|
+
expect(joinSplitCookieValues(["only"])).toBe("only");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("handles empty array", () => {
|
|
55
|
+
expect(joinSplitCookieValues([])).toBe("");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("splitCookieValue + joinSplitCookieValues roundtrip", () => {
|
|
60
|
+
it("reconstructs original value after split and join", () => {
|
|
61
|
+
const original = "y".repeat(8000);
|
|
62
|
+
const reconstructed = joinSplitCookieValues(splitCookieValue(original));
|
|
63
|
+
expect(reconstructed).toBe(original);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("readOverridesCookie", () => {
|
|
68
|
+
it("returns empty object when no chunks exist", () => {
|
|
69
|
+
expect(readOverridesCookie({})).toEqual({});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("reads a single chunk override", () => {
|
|
73
|
+
const overrides: Partial<Knobs> = { fillStyle: "outlined" };
|
|
74
|
+
const cookies: Record<string, string> = {
|
|
75
|
+
"mp.ov.0": JSON.stringify(overrides),
|
|
76
|
+
};
|
|
77
|
+
expect(readOverridesCookie(cookies)).toEqual(overrides);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("reads and joins multiple chunks", () => {
|
|
81
|
+
const overrides: Partial<Knobs> = { fillStyle: "outlined", size: "large" };
|
|
82
|
+
const json = JSON.stringify(overrides);
|
|
83
|
+
const mid = Math.floor(json.length / 2);
|
|
84
|
+
const cookies: Record<string, string> = {
|
|
85
|
+
"mp.ov.0": json.slice(0, mid),
|
|
86
|
+
"mp.ov.1": json.slice(mid),
|
|
87
|
+
};
|
|
88
|
+
expect(readOverridesCookie(cookies)).toEqual(overrides);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("returns empty object for malformed JSON", () => {
|
|
92
|
+
const cookies: Record<string, string> = {
|
|
93
|
+
"mp.ov.0": "not{valid json",
|
|
94
|
+
};
|
|
95
|
+
expect(readOverridesCookie(cookies)).toEqual({});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("stops reading at first missing chunk", () => {
|
|
99
|
+
const cookies: Record<string, string> = {
|
|
100
|
+
"mp.ov.0": '{"fill',
|
|
101
|
+
"mp.ov.2": 'Style":"outlined"}',
|
|
102
|
+
};
|
|
103
|
+
expect(readOverridesCookie(cookies)).toEqual({});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("readPresetCookie", () => {
|
|
108
|
+
it("returns undefined when cookie is missing", () => {
|
|
109
|
+
expect(readPresetCookie({})).toBeUndefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("returns undefined for empty string", () => {
|
|
113
|
+
expect(readPresetCookie({ [cookiePreset]: "" })).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("returns the preset name", () => {
|
|
117
|
+
expect(readPresetCookie({ [cookiePreset]: "dark-mode" })).toBe("dark-mode");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("writePresetCookie", () => {
|
|
122
|
+
it("returns the correct key and value", () => {
|
|
123
|
+
expect(writePresetCookie("my-preset")).toEqual({
|
|
124
|
+
key: cookiePreset,
|
|
125
|
+
value: "my-preset",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("writeOverridesCookie", () => {
|
|
131
|
+
it("returns a single chunk for small overrides", () => {
|
|
132
|
+
const overrides: Partial<Knobs> = { fillStyle: "outlined" };
|
|
133
|
+
const { toSet, toRemove } = writeOverridesCookie(overrides);
|
|
134
|
+
expect(toSet).toHaveLength(1);
|
|
135
|
+
expect(toSet[0].key).toBe("mp.ov.0");
|
|
136
|
+
expect(JSON.parse(toSet[0].value)).toEqual(overrides);
|
|
137
|
+
expect(toRemove.length).toBeGreaterThan(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns cleanup keys for indices beyond chunks written", () => {
|
|
141
|
+
const { toSet, toRemove } = writeOverridesCookie({});
|
|
142
|
+
expect(toSet).toHaveLength(1);
|
|
143
|
+
expect(toRemove[0]).toBe("mp.ov.1");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("handles empty overrides", () => {
|
|
147
|
+
const { toSet } = writeOverridesCookie({});
|
|
148
|
+
expect(JSON.parse(toSet[0].value)).toEqual({});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("computeOverrides", () => {
|
|
153
|
+
it("returns empty object when current matches base", () => {
|
|
154
|
+
expect(computeOverrides(defaultKnobs, { ...defaultKnobs })).toEqual({});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("returns only changed keys", () => {
|
|
158
|
+
const current: Knobs = { ...defaultKnobs, fillStyle: "outlined", size: "large" };
|
|
159
|
+
const overrides = computeOverrides(defaultKnobs, current);
|
|
160
|
+
expect(overrides).toEqual({ fillStyle: "outlined", size: "large" });
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("does not include unchanged keys", () => {
|
|
164
|
+
const current: Knobs = { ...defaultKnobs, fillStyle: "outlined" };
|
|
165
|
+
const overrides = computeOverrides(defaultKnobs, current);
|
|
166
|
+
expect(Object.keys(overrides)).toEqual(["fillStyle"]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("getCookieWatchList", () => {
|
|
171
|
+
it("includes the preset cookie key", () => {
|
|
172
|
+
const list = getCookieWatchList();
|
|
173
|
+
expect(list).toContain(cookiePreset);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("includes chunked override keys", () => {
|
|
177
|
+
const list = getCookieWatchList();
|
|
178
|
+
expect(list).toContain(`${cookieOverridesPrefix}.0`);
|
|
179
|
+
expect(list).toContain(`${cookieOverridesPrefix}.1`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("returns 11 keys (1 preset + 10 chunks)", () => {
|
|
183
|
+
expect(getCookieWatchList()).toHaveLength(11);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie utilities for the semantic component theming system.
|
|
3
|
+
*
|
|
4
|
+
* Uses two cookie families:
|
|
5
|
+
* - `mp.preset` — preset name string
|
|
6
|
+
* - `mp.ov.0`, `mp.ov.1`, … — JSON of only changed knobs (partial Knobs),
|
|
7
|
+
* chunked to stay under 4 KB per cookie
|
|
8
|
+
*
|
|
9
|
+
* Old cookie formats (`mp.overrides`, `mp.overrides0`, …) are NOT migrated —
|
|
10
|
+
* stale cookies are silently ignored/overwritten.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getCookie, setCookie, deleteCookie } from "@multiplatform.one/platform";
|
|
14
|
+
import type { Knobs } from "./knobs";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Cookie key constants
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/** Cookie key for the active preset name. */
|
|
21
|
+
export const cookiePreset = "mp.preset";
|
|
22
|
+
|
|
23
|
+
/** Cookie key prefix for knob overrides (chunked). */
|
|
24
|
+
export const cookieOverridesPrefix = "mp.ov";
|
|
25
|
+
|
|
26
|
+
/** Maximum size (in characters) before splitting a cookie value. ~3.5 KB */
|
|
27
|
+
const cookieSplitThreshold = 3500;
|
|
28
|
+
|
|
29
|
+
/** Upper bound on the number of split chunks we scan for. */
|
|
30
|
+
const maxChunks = 10;
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Cookie splitting / reconstruction
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Split a cookie value string into chunks if it exceeds the threshold.
|
|
38
|
+
* Returns an array of chunks — if the value is small enough, the array
|
|
39
|
+
* contains a single element.
|
|
40
|
+
*/
|
|
41
|
+
export function splitCookieValue(value: string): string[] {
|
|
42
|
+
if (value.length <= cookieSplitThreshold) {
|
|
43
|
+
return [value];
|
|
44
|
+
}
|
|
45
|
+
const chunks: string[] = [];
|
|
46
|
+
for (let i = 0; i < value.length; i += cookieSplitThreshold) {
|
|
47
|
+
chunks.push(value.slice(i, i + cookieSplitThreshold));
|
|
48
|
+
}
|
|
49
|
+
return chunks;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Reconstruct a cookie value from its split chunks.
|
|
54
|
+
*/
|
|
55
|
+
export function joinSplitCookieValues(chunks: string[]): string {
|
|
56
|
+
return chunks.join("");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Internal: chunk key helpers
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/** Build the cookie key for a given chunk index: `mp.ov.0`, `mp.ov.1`, … */
|
|
64
|
+
function chunkKey(index: number): string {
|
|
65
|
+
return `${cookieOverridesPrefix}.${index}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Cookie read helpers (work with raw cookie objects from react-cookie)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read the overrides cookie value, handling chunked cookies.
|
|
74
|
+
* Accepts the cookies object from `useCookies()`.
|
|
75
|
+
*/
|
|
76
|
+
export function readOverridesCookie(cookies: Record<string, string | undefined>): Partial<Knobs> {
|
|
77
|
+
// Reconstruct from chunked cookies: mp.ov.0, mp.ov.1, …
|
|
78
|
+
const chunks: string[] = [];
|
|
79
|
+
for (let i = 0; i < maxChunks; i++) {
|
|
80
|
+
const chunk = cookies[chunkKey(i)];
|
|
81
|
+
if (!chunk) break;
|
|
82
|
+
chunks.push(chunk);
|
|
83
|
+
}
|
|
84
|
+
if (chunks.length > 0) {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(joinSplitCookieValues(chunks));
|
|
87
|
+
} catch {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Read the preset name from the cookie.
|
|
97
|
+
*/
|
|
98
|
+
export function readPresetCookie(cookies: Record<string, string | undefined>): string | undefined {
|
|
99
|
+
return cookies[cookiePreset] || undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Cookie write helpers
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Write the preset name cookie.
|
|
108
|
+
* Returns the cookie key and value for the caller to pass to setCookie.
|
|
109
|
+
*/
|
|
110
|
+
export function writePresetCookie(presetName: string): { key: string; value: string } {
|
|
111
|
+
return { key: cookiePreset, value: presetName };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Write overrides cookie, splitting if necessary.
|
|
116
|
+
* Returns an array of { key, value } pairs for the caller to pass to setCookie.
|
|
117
|
+
* Also returns keys to remove (old split cookies that are no longer needed).
|
|
118
|
+
*/
|
|
119
|
+
export function writeOverridesCookie(overrides: Partial<Knobs>): {
|
|
120
|
+
toSet: Array<{ key: string; value: string }>;
|
|
121
|
+
toRemove: string[];
|
|
122
|
+
} {
|
|
123
|
+
const json = JSON.stringify(overrides);
|
|
124
|
+
const chunks = splitCookieValue(json);
|
|
125
|
+
|
|
126
|
+
const toSet = chunks.map((chunk, i) => ({
|
|
127
|
+
key: chunkKey(i),
|
|
128
|
+
value: chunk,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
// Clean up any leftover chunks beyond what we wrote
|
|
132
|
+
const toRemove: string[] = [];
|
|
133
|
+
for (let i = chunks.length; i < chunks.length + maxChunks; i++) {
|
|
134
|
+
toRemove.push(chunkKey(i));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { toSet, toRemove };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Overrides diff
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Compute the diff between a base knobs object and the current knobs to
|
|
146
|
+
* produce minimal overrides. Only includes keys where the value differs.
|
|
147
|
+
*/
|
|
148
|
+
export function computeOverrides(base: Knobs, current: Knobs): Partial<Knobs> {
|
|
149
|
+
const overrides: Partial<Knobs> = {};
|
|
150
|
+
for (const key of Object.keys(base) as Array<keyof Knobs>) {
|
|
151
|
+
if (current[key] !== base[key]) {
|
|
152
|
+
(overrides as Record<string, unknown>)[key] = current[key];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return overrides;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Convenience persistence functions
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Build the list of cookie keys to watch for react-cookie.
|
|
164
|
+
*/
|
|
165
|
+
export function getCookieWatchList(): string[] {
|
|
166
|
+
const keys = [cookiePreset];
|
|
167
|
+
for (let i = 0; i < maxChunks; i++) {
|
|
168
|
+
keys.push(chunkKey(i));
|
|
169
|
+
}
|
|
170
|
+
return keys;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Persist knob overrides to document cookies directly (no react-cookie).
|
|
175
|
+
* Useful outside of React component trees.
|
|
176
|
+
*/
|
|
177
|
+
export function persistPresetOverrides(overrides: Partial<Knobs>): void {
|
|
178
|
+
const { toSet, toRemove } = writeOverridesCookie(overrides);
|
|
179
|
+
for (const { key, value } of toSet) {
|
|
180
|
+
setCookie(key, value, { days: 365, sameSite: "Strict" });
|
|
181
|
+
}
|
|
182
|
+
for (const key of toRemove) {
|
|
183
|
+
deleteCookie(key);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Load knob overrides from document cookies directly (no react-cookie).
|
|
189
|
+
* Useful outside of React component trees and during SSR when given raw
|
|
190
|
+
* cookie headers.
|
|
191
|
+
*/
|
|
192
|
+
export function loadPresetOverrides(
|
|
193
|
+
rawCookies?: Record<string, string | undefined>,
|
|
194
|
+
): Partial<Knobs> {
|
|
195
|
+
const cookies = rawCookies ?? parseBrowserCookies();
|
|
196
|
+
return readOverridesCookie(cookies);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Read the cookies relevant to the theme system into a key/value map.
|
|
201
|
+
*/
|
|
202
|
+
function parseBrowserCookies(): Record<string, string | undefined> {
|
|
203
|
+
const map: Record<string, string | undefined> = {};
|
|
204
|
+
for (const key of getCookieWatchList()) {
|
|
205
|
+
const value = getCookie(key);
|
|
206
|
+
if (value !== null) map[key] = value;
|
|
207
|
+
}
|
|
208
|
+
return map;
|
|
209
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defaultConfig, mediaQueryDefaultActive } from "@tamagui/config/v5";
|
|
2
|
+
import { shorthands } from "@tamagui/shorthands";
|
|
3
|
+
import { tokens } from "@tamagui/themes";
|
|
4
|
+
import type { CreateTamaguiProps, GenericFont } from "tamagui";
|
|
5
|
+
import { animations } from "./animations/index";
|
|
6
|
+
import {
|
|
7
|
+
type BaseThemeBuilderDefinition,
|
|
8
|
+
type CreateThemesBuilderOptions,
|
|
9
|
+
type ThemeBuilderDefinition,
|
|
10
|
+
createThemesBuilder,
|
|
11
|
+
} from "./createThemes";
|
|
12
|
+
import type { Preset } from "./preset.types";
|
|
13
|
+
import { type ThemeConfig, createThemeConfig } from "./shared";
|
|
14
|
+
|
|
15
|
+
export interface CreateDefaultThemeConfigOptions {
|
|
16
|
+
/** Base theme palette (dark/light with optional shadows) */
|
|
17
|
+
base: BaseThemeBuilderDefinition;
|
|
18
|
+
/** Accent theme palette (defaults to base if not provided) */
|
|
19
|
+
accent?: ThemeBuilderDefinition;
|
|
20
|
+
/** Map of extra named themes with their palettes */
|
|
21
|
+
additionalThemes?: Record<string, ThemeBuilderDefinition>;
|
|
22
|
+
/** Font definitions */
|
|
23
|
+
fonts?: Record<string, GenericFont>;
|
|
24
|
+
/** Animation config (defaults to framework reanimated animations) */
|
|
25
|
+
animations?: CreateTamaguiProps["animations"];
|
|
26
|
+
/** Options for the theme builder (grandChildrenThemes, getTheme) */
|
|
27
|
+
builderOptions?: CreateThemesBuilderOptions;
|
|
28
|
+
/** Override tamagui settings */
|
|
29
|
+
settings?: Partial<NonNullable<CreateTamaguiProps["settings"]>>;
|
|
30
|
+
/** Override any top-level tamagui config property */
|
|
31
|
+
tamagui?: Partial<CreateTamaguiProps>;
|
|
32
|
+
/** Named theme presets for the preset system */
|
|
33
|
+
presets?: Record<string, Preset>;
|
|
34
|
+
/** Default preset name to use when no `mp.preset` cookie is set */
|
|
35
|
+
defaultPreset?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createDefaultThemeConfig(options: CreateDefaultThemeConfigOptions): ThemeConfig {
|
|
39
|
+
const accent = options.accent ?? options.base;
|
|
40
|
+
const themesBuilder = createThemesBuilder(options.base, accent, options.builderOptions);
|
|
41
|
+
const builtThemes: Record<string, Record<string, string>> = {
|
|
42
|
+
...themesBuilder.themes(),
|
|
43
|
+
};
|
|
44
|
+
if (options.additionalThemes) {
|
|
45
|
+
for (const [name, palette] of Object.entries(options.additionalThemes)) {
|
|
46
|
+
Object.assign(builtThemes, themesBuilder.addTheme(name, palette));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// NOTE: Previously this stripped themes in production client builds
|
|
50
|
+
// (TAMAGUI_ENVIRONMENT=client && NODE_ENV=production) assuming CSS-only
|
|
51
|
+
// theming. This optimization breaks builds served outside Tamagui's SSR
|
|
52
|
+
// pipeline (e.g. the Frappe SPA) where runtime JS themes are required.
|
|
53
|
+
// Always keep themes to ensure TamaguiProvider has valid theme data.
|
|
54
|
+
const themes = builtThemes;
|
|
55
|
+
return createThemeConfig(
|
|
56
|
+
{
|
|
57
|
+
...defaultConfig,
|
|
58
|
+
animations: options.animations ?? animations,
|
|
59
|
+
disableSSR: false,
|
|
60
|
+
fonts: options.fonts ?? defaultConfig.fonts,
|
|
61
|
+
shorthands,
|
|
62
|
+
shouldAddPrefersColorThemes: true,
|
|
63
|
+
themeClassNameOnRoot: true,
|
|
64
|
+
themes,
|
|
65
|
+
allowedStyleValues: undefined,
|
|
66
|
+
tokens,
|
|
67
|
+
settings: {
|
|
68
|
+
...defaultConfig.settings,
|
|
69
|
+
allowedStyleValues: "somewhat-strict-web",
|
|
70
|
+
defaultFont: "body",
|
|
71
|
+
fastSchemeChange: true,
|
|
72
|
+
maxDarkLightNesting: 2,
|
|
73
|
+
mediaQueryDefaultActive,
|
|
74
|
+
onlyAllowShorthands: false,
|
|
75
|
+
shouldAddPrefersColorThemes: true,
|
|
76
|
+
themeClassNameOnRoot: true,
|
|
77
|
+
...options.settings,
|
|
78
|
+
},
|
|
79
|
+
...options.tamagui,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
presets: options.presets,
|
|
83
|
+
defaultPreset: options.defaultPreset,
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
}
|