create-your-stack 0.0.7 โ 0.0.9
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/bin/create-your-stack +163 -14
- package/package.json +1 -1
package/bin/create-your-stack
CHANGED
|
@@ -78,12 +78,11 @@ if [[ "$USE_CLERK" == "true" ]]; then
|
|
|
78
78
|
fi
|
|
79
79
|
|
|
80
80
|
if [[ "$USE_CONVEX" == "true" ]]; then
|
|
81
|
-
DEPS="$DEPS convex"
|
|
82
|
-
DEV_DEPS="$DEV_DEPS convex-helpers"
|
|
81
|
+
DEPS="$DEPS convex convex-helpers"
|
|
83
82
|
fi
|
|
84
83
|
|
|
85
84
|
if [[ "$USE_UNIWIND" == "true" ]]; then
|
|
86
|
-
DEPS="$DEPS uniwind tailwindcss clsx tailwind-merge lucide-react-native react-native-reanimated react-native-worklets @gorhom/bottom-sheet react-native-gesture-handler reanimated-color-picker"
|
|
85
|
+
DEPS="$DEPS uniwind tailwindcss clsx tailwind-merge lucide-react-native react-native-reanimated react-native-worklets @gorhom/bottom-sheet react-native-gesture-handler reanimated-color-picker react-native-keyboard-controller"
|
|
87
86
|
fi
|
|
88
87
|
|
|
89
88
|
echo "Installing main dependencies..."
|
|
@@ -115,6 +114,134 @@ EOT"
|
|
|
115
114
|
|
|
116
115
|
echo "๐ Creating utility files..."
|
|
117
116
|
|
|
117
|
+
echo "๐ฆ Creating stores..."
|
|
118
|
+
cat <<'EOT' > stores/themeStore.ts
|
|
119
|
+
import { create } from 'zustand';
|
|
120
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
121
|
+
import { NativeWindStyleSheet } from 'nativewind';
|
|
122
|
+
import { mmkvStorage } from './mmkv'; // We'll need to make sure this exists or inline it if simple
|
|
123
|
+
|
|
124
|
+
// Simple mmkv wrapper if not existing, but let's assume standard zustand setup or use a simple one for now.
|
|
125
|
+
// Actually, to be safe and self-contained:
|
|
126
|
+
import { MMKV } from 'react-native-mmkv';
|
|
127
|
+
|
|
128
|
+
const storage = new MMKV();
|
|
129
|
+
|
|
130
|
+
export const useThemeStore = create(
|
|
131
|
+
persist(
|
|
132
|
+
(set) => ({
|
|
133
|
+
theme: 'system',
|
|
134
|
+
setTheme: (theme: string) => {
|
|
135
|
+
set({ theme });
|
|
136
|
+
// You might want to add logic here to update NativeWind/Uniwind if needed,
|
|
137
|
+
// but often that's handled by a separate effect or the provider.
|
|
138
|
+
// For Uniwind/NativeWind v4, it usually watches the system or a class.
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
{
|
|
142
|
+
name: 'theme-storage',
|
|
143
|
+
storage: createJSONStorage(() => ({
|
|
144
|
+
setItem: (name, value) => storage.set(name, value),
|
|
145
|
+
getItem: (name) => storage.getString(name),
|
|
146
|
+
removeItem: (name) => storage.delete(name),
|
|
147
|
+
})),
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
EOT
|
|
152
|
+
|
|
153
|
+
echo "๐งฉ Creating components..."
|
|
154
|
+
cat <<'EOT' > components/ThemeSwitcher.tsx
|
|
155
|
+
import { api } from '@/convex/_generated/api';
|
|
156
|
+
import { cn } from '@/lib/cn';
|
|
157
|
+
import { useThemeStore } from '@/stores/themeStore';
|
|
158
|
+
import { useQuery } from 'convex/react';
|
|
159
|
+
import { Moon, Sparkles, Sun } from 'lucide-react-native';
|
|
160
|
+
import React, { useMemo } from 'react';
|
|
161
|
+
import { Pressable, Text, View } from 'react-native';
|
|
162
|
+
import Animated, {
|
|
163
|
+
useAnimatedStyle,
|
|
164
|
+
useSharedValue,
|
|
165
|
+
withTiming
|
|
166
|
+
} from 'react-native-reanimated';
|
|
167
|
+
import { useUniwind } from 'uniwind';
|
|
168
|
+
|
|
169
|
+
type Theme = 'light' | 'dark' | 'premium';
|
|
170
|
+
|
|
171
|
+
const THEMES: { name: Theme; label: string; Icon: React.ElementType }[] = [
|
|
172
|
+
{ name: 'light', label: 'Light', Icon: Sun },
|
|
173
|
+
{ name: 'dark', label: 'Dark', Icon: Moon },
|
|
174
|
+
{ name: 'premium', label: 'Premium', Icon: Sparkles },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
export default function ThemeSwitcher() {
|
|
178
|
+
const subscription = useQuery(api.subscriptions.requirePremium);
|
|
179
|
+
const { theme, hasAdaptiveThemes } = useUniwind();
|
|
180
|
+
const activeTheme = hasAdaptiveThemes && theme === 'premium' ? 'premium' : theme;
|
|
181
|
+
const { setTheme } = useThemeStore();
|
|
182
|
+
|
|
183
|
+
const themeAvailables = useMemo(() => {
|
|
184
|
+
return THEMES.filter(t => t.name === 'premium' ? subscription?.ok : true);
|
|
185
|
+
}, [subscription?.ok]);
|
|
186
|
+
|
|
187
|
+
// Determine active index
|
|
188
|
+
const activeIndex = themeAvailables.findIndex(t => t.name === activeTheme);
|
|
189
|
+
const safeIndex = activeIndex === -1 ? 0 : activeIndex;
|
|
190
|
+
|
|
191
|
+
const pillPosition = useSharedValue(safeIndex);
|
|
192
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
193
|
+
return {
|
|
194
|
+
width: `${100 / themeAvailables.length}%`,
|
|
195
|
+
left: `${(pillPosition.value * 100) / themeAvailables.length}%`,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
pillPosition.value = withTiming(safeIndex, {
|
|
201
|
+
duration: 600,
|
|
202
|
+
});
|
|
203
|
+
}, [safeIndex, pillPosition]);
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<View className="w-full px-5 py-2">
|
|
207
|
+
<View className="h-12 bg-background rounded-full p-1 flex-row relative">
|
|
208
|
+
{/* Animated Background Pill */}
|
|
209
|
+
<Animated.View
|
|
210
|
+
className="absolute top-1 bottom-1 rounded-full bg-primary shadow-sm shadow-black/10"
|
|
211
|
+
style={animatedStyle}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
{themeAvailables.map((t, index) => {
|
|
215
|
+
const isActive = activeTheme === t.name;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<Pressable
|
|
219
|
+
key={t.name + index}
|
|
220
|
+
onPress={() => setTheme(t.name)}
|
|
221
|
+
className="flex-1 items-center justify-center flex-row gap-2 rounded-full z-10"
|
|
222
|
+
>
|
|
223
|
+
<t.Icon
|
|
224
|
+
size={18}
|
|
225
|
+
color={isActive ? (t.name === 'premium' ? '#EAB308' : t.name === 'light' ? '#e6e8ecff' : '#3B82F6') : '#94A3B8'}
|
|
226
|
+
fill={isActive && t.name === 'premium' ? '#EAB308' : 'transparent'}
|
|
227
|
+
/>
|
|
228
|
+
<Text className={cn(
|
|
229
|
+
"text-sm font-semibold transition-colors",
|
|
230
|
+
isActive ? "text-slate-900 dark:text-white light:text-white" : "text-slate-500",
|
|
231
|
+
t.name === 'premium' ? "text-yellow-500" : ""
|
|
232
|
+
|
|
233
|
+
)}>
|
|
234
|
+
{t.label}
|
|
235
|
+
</Text>
|
|
236
|
+
</Pressable>
|
|
237
|
+
);
|
|
238
|
+
})}
|
|
239
|
+
</View>
|
|
240
|
+
</View>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
EOT
|
|
244
|
+
|
|
118
245
|
cat <<'EOT' > lib/cn.ts
|
|
119
246
|
import { clsx, type ClassValue } from 'clsx';
|
|
120
247
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -128,7 +255,8 @@ cat <<'EOT' > lib/clerk.ts
|
|
|
128
255
|
import { ClerkProvider, useAuth } from '@clerk/clerk-expo';
|
|
129
256
|
import { Platform } from 'react-native';
|
|
130
257
|
import { ConvexProviderWithClerk } from 'convex/react-clerk';
|
|
131
|
-
import { ConvexReactClient } from 'convex/react';
|
|
258
|
+
import { ConvexReactClient, useQueries } from 'convex/react';
|
|
259
|
+
import { makeUseQueryWithStatus } from 'convex-helpers/react';
|
|
132
260
|
import * as SecureStore from 'expo-secure-store';
|
|
133
261
|
import Constants from 'expo-constants';
|
|
134
262
|
|
|
@@ -191,6 +319,8 @@ const tokenCache = Platform.OS === 'web' ? {
|
|
|
191
319
|
},
|
|
192
320
|
};
|
|
193
321
|
|
|
322
|
+
export const useQueryWithStatus = makeUseQueryWithStatus(useQueries);
|
|
323
|
+
|
|
194
324
|
export { ClerkProvider, useAuth, ConvexProviderWithClerk, convex, publishableKey, tokenCache };
|
|
195
325
|
EOT
|
|
196
326
|
|
|
@@ -200,18 +330,34 @@ const { getDefaultConfig } = require("expo/metro-config");
|
|
|
200
330
|
const { withUniwindConfig } = require("uniwind/metro");
|
|
201
331
|
|
|
202
332
|
const config = getDefaultConfig(__dirname);
|
|
203
|
-
module.exports = withUniwindConfig(config, {
|
|
333
|
+
module.exports = withUniwindConfig(config, {
|
|
334
|
+
cssEntryFile: './global.css',
|
|
335
|
+
dtsFile: './uniwind-types.d.ts'
|
|
336
|
+
});
|
|
204
337
|
EOT
|
|
205
338
|
|
|
206
|
-
echo "
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
339
|
+
echo "๏ฟฝ Creating app/_layout.tsx with providers..."
|
|
340
|
+
run "cat <<'EOT' > app/_layout.tsx
|
|
341
|
+
import { Slot } from 'expo-router';
|
|
342
|
+
import { ClerkProvider, useAuth, ConvexProviderWithClerk, convex, publishableKey, tokenCache } from '../lib/clerk';
|
|
343
|
+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
344
|
+
import { KeyboardProvider } from 'react-native-keyboard-controller';
|
|
345
|
+
import '../global.css';
|
|
346
|
+
|
|
347
|
+
export default function Layout() {
|
|
348
|
+
return (
|
|
349
|
+
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
350
|
+
<KeyboardProvider>
|
|
351
|
+
<ClerkProvider publishableKey={publishableKey} tokenCache={tokenCache}>
|
|
352
|
+
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
|
|
353
|
+
<Slot />
|
|
354
|
+
</ConvexProviderWithClerk>
|
|
355
|
+
</ClerkProvider>
|
|
356
|
+
</KeyboardProvider>
|
|
357
|
+
</GestureHandlerRootView>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
EOT"
|
|
215
361
|
|
|
216
362
|
echo "๐งช Creating .env.local..."
|
|
217
363
|
run "cat <<'EOT' > .env.local
|
|
@@ -239,6 +385,9 @@ if [[ "$USE_CONVEX" == "true" ]]; then
|
|
|
239
385
|
fi
|
|
240
386
|
fi
|
|
241
387
|
|
|
388
|
+
echo "๐ง Fixing dependencies..."
|
|
389
|
+
run "bunx expo install --fix"
|
|
390
|
+
|
|
242
391
|
echo "โ
Project '$PROJECT_NAME' is ready!"
|
|
243
392
|
echo "๐ Next steps:"
|
|
244
393
|
echo " 1. cd $PROJECT_NAME"
|