Skip to content

Commit 1f73bcf

Browse files
feat(mobile): migrate theming to Uniwind Pro
- Add generated theme definitions and architecture checks - Replace legacy theme hooks with runtime Uniwind theme switching - Update mobile components and build configuration for Uniwind Pro
1 parent cd096b9 commit 1f73bcf

103 files changed

Lines changed: 3628 additions & 1396 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/mobile/babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ module.exports = function (api) {
22
api.cache(true);
33
return {
44
presets: [["babel-preset-expo", { unstable_transformImportMeta: true }]],
5+
plugins: ["react-native-worklets/plugin"],
56
};
67
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
"t3-code-light",
3+
"t3-code-dark",
4+
"t3-chat-light",
5+
"t3-chat-dark",
6+
"grove-light",
7+
"grove-dark",
8+
"ocean-light",
9+
"ocean-dark",
10+
"ember-light",
11+
"ember-dark",
12+
"iris-light",
13+
"iris-dark"
14+
]

apps/mobile/generated-uniwind-themes.css

Lines changed: 1726 additions & 0 deletions
Large diffs are not rendered by default.

apps/mobile/global.css

Lines changed: 1 addition & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,210 +1,12 @@
11
@import "tailwindcss";
22
@import "uniwind";
3+
@import "./generated-uniwind-themes.css";
34

4-
/* ─── Theme tokens ──────────────────────────────────────────────────── */
55
@layer theme {
66
:root {
77
@variant android {
88
--font-mono: "monospace";
99
}
10-
11-
@variant light {
12-
/* Page backgrounds */
13-
--color-screen: #f2f2f7;
14-
--color-sheet: rgba(242, 242, 247, 0.98);
15-
--color-sheet-solid: #f2f2f7;
16-
17-
/* Card / surface */
18-
--color-card: #ffffff;
19-
--color-card-alt: #f5f5f5;
20-
--color-card-translucent: rgba(255, 255, 255, 0.8);
21-
22-
/* Text */
23-
--color-foreground: #262626;
24-
--color-foreground-secondary: #525252;
25-
--color-foreground-muted: #737373;
26-
--color-foreground-tertiary: #8e8e93;
27-
28-
/* Borders & separators */
29-
--color-border: rgba(0, 0, 0, 0.08);
30-
--color-border-subtle: rgba(0, 0, 0, 0.06);
31-
--color-separator: rgba(0, 0, 0, 0.04);
32-
33-
/* Subtle backgrounds (badges, pills, overlays) */
34-
--color-subtle: rgba(0, 0, 0, 0.04);
35-
--color-subtle-strong: rgba(0, 0, 0, 0.08);
36-
--color-inline-skill-background: rgba(217, 70, 239, 0.12);
37-
--color-inline-skill-border: rgba(217, 70, 239, 0.25);
38-
--color-inline-skill-foreground: #a21caf;
39-
40-
/* Primary action */
41-
--color-primary: #262626;
42-
--color-primary-foreground: #ffffff;
43-
--color-primary-shadow: #000000;
44-
45-
/* Secondary action */
46-
--color-secondary: #ffffff;
47-
--color-secondary-foreground: #262626;
48-
--color-secondary-border: rgba(0, 0, 0, 0.08);
49-
--color-switch-active-track: #34c759;
50-
--color-switch-active-thumb: #ffffff;
51-
--color-switch-inactive-track: rgba(0, 0, 0, 0.08);
52-
--color-switch-inactive-thumb: #8e8e93;
53-
54-
/* Danger */
55-
--color-danger: #fef2f2;
56-
--color-danger-border: rgba(239, 68, 68, 0.12);
57-
--color-danger-foreground: #dc2626;
58-
59-
/* Inputs */
60-
--color-input: #ffffff;
61-
--color-input-border: rgba(0, 0, 0, 0.1);
62-
--color-sidebar-search: rgba(118, 118, 128, 0.12);
63-
--color-placeholder: #737373;
64-
65-
/* Icons */
66-
--color-icon: #262626;
67-
--color-icon-muted: #525252;
68-
--color-icon-subtle: #a3a3a3;
69-
70-
/* Header / glass chrome */
71-
--color-header: rgba(255, 255, 255, 0.97);
72-
--color-header-border: rgba(0, 0, 0, 0.06);
73-
--color-glass-surface: rgba(255, 255, 255, 0.72);
74-
--color-glass-tint: rgba(255, 255, 255, 0.18);
75-
76-
/* StatusBar */
77-
--color-status-bar: #f2f2f7;
78-
79-
/* Markdown */
80-
--color-md-body: #111111;
81-
--color-md-strong: #000000;
82-
--color-md-link: #2563eb;
83-
--color-md-blockquote-border: rgba(0, 0, 0, 0.08);
84-
--color-md-blockquote-bg: rgba(0, 0, 0, 0.02);
85-
--color-md-code-bg: rgba(0, 0, 0, 0.04);
86-
--color-md-code-text: #262626;
87-
--color-md-user-code-bg: rgba(255, 255, 255, 0.22);
88-
--color-md-user-code-text: #ffffff;
89-
--color-md-user-fence-bg: rgba(0, 0, 0, 0.16);
90-
--color-md-user-fence-text: #ffffff;
91-
--color-md-hr: rgba(0, 0, 0, 0.08);
92-
93-
/* iMessage-style user bubble */
94-
--color-user-bubble: #007aff;
95-
--color-user-bubble-foreground: #ffffff;
96-
--color-user-bubble-foreground-muted: rgba(255, 255, 255, 0.78);
97-
--color-user-bubble-skill-foreground: #f0abfc;
98-
99-
/* Drawer / modal backdrop */
100-
--color-backdrop: rgba(0, 0, 0, 0.22);
101-
--color-drawer: rgba(255, 255, 255, 0.99);
102-
--color-drawer-shadow: rgba(0, 0, 0, 0.12);
103-
104-
/* Misc */
105-
--color-dot-separator: rgba(0, 0, 0, 0.2);
106-
--color-wordmark: #262626;
107-
--color-chevron: rgba(0, 0, 0, 0.2);
108-
}
109-
110-
@variant dark {
111-
/* Page backgrounds */
112-
--color-screen: #0a0a0a;
113-
--color-sheet: rgba(14, 14, 14, 0.98);
114-
--color-sheet-solid: #0e0e0e;
115-
116-
/* Card / surface */
117-
--color-card: #171717;
118-
--color-card-alt: #1c1c1c;
119-
--color-card-translucent: rgba(17, 17, 17, 0.8);
120-
121-
/* Text */
122-
--color-foreground: #f5f5f5;
123-
--color-foreground-secondary: #a3a3a3;
124-
--color-foreground-muted: #8e8e93;
125-
--color-foreground-tertiary: #636366;
126-
127-
/* Borders & separators */
128-
--color-border: rgba(255, 255, 255, 0.06);
129-
--color-border-subtle: rgba(255, 255, 255, 0.04);
130-
--color-separator: rgba(255, 255, 255, 0.03);
131-
132-
/* Subtle backgrounds (badges, pills, overlays) */
133-
--color-subtle: rgba(255, 255, 255, 0.04);
134-
--color-subtle-strong: rgba(255, 255, 255, 0.08);
135-
--color-inline-skill-background: rgba(217, 70, 239, 0.12);
136-
--color-inline-skill-border: rgba(217, 70, 239, 0.25);
137-
--color-inline-skill-foreground: #f0abfc;
138-
139-
/* Primary action */
140-
--color-primary: #f5f5f5;
141-
--color-primary-foreground: #0a0a0a;
142-
--color-primary-shadow: #000000;
143-
144-
/* Secondary action */
145-
--color-secondary: rgba(255, 255, 255, 0.04);
146-
--color-secondary-foreground: #f5f5f5;
147-
--color-secondary-border: rgba(255, 255, 255, 0.06);
148-
--color-switch-active-track: #30d158;
149-
--color-switch-active-thumb: #ffffff;
150-
--color-switch-inactive-track: rgba(255, 255, 255, 0.06);
151-
--color-switch-inactive-thumb: #8e8e93;
152-
153-
/* Danger */
154-
--color-danger: rgba(239, 68, 68, 0.14);
155-
--color-danger-border: rgba(248, 113, 113, 0.18);
156-
--color-danger-foreground: #fca5a5;
157-
158-
/* Inputs */
159-
--color-input: #141414;
160-
--color-input-border: rgba(255, 255, 255, 0.08);
161-
--color-sidebar-search: rgba(118, 118, 128, 0.24);
162-
--color-placeholder: #8e8e93;
163-
164-
/* Icons */
165-
--color-icon: #f5f5f5;
166-
--color-icon-muted: #a3a3a3;
167-
--color-icon-subtle: #8e8e93;
168-
169-
/* Header / glass chrome */
170-
--color-header: rgba(10, 10, 10, 0.97);
171-
--color-header-border: rgba(255, 255, 255, 0.06);
172-
--color-glass-surface: rgba(23, 23, 23, 0.78);
173-
--color-glass-tint: rgba(23, 23, 23, 0.24);
174-
175-
/* StatusBar */
176-
--color-status-bar: #0a0a0a;
177-
178-
/* Markdown */
179-
--color-md-body: #e5e5e5;
180-
--color-md-strong: #f5f5f5;
181-
--color-md-link: #60a5fa;
182-
--color-md-blockquote-border: rgba(255, 255, 255, 0.1);
183-
--color-md-blockquote-bg: rgba(255, 255, 255, 0.03);
184-
--color-md-code-bg: rgba(255, 255, 255, 0.06);
185-
--color-md-code-text: #e5e5e5;
186-
--color-md-user-code-bg: rgba(255, 255, 255, 0.18);
187-
--color-md-user-code-text: #ffffff;
188-
--color-md-user-fence-bg: rgba(0, 0, 0, 0.28);
189-
--color-md-user-fence-text: #ffffff;
190-
--color-md-hr: rgba(255, 255, 255, 0.08);
191-
192-
/* iMessage-style user bubble */
193-
--color-user-bubble: #0a84ff;
194-
--color-user-bubble-foreground: #ffffff;
195-
--color-user-bubble-foreground-muted: rgba(255, 255, 255, 0.78);
196-
--color-user-bubble-skill-foreground: #f0abfc;
197-
198-
/* Drawer / modal backdrop */
199-
--color-backdrop: rgba(0, 0, 0, 0.48);
200-
--color-drawer: rgba(14, 14, 14, 0.99);
201-
--color-drawer-shadow: rgba(0, 0, 0, 0.32);
202-
203-
/* Misc */
204-
--color-dot-separator: rgba(255, 255, 255, 0.2);
205-
--color-wordmark: #f5f5f5;
206-
--color-chevron: rgba(255, 255, 255, 0.2);
207-
}
20810
}
20911
}
21012

apps/mobile/metro.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const fs = require("node:fs");
22
const path = require("node:path");
33
const { getDefaultConfig } = require("expo/metro-config");
44
const { withUniwindConfig } = require("uniwind/metro");
5+
const extraThemes = require("./generated-uniwind-theme-names.json");
56

67
/** @type {import("expo/metro-config").MetroConfig} */
78
const config = getDefaultConfig(__dirname);
@@ -50,5 +51,6 @@ config.resolver = {
5051

5152
module.exports = withUniwindConfig(config, {
5253
cssEntryFile: "./global.css",
54+
extraThemes,
5355
polyfills: { rem: 14 },
5456
});

apps/mobile/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
"config:prod": "APP_VARIANT=production expo config",
4040
"profile:android:hermes": "mkdir -p profiles/review && react-native profile-hermes profiles/review",
4141
"sync:pierre-icons": "node modules/t3-markdown-text/scripts/sync-pierre-file-icons.mjs",
42+
"themes:check": "node scripts/generateUniwindThemes.cjs --check && node scripts/checkUniwindThemeArchitecture.cjs",
43+
"themes:generate": "node scripts/generateUniwindThemes.cjs",
4244
"test": "vp test run",
4345
"typecheck": "tsc --noEmit"
4446
},
@@ -108,7 +110,7 @@
108110
"react-native-image-viewing": "^0.2.2",
109111
"react-native-keyboard-controller": "1.21.13",
110112
"react-native-nitro-markdown": "^0.5.0",
111-
"react-native-nitro-modules": "0.35.9",
113+
"react-native-nitro-modules": "0.36.5",
112114
"react-native-reanimated": "4.3.1",
113115
"react-native-safe-area-context": "~5.7.0",
114116
"react-native-screens": "4.25.2",
@@ -118,7 +120,7 @@
118120
"react-native-worklets": "0.8.3",
119121
"shiki": "4.2.0",
120122
"tailwind-merge": "^3.5.0",
121-
"uniwind": "^1.6.2"
123+
"uniwind": "file:./uniwind-pro-demo-ios-rn.0.85.3.tgz"
122124
},
123125
"devDependencies": {
124126
"@effect/vitest": "catalog:",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const fs = require("node:fs");
2+
const path = require("node:path");
3+
4+
const SOURCE_ROOT = path.resolve(__dirname, "../src");
5+
const INTEROP_ALLOWLIST = new Set([
6+
"features/archive/ArchivedThreadsScreen.tsx",
7+
"features/connection/ConnectionsNewRouteScreen.tsx",
8+
"features/files/FileMarkdownPreview.tsx",
9+
"features/files/ThreadFilesRouteScreen.tsx",
10+
"features/files/thread-file-navigator-pane.tsx",
11+
"features/home/HomeHeader.tsx",
12+
"features/review/ReviewSheet.tsx",
13+
"features/settings/SettingsEnvironmentsRouteScreen.tsx",
14+
"features/settings/appearance/components/AppearancePreviews.tsx",
15+
"features/settings/appearance/components/FontSizeSliderRow.tsx",
16+
"features/threads/NewTaskContextPickerScreens.tsx",
17+
"features/threads/NewTaskDraftScreen.tsx",
18+
"features/threads/ThreadComposer.tsx",
19+
"features/threads/ThreadFeed.tsx",
20+
"features/threads/ThreadNavigationSidebar.tsx",
21+
"features/threads/ThreadSettingsSheet.tsx",
22+
"features/threads/git/GitOverviewSheet.tsx",
23+
"features/threads/thread-list-items.tsx",
24+
"features/threads/thread-list-v2-items.tsx",
25+
"lib/useUniwindTheme.ts",
26+
"native/T3ComposerEditor.ios.tsx",
27+
"native/T3ComposerEditor.native.tsx",
28+
]);
29+
30+
function sourceFiles(directory) {
31+
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
32+
const target = path.join(directory, entry.name);
33+
if (entry.isDirectory()) return sourceFiles(target);
34+
return /\.[cm]?[jt]sx?$/.test(entry.name) ? [target] : [];
35+
});
36+
}
37+
38+
const violations = [];
39+
for (const filename of sourceFiles(SOURCE_ROOT)) {
40+
const relative = path.relative(SOURCE_ROOT, filename);
41+
const source = fs.readFileSync(filename, "utf8");
42+
43+
if (/\buseCSSVariable\s*\(/.test(source)) {
44+
violations.push(`${relative}: useCSSVariable bypasses the Pro ShadowTree path`);
45+
}
46+
if (/\buseThemeColor\s*\(/.test(source)) {
47+
violations.push(`${relative}: useThemeColor was replaced by Uniwind classes`);
48+
}
49+
if (source.includes("useUniwindTheme") && !INTEROP_ALLOWLIST.has(relative)) {
50+
violations.push(
51+
`${relative}: add theme styling through className or document this interop boundary`,
52+
);
53+
}
54+
}
55+
56+
if (violations.length > 0) {
57+
console.error(violations.join("\n"));
58+
process.exitCode = 1;
59+
}

0 commit comments

Comments
 (0)