@dubsdotapp/expo 0.2.0 → 0.2.2
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/index.d.mts +22 -4
- package/dist/index.d.ts +22 -4
- package/dist/index.js +71 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +86 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/client.ts +9 -0
- package/src/index.ts +2 -1
- package/src/managed-wallet.tsx +10 -0
- package/src/provider.tsx +22 -5
- package/src/types.ts +9 -0
- package/src/ui/AuthGate.tsx +16 -10
- package/src/ui/ConnectWalletScreen.tsx +31 -5
- package/src/ui/index.ts +1 -1
- package/src/ui/theme.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dubsdotapp/expo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "React Native SDK for the Dubs betting platform",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
|
+
"react-native": "./dist/index.js",
|
|
11
12
|
"import": "./dist/index.mjs",
|
|
12
13
|
"require": "./dist/index.js"
|
|
13
14
|
}
|
package/src/client.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
DubsAppUser,
|
|
31
31
|
CheckUsernameResult,
|
|
32
32
|
LiveScore,
|
|
33
|
+
UiConfig,
|
|
33
34
|
} from './types';
|
|
34
35
|
|
|
35
36
|
export interface DubsClientConfig {
|
|
@@ -372,4 +373,12 @@ export class DubsClient {
|
|
|
372
373
|
getErrorCodesLocal(): Record<number, SolanaErrorCode> {
|
|
373
374
|
return { ...SOLANA_PROGRAM_ERRORS };
|
|
374
375
|
}
|
|
376
|
+
|
|
377
|
+
// ── App Config ──
|
|
378
|
+
|
|
379
|
+
/** Fetch the app's UI customization config (accent color, icon, tagline) */
|
|
380
|
+
async getAppConfig(): Promise<UiConfig> {
|
|
381
|
+
const res = await this.request<{ uiConfig: UiConfig }>('GET', '/apps/config');
|
|
382
|
+
return res.uiConfig || {};
|
|
383
|
+
}
|
|
375
384
|
}
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,7 @@ export type {
|
|
|
54
54
|
AuthStatus,
|
|
55
55
|
LiveScore,
|
|
56
56
|
LiveScoreCompetitor,
|
|
57
|
+
UiConfig,
|
|
57
58
|
} from './types';
|
|
58
59
|
|
|
59
60
|
// Provider
|
|
@@ -84,7 +85,7 @@ export type {
|
|
|
84
85
|
} from './hooks';
|
|
85
86
|
|
|
86
87
|
// UI
|
|
87
|
-
export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsTheme } from './ui';
|
|
88
|
+
export { AuthGate, ConnectWalletScreen, UserProfileCard, SettingsSheet, useDubsTheme, mergeTheme } from './ui';
|
|
88
89
|
export type { AuthGateProps, RegistrationScreenProps, ConnectWalletScreenProps, UserProfileCardProps, SettingsSheetProps, DubsTheme } from './ui';
|
|
89
90
|
|
|
90
91
|
// Game widgets
|
package/src/managed-wallet.tsx
CHANGED
|
@@ -22,6 +22,10 @@ interface ManagedWalletProviderProps {
|
|
|
22
22
|
cluster: string;
|
|
23
23
|
storage: TokenStorage;
|
|
24
24
|
renderConnectScreen?: ((props: ConnectWalletScreenProps) => React.ReactNode) | false;
|
|
25
|
+
/** Developer UI config overrides for the connect screen */
|
|
26
|
+
accentColor?: string;
|
|
27
|
+
appIcon?: string;
|
|
28
|
+
tagline?: string;
|
|
25
29
|
children: (adapter: MwaWalletAdapter) => React.ReactNode;
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -32,6 +36,9 @@ export function ManagedWalletProvider({
|
|
|
32
36
|
cluster,
|
|
33
37
|
storage,
|
|
34
38
|
renderConnectScreen,
|
|
39
|
+
accentColor,
|
|
40
|
+
appIcon,
|
|
41
|
+
tagline,
|
|
35
42
|
children,
|
|
36
43
|
}: ManagedWalletProviderProps) {
|
|
37
44
|
const [connected, setConnected] = useState(false);
|
|
@@ -132,6 +139,9 @@ export function ManagedWalletProvider({
|
|
|
132
139
|
connecting,
|
|
133
140
|
error,
|
|
134
141
|
appName,
|
|
142
|
+
accentColor,
|
|
143
|
+
appIcon,
|
|
144
|
+
tagline,
|
|
135
145
|
};
|
|
136
146
|
if (renderConnectScreen) {
|
|
137
147
|
return <>{renderConnectScreen(connectProps)}</>;
|
package/src/provider.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext, useContext, useMemo, useCallback } from 'react';
|
|
1
|
+
import React, { createContext, useContext, useMemo, useCallback, useState, useEffect } from 'react';
|
|
2
2
|
import { Connection } from '@solana/web3.js';
|
|
3
3
|
import { DubsClient } from './client';
|
|
4
4
|
import { NETWORK_CONFIG } from './constants';
|
|
@@ -10,7 +10,7 @@ import { ManagedWalletProvider, useDisconnect } from './managed-wallet';
|
|
|
10
10
|
import { AuthGate } from './ui/AuthGate';
|
|
11
11
|
import type { RegistrationScreenProps } from './ui/AuthGate';
|
|
12
12
|
import type { ConnectWalletScreenProps } from './ui/ConnectWalletScreen';
|
|
13
|
-
import type { AuthStatus } from './types';
|
|
13
|
+
import type { AuthStatus, UiConfig } from './types';
|
|
14
14
|
|
|
15
15
|
// ── Context ──
|
|
16
16
|
|
|
@@ -80,6 +80,12 @@ export function DubsProvider({
|
|
|
80
80
|
const connection = useMemo(() => new Connection(rpcUrl, { commitment: 'confirmed' }), [rpcUrl]);
|
|
81
81
|
const storage = useMemo(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
|
|
82
82
|
|
|
83
|
+
// Fetch developer UI config on mount (silent fail = default theme)
|
|
84
|
+
const [uiConfig, setUiConfig] = useState<UiConfig>({});
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
client.getAppConfig().then(setUiConfig).catch(() => {});
|
|
87
|
+
}, [client]);
|
|
88
|
+
|
|
83
89
|
// ── Path A: External wallet provided (BYOA) ──
|
|
84
90
|
if (externalWallet) {
|
|
85
91
|
return (
|
|
@@ -87,13 +93,14 @@ export function DubsProvider({
|
|
|
87
93
|
client={client}
|
|
88
94
|
connection={connection}
|
|
89
95
|
wallet={externalWallet}
|
|
90
|
-
appName={appName}
|
|
96
|
+
appName={uiConfig.appName || appName}
|
|
91
97
|
network={network}
|
|
92
98
|
storage={storage}
|
|
93
99
|
managed={managed}
|
|
94
100
|
renderLoading={renderLoading}
|
|
95
101
|
renderError={renderError}
|
|
96
102
|
renderRegistration={renderRegistration}
|
|
103
|
+
accentColor={uiConfig.accentColor}
|
|
97
104
|
>
|
|
98
105
|
{children}
|
|
99
106
|
</ExternalWalletProvider>
|
|
@@ -103,22 +110,26 @@ export function DubsProvider({
|
|
|
103
110
|
// ── Path B: Managed MWA wallet ──
|
|
104
111
|
return (
|
|
105
112
|
<ManagedWalletProvider
|
|
106
|
-
appName={appName}
|
|
113
|
+
appName={uiConfig.appName || appName}
|
|
107
114
|
cluster={cluster}
|
|
108
115
|
storage={storage}
|
|
109
116
|
renderConnectScreen={renderConnectScreen}
|
|
117
|
+
accentColor={uiConfig.accentColor}
|
|
118
|
+
appIcon={uiConfig.appIcon}
|
|
119
|
+
tagline={uiConfig.tagline}
|
|
110
120
|
>
|
|
111
121
|
{(adapter) => (
|
|
112
122
|
<ManagedInner
|
|
113
123
|
client={client}
|
|
114
124
|
connection={connection}
|
|
115
125
|
wallet={adapter}
|
|
116
|
-
appName={appName}
|
|
126
|
+
appName={uiConfig.appName || appName}
|
|
117
127
|
network={network}
|
|
118
128
|
storage={storage}
|
|
119
129
|
renderLoading={renderLoading}
|
|
120
130
|
renderError={renderError}
|
|
121
131
|
renderRegistration={renderRegistration}
|
|
132
|
+
accentColor={uiConfig.accentColor}
|
|
122
133
|
>
|
|
123
134
|
{children}
|
|
124
135
|
</ManagedInner>
|
|
@@ -139,6 +150,7 @@ function ManagedInner({
|
|
|
139
150
|
renderLoading,
|
|
140
151
|
renderError,
|
|
141
152
|
renderRegistration,
|
|
153
|
+
accentColor,
|
|
142
154
|
children,
|
|
143
155
|
}: {
|
|
144
156
|
client: DubsClient;
|
|
@@ -150,6 +162,7 @@ function ManagedInner({
|
|
|
150
162
|
renderLoading?: (status: AuthStatus) => React.ReactNode;
|
|
151
163
|
renderError?: (error: Error, retry: () => void) => React.ReactNode;
|
|
152
164
|
renderRegistration?: (props: RegistrationScreenProps) => React.ReactNode;
|
|
165
|
+
accentColor?: string;
|
|
153
166
|
children: React.ReactNode;
|
|
154
167
|
}) {
|
|
155
168
|
const managedDisconnect = useDisconnect();
|
|
@@ -178,6 +191,7 @@ function ManagedInner({
|
|
|
178
191
|
renderError={renderError}
|
|
179
192
|
renderRegistration={renderRegistration}
|
|
180
193
|
appName={appName}
|
|
194
|
+
accentColor={accentColor}
|
|
181
195
|
>
|
|
182
196
|
{children}
|
|
183
197
|
</AuthGate>
|
|
@@ -198,6 +212,7 @@ function ExternalWalletProvider({
|
|
|
198
212
|
renderLoading,
|
|
199
213
|
renderError,
|
|
200
214
|
renderRegistration,
|
|
215
|
+
accentColor,
|
|
201
216
|
children,
|
|
202
217
|
}: {
|
|
203
218
|
client: DubsClient;
|
|
@@ -210,6 +225,7 @@ function ExternalWalletProvider({
|
|
|
210
225
|
renderLoading?: (status: AuthStatus) => React.ReactNode;
|
|
211
226
|
renderError?: (error: Error, retry: () => void) => React.ReactNode;
|
|
212
227
|
renderRegistration?: (props: RegistrationScreenProps) => React.ReactNode;
|
|
228
|
+
accentColor?: string;
|
|
213
229
|
children: React.ReactNode;
|
|
214
230
|
}) {
|
|
215
231
|
const disconnect = useCallback(async () => {
|
|
@@ -240,6 +256,7 @@ function ExternalWalletProvider({
|
|
|
240
256
|
renderError={renderError}
|
|
241
257
|
renderRegistration={renderRegistration}
|
|
242
258
|
appName={appName}
|
|
259
|
+
accentColor={accentColor}
|
|
243
260
|
>
|
|
244
261
|
{children}
|
|
245
262
|
</AuthGate>
|
package/src/types.ts
CHANGED
|
@@ -373,3 +373,12 @@ export interface LiveScore {
|
|
|
373
373
|
statusDetail: string;
|
|
374
374
|
};
|
|
375
375
|
}
|
|
376
|
+
|
|
377
|
+
// ── UI Config (developer branding) ──
|
|
378
|
+
|
|
379
|
+
export interface UiConfig {
|
|
380
|
+
accentColor?: string;
|
|
381
|
+
appIcon?: string;
|
|
382
|
+
appName?: string;
|
|
383
|
+
tagline?: string;
|
|
384
|
+
}
|
package/src/ui/AuthGate.tsx
CHANGED
|
@@ -56,6 +56,8 @@ export interface AuthGateProps {
|
|
|
56
56
|
renderError?: (error: Error, retry: () => void) => React.ReactNode;
|
|
57
57
|
renderRegistration?: (props: RegistrationScreenProps) => React.ReactNode;
|
|
58
58
|
appName?: string;
|
|
59
|
+
/** Override accent color for registration screens (from developer UI config) */
|
|
60
|
+
accentColor?: string;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// ── AuthGate Component ──
|
|
@@ -68,6 +70,7 @@ export function AuthGate({
|
|
|
68
70
|
renderError,
|
|
69
71
|
renderRegistration,
|
|
70
72
|
appName = 'Dubs',
|
|
73
|
+
accentColor,
|
|
71
74
|
}: AuthGateProps) {
|
|
72
75
|
const { client } = useDubs();
|
|
73
76
|
const auth = useAuth();
|
|
@@ -143,6 +146,7 @@ export function AuthGate({
|
|
|
143
146
|
error={regError}
|
|
144
147
|
client={client}
|
|
145
148
|
appName={appName}
|
|
149
|
+
accentColor={accentColor}
|
|
146
150
|
/>
|
|
147
151
|
);
|
|
148
152
|
}
|
|
@@ -256,8 +260,10 @@ function DefaultRegistrationScreen({
|
|
|
256
260
|
error,
|
|
257
261
|
client,
|
|
258
262
|
appName,
|
|
259
|
-
|
|
263
|
+
accentColor,
|
|
264
|
+
}: RegistrationScreenProps & { appName: string; accentColor?: string }) {
|
|
260
265
|
const t = useDubsTheme();
|
|
266
|
+
const accent = accentColor || t.accent;
|
|
261
267
|
|
|
262
268
|
// ── Shared state ──
|
|
263
269
|
const [step, setStep] = useState(0);
|
|
@@ -325,7 +331,7 @@ function DefaultRegistrationScreen({
|
|
|
325
331
|
<StepIndicator currentStep={0} />
|
|
326
332
|
|
|
327
333
|
<View style={s.avatarCenter}>
|
|
328
|
-
<View style={[s.avatarFrame, { borderColor:
|
|
334
|
+
<View style={[s.avatarFrame, { borderColor: accent }]}>
|
|
329
335
|
<Image source={{ uri: avatarUrl }} style={s.avatarLarge} />
|
|
330
336
|
<View style={[s.checkBadge, { backgroundColor: t.success }]}>
|
|
331
337
|
<Text style={s.checkBadgeText}>✓</Text>
|
|
@@ -342,11 +348,11 @@ function DefaultRegistrationScreen({
|
|
|
342
348
|
<Text style={[s.outlineBtnText, { color: t.text }]}>↻ Shuffle</Text>
|
|
343
349
|
</TouchableOpacity>
|
|
344
350
|
<TouchableOpacity
|
|
345
|
-
style={[s.outlineBtn, { borderColor:
|
|
351
|
+
style={[s.outlineBtn, { borderColor: accent, backgroundColor: accent + '15' }]}
|
|
346
352
|
onPress={() => setShowStyles(!showStyles)}
|
|
347
353
|
activeOpacity={0.7}
|
|
348
354
|
>
|
|
349
|
-
<Text style={[s.outlineBtnText, { color:
|
|
355
|
+
<Text style={[s.outlineBtnText, { color: accent }]}>☺ Customize</Text>
|
|
350
356
|
</TouchableOpacity>
|
|
351
357
|
</View>
|
|
352
358
|
|
|
@@ -356,7 +362,7 @@ function DefaultRegistrationScreen({
|
|
|
356
362
|
<TouchableOpacity
|
|
357
363
|
key={st}
|
|
358
364
|
onPress={() => setAvatarStyle(st)}
|
|
359
|
-
style={[s.styleThumbWrap, { borderColor: st === avatarStyle ?
|
|
365
|
+
style={[s.styleThumbWrap, { borderColor: st === avatarStyle ? accent : t.border }]}
|
|
360
366
|
>
|
|
361
367
|
<Image source={{ uri: getAvatarUrl(st, avatarSeed, 80) }} style={s.styleThumb} />
|
|
362
368
|
</TouchableOpacity>
|
|
@@ -367,7 +373,7 @@ function DefaultRegistrationScreen({
|
|
|
367
373
|
|
|
368
374
|
<View style={s.bottomRow}>
|
|
369
375
|
<TouchableOpacity
|
|
370
|
-
style={[s.primaryBtn, { backgroundColor:
|
|
376
|
+
style={[s.primaryBtn, { backgroundColor: accent, flex: 1 }]}
|
|
371
377
|
onPress={() => animateToStep(1)}
|
|
372
378
|
activeOpacity={0.8}
|
|
373
379
|
>
|
|
@@ -391,7 +397,7 @@ function DefaultRegistrationScreen({
|
|
|
391
397
|
<StepIndicator currentStep={1} />
|
|
392
398
|
|
|
393
399
|
<View style={s.avatarCenter}>
|
|
394
|
-
<View style={[s.avatarFrameSmall, { borderColor:
|
|
400
|
+
<View style={[s.avatarFrameSmall, { borderColor: accent }]}>
|
|
395
401
|
<Image source={{ uri: avatarUrl }} style={s.avatarSmall} />
|
|
396
402
|
<View style={[s.checkBadgeSm, { backgroundColor: t.success }]}>
|
|
397
403
|
<Text style={s.checkBadgeTextSm}>✓</Text>
|
|
@@ -404,7 +410,7 @@ function DefaultRegistrationScreen({
|
|
|
404
410
|
Username <Text style={{ color: t.errorText }}>*</Text>
|
|
405
411
|
</Text>
|
|
406
412
|
<TextInput
|
|
407
|
-
style={[s.input, { backgroundColor: t.surface, color: t.text, borderColor:
|
|
413
|
+
style={[s.input, { backgroundColor: t.surface, color: t.text, borderColor: accent }]}
|
|
408
414
|
placeholder="Enter username"
|
|
409
415
|
placeholderTextColor={t.textDim}
|
|
410
416
|
value={username}
|
|
@@ -434,7 +440,7 @@ function DefaultRegistrationScreen({
|
|
|
434
440
|
<Text style={[s.secondaryBtnText, { color: t.text }]}>‹ Back</Text>
|
|
435
441
|
</TouchableOpacity>
|
|
436
442
|
<TouchableOpacity
|
|
437
|
-
style={[s.primaryBtn, { backgroundColor:
|
|
443
|
+
style={[s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: canContinueUsername ? 1 : 0.4 }]}
|
|
438
444
|
onPress={() => animateToStep(2)}
|
|
439
445
|
disabled={!canContinueUsername}
|
|
440
446
|
activeOpacity={0.8}
|
|
@@ -506,7 +512,7 @@ function DefaultRegistrationScreen({
|
|
|
506
512
|
<Text style={[s.secondaryBtnText, { color: t.text }]}>‹ Back</Text>
|
|
507
513
|
</TouchableOpacity>
|
|
508
514
|
<TouchableOpacity
|
|
509
|
-
style={[s.primaryBtn, { backgroundColor:
|
|
515
|
+
style={[s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: registering ? 0.7 : 1 }]}
|
|
510
516
|
onPress={handleSubmit}
|
|
511
517
|
disabled={registering}
|
|
512
518
|
activeOpacity={0.8}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
TouchableOpacity,
|
|
6
6
|
ActivityIndicator,
|
|
7
7
|
StyleSheet,
|
|
8
|
+
Image,
|
|
8
9
|
} from 'react-native';
|
|
9
10
|
import { useDubsTheme } from './theme';
|
|
10
11
|
|
|
@@ -17,6 +18,12 @@ export interface ConnectWalletScreenProps {
|
|
|
17
18
|
error?: string | null;
|
|
18
19
|
/** App name shown in the header. Defaults to "Dubs" */
|
|
19
20
|
appName?: string;
|
|
21
|
+
/** Override accent color (e.g. from developer UI config) */
|
|
22
|
+
accentColor?: string;
|
|
23
|
+
/** URL to app icon — renders as Image instead of the default letter circle */
|
|
24
|
+
appIcon?: string;
|
|
25
|
+
/** Override subtitle text below app name */
|
|
26
|
+
tagline?: string;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
export function ConnectWalletScreen({
|
|
@@ -24,20 +31,33 @@ export function ConnectWalletScreen({
|
|
|
24
31
|
connecting = false,
|
|
25
32
|
error = null,
|
|
26
33
|
appName = 'Dubs',
|
|
34
|
+
accentColor,
|
|
35
|
+
appIcon,
|
|
36
|
+
tagline,
|
|
27
37
|
}: ConnectWalletScreenProps) {
|
|
28
38
|
const t = useDubsTheme();
|
|
39
|
+
const accent = accentColor || t.accent;
|
|
29
40
|
|
|
30
41
|
return (
|
|
31
42
|
<View style={[styles.container, { backgroundColor: t.background }]}>
|
|
32
43
|
<View style={styles.content}>
|
|
33
44
|
{/* Branding */}
|
|
34
45
|
<View style={styles.brandingSection}>
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
46
|
+
{appIcon ? (
|
|
47
|
+
<Image
|
|
48
|
+
source={{ uri: appIcon }}
|
|
49
|
+
style={styles.logoImage}
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<View style={[styles.logoCircle, { backgroundColor: accent }]}>
|
|
53
|
+
<Text style={styles.logoText}>
|
|
54
|
+
{appName.charAt(0).toUpperCase()}
|
|
55
|
+
</Text>
|
|
56
|
+
</View>
|
|
57
|
+
)}
|
|
38
58
|
<Text style={[styles.appName, { color: t.text }]}>{appName}</Text>
|
|
39
59
|
<Text style={[styles.subtitle, { color: t.textMuted }]}>
|
|
40
|
-
Connect your Solana wallet to get started
|
|
60
|
+
{tagline || 'Connect your Solana wallet to get started'}
|
|
41
61
|
</Text>
|
|
42
62
|
</View>
|
|
43
63
|
|
|
@@ -55,7 +75,7 @@ export function ConnectWalletScreen({
|
|
|
55
75
|
) : null}
|
|
56
76
|
|
|
57
77
|
<TouchableOpacity
|
|
58
|
-
style={[styles.connectButton, { backgroundColor:
|
|
78
|
+
style={[styles.connectButton, { backgroundColor: accent }]}
|
|
59
79
|
onPress={onConnect}
|
|
60
80
|
disabled={connecting}
|
|
61
81
|
activeOpacity={0.8}
|
|
@@ -100,6 +120,12 @@ const styles = StyleSheet.create({
|
|
|
100
120
|
alignItems: 'center',
|
|
101
121
|
marginBottom: 8,
|
|
102
122
|
},
|
|
123
|
+
logoImage: {
|
|
124
|
+
width: 80,
|
|
125
|
+
height: 80,
|
|
126
|
+
borderRadius: 16,
|
|
127
|
+
marginBottom: 8,
|
|
128
|
+
},
|
|
103
129
|
logoText: {
|
|
104
130
|
fontSize: 36,
|
|
105
131
|
fontWeight: '800',
|
package/src/ui/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { UserProfileCard } from './UserProfileCard';
|
|
|
6
6
|
export type { UserProfileCardProps } from './UserProfileCard';
|
|
7
7
|
export { SettingsSheet } from './SettingsSheet';
|
|
8
8
|
export type { SettingsSheetProps } from './SettingsSheet';
|
|
9
|
-
export { useDubsTheme } from './theme';
|
|
9
|
+
export { useDubsTheme, mergeTheme } from './theme';
|
|
10
10
|
export type { DubsTheme } from './theme';
|
|
11
11
|
|
|
12
12
|
// Game widgets
|
package/src/ui/theme.ts
CHANGED
|
@@ -55,3 +55,8 @@ export function useDubsTheme(): DubsTheme {
|
|
|
55
55
|
const scheme = useColorScheme();
|
|
56
56
|
return scheme === 'light' ? light : dark;
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
/** Merge overrides into a base theme (e.g. custom accent color from developer config) */
|
|
60
|
+
export function mergeTheme(base: DubsTheme, overrides: Partial<DubsTheme>): DubsTheme {
|
|
61
|
+
return { ...base, ...overrides };
|
|
62
|
+
}
|