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.
Files changed (2) hide show
  1. package/bin/create-your-stack +163 -14
  2. package/package.json +1 -1
@@ -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, { input: "./global.css" });
333
+ module.exports = withUniwindConfig(config, {
334
+ cssEntryFile: './global.css',
335
+ dtsFile: './uniwind-types.d.ts'
336
+ });
204
337
  EOT
205
338
 
206
- echo "๐Ÿ”Œ Injecting global.css into _layout..."
207
- for layout in app/_layout.tsx app/_layout.js; do
208
- if [[ -f "$layout" ]] && ! grep -q "global.css" "$layout"; then
209
- tmp="${layout}.tmp"
210
- echo "import '../global.css';" > "$tmp"
211
- cat "$layout" >> "$tmp"
212
- mv "$tmp" "$layout"
213
- fi
214
- done
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"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-your-stack",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "bin": {