newcandies 0.1.5 → 0.1.7

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/dist/index.js +226 -60
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -151,11 +151,13 @@ async function writeBaseline({ dest, name, withQuery }) {
151
151
  "react-native-worklets": "0.5.1",
152
152
  // Styling
153
153
  tailwindcss: "^4.1.16",
154
- uniwind: "^1.0.0"
154
+ uniwind: "^1.0.0",
155
+ "@expo/vector-icons": "latest"
155
156
  };
156
157
  if (withQuery) {
157
158
  baseDeps["@tanstack/react-query"] = "latest";
158
159
  }
160
+ baseDeps["tailwind-merge"] = "latest";
159
161
  const pkg = {
160
162
  name,
161
163
  version: "0.0.0",
@@ -169,7 +171,8 @@ async function writeBaseline({ dest, name, withQuery }) {
169
171
  dependencies: baseDeps,
170
172
  devDependencies: {
171
173
  "@types/react": "~19.1.0",
172
- typescript: "~5.9.2"
174
+ typescript: "~5.9.2",
175
+ "babel-plugin-module-resolver": "latest"
173
176
  }
174
177
  };
175
178
  await fs.writeJSON(path.join(dest, "package.json"), pkg, { spaces: 2 });
@@ -209,7 +212,7 @@ async function writeBaseline({ dest, name, withQuery }) {
209
212
  output: "static",
210
213
  favicon: "./assets/images/favicon.png"
211
214
  },
212
- plugins: ["expo-router"],
215
+ plugins: [["expo-router", { appRoot: "./src/app" }]],
213
216
  experiments: {
214
217
  typedRoutes: true
215
218
  }
@@ -221,6 +224,7 @@ async function writeBaseline({ dest, name, withQuery }) {
221
224
  return {
222
225
  presets: ['babel-preset-expo'],
223
226
  plugins: [
227
+ ['module-resolver', { alias: { '~': './' } }],
224
228
  'react-native-reanimated/plugin'
225
229
  ]
226
230
  };
@@ -239,22 +243,79 @@ module.exports = withUniwindConfig(config, {
239
243
  `;
240
244
  await fs.writeFile(path.join(dest, "metro.config.js"), metro);
241
245
  const tsconfig = {
246
+ extends: "expo/tsconfig.base",
242
247
  compilerOptions: {
243
248
  strict: true,
244
- jsx: "react-native",
245
- target: "ES2020",
246
- moduleResolution: "node",
247
- types: ["react", "react-native"]
249
+ paths: {
250
+ "~/*": ["./*"]
251
+ }
248
252
  },
249
- include: ["app", "./uniwind-types.d.ts", "./expo-env.d.ts"]
253
+ include: [
254
+ "**/*.ts",
255
+ "**/*.tsx",
256
+ ".expo/types/**/*.ts",
257
+ "expo-env.d.ts",
258
+ "./uniwind-types.d.ts"
259
+ ]
250
260
  };
251
261
  await fs.writeJSON(path.join(dest, "tsconfig.json"), tsconfig, { spaces: 2 });
252
262
  const expoEnv = `/// <reference types="expo" />
253
263
  /// <reference types="expo-router" />
254
264
  `;
255
265
  await fs.writeFile(path.join(dest, "expo-env.d.ts"), expoEnv);
256
- const appDir = path.join(dest, "app");
266
+ const srcDir = path.join(dest, "src");
267
+ const appDir = path.join(srcDir, "app");
257
268
  await fs.ensureDir(appDir);
269
+ await fs.ensureDir(path.join(srcDir, "core"));
270
+ await fs.ensureDir(path.join(srcDir, "api"));
271
+ await fs.ensureDir(path.join(srcDir, "lib"));
272
+ await fs.ensureDir(path.join(srcDir, "types"));
273
+ await fs.ensureDir(path.join(srcDir, "components", "screens", "home"));
274
+ await fs.ensureDir(path.join(srcDir, "components", "screens", "profile"));
275
+ await fs.ensureDir(path.join(srcDir, "components", "core"));
276
+ const textCore = `import { Text as RNText, TextProps } from 'react-native';
277
+ import { twMerge } from 'tailwind-merge';
278
+
279
+ type TypographyVariant =
280
+ | 'title'
281
+ | 'subtitle'
282
+ | 'body'
283
+ | 'caption'
284
+ | 'button'
285
+ | 'display'
286
+ | 'caption-primary'
287
+ | 'body-primary'
288
+ | 'subtitle-primary';
289
+
290
+ interface TextComponentProps extends TextProps {
291
+ className?: string;
292
+ variant?: TypographyVariant;
293
+ }
294
+
295
+ const variantStyles: Record<TypographyVariant, string> = {
296
+ title: 'text-2xl font-bold',
297
+ subtitle: 'text-xl font-semibold',
298
+ 'subtitle-primary': 'text-xl font-semibold text-primary',
299
+ body: 'text-base',
300
+ 'body-primary': 'text-base text-primary',
301
+ caption: 'text-sm font-medium',
302
+ 'caption-primary': 'text-sm text-primary font-medium',
303
+ button: 'text-xl text-primary font-semibold text-white text-center',
304
+ display: 'text-3x font-bold',
305
+ };
306
+
307
+ const Text = ({ variant = 'body', children, className, ...props }: TextComponentProps) => {
308
+ const textStyle = twMerge('text-black', variantStyles[variant], className);
309
+ return (
310
+ <RNText className={textStyle} {...props}>
311
+ {children}
312
+ </RNText>
313
+ );
314
+ };
315
+
316
+ export default Text;
317
+ `;
318
+ await fs.writeFile(path.join(srcDir, "components", "core", "text.tsx"), textCore);
258
319
  const easJson = {
259
320
  cli: { version: ">= 7.0.0" },
260
321
  build: {
@@ -285,80 +346,185 @@ module.exports = withUniwindConfig(config, {
285
346
  `;
286
347
  await fs.writeFile(path.join(dest, "global.css"), globalCss);
287
348
  {
288
- const rootLayout = withQuery ? `import 'react-native-gesture-handler';
289
- import '../global.css';
290
- import { Slot } from 'expo-router';
291
- import { GestureHandlerRootView } from 'react-native-gesture-handler';
292
- import { SafeAreaProvider } from 'react-native-safe-area-context';
293
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
349
+ const rootLayout = withQuery ? `import FontAwesome from "@expo/vector-icons/FontAwesome";
350
+ import { useFonts } from "expo-font";
351
+ import { Stack } from "expo-router";
352
+ import * as SplashScreen from "expo-splash-screen";
353
+ import { useEffect } from "react";
354
+ import "react-native-reanimated";
355
+ import "../../global.css";
356
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
357
+
358
+ export { ErrorBoundary } from "expo-router";
359
+
360
+ export const unstable_settings = {
361
+ initialRouteName: "(tabs)",
362
+ };
363
+
364
+ SplashScreen.preventAutoHideAsync();
294
365
 
295
366
  const queryClient = new QueryClient();
296
367
 
297
- const RootLayout = () => {
368
+ export default function RootLayout() {
369
+ const [loaded, error] = useFonts({
370
+ SpaceMono: require("../../assets/fonts/SpaceMono-Regular.ttf"),
371
+ ...FontAwesome.font,
372
+ });
373
+
374
+ useEffect(() => {
375
+ if (error) throw error;
376
+ }, [error]);
377
+
378
+ useEffect(() => {
379
+ if (loaded) {
380
+ SplashScreen.hideAsync();
381
+ }
382
+ }, [loaded]);
383
+
384
+ if (!loaded) {
385
+ return null;
386
+ }
387
+
298
388
  return (
299
- <GestureHandlerRootView style={{ flex: 1 }}>
300
- <SafeAreaProvider>
301
- <QueryClientProvider client={queryClient}>
302
- <Slot />
303
- </QueryClientProvider>
304
- </SafeAreaProvider>
305
- </GestureHandlerRootView>
389
+ <QueryClientProvider client={queryClient}>
390
+ <RootLayoutNav />
391
+ </QueryClientProvider>
306
392
  );
307
- };
308
-
309
- export default RootLayout;
310
- ` : `import 'react-native-gesture-handler';
311
- import '../global.css';
312
- import { Slot } from 'expo-router';
313
- import { GestureHandlerRootView } from 'react-native-gesture-handler';
314
- import { SafeAreaProvider } from 'react-native-safe-area-context';
393
+ }
315
394
 
316
- const RootLayout = () => {
395
+ function RootLayoutNav() {
317
396
  return (
318
- <GestureHandlerRootView style={{ flex: 1 }}>
319
- <SafeAreaProvider>
320
- <Slot />
321
- </SafeAreaProvider>
322
- </GestureHandlerRootView>
397
+ <Stack>
398
+ <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
399
+ <Stack.Screen name="modal" options={{ presentation: "modal" }} />
400
+ </Stack>
323
401
  );
402
+ }
403
+ ` : `import FontAwesome from "@expo/vector-icons/FontAwesome";
404
+ import { useFonts } from "expo-font";
405
+ import { Stack } from "expo-router";
406
+ import * as SplashScreen from "expo-splash-screen";
407
+ import { useEffect } from "react";
408
+ import "react-native-reanimated";
409
+ import "../../global.css";
410
+
411
+ export { ErrorBoundary } from "expo-router";
412
+
413
+ export const unstable_settings = {
414
+ initialRouteName: "(tabs)",
324
415
  };
325
416
 
326
- export default RootLayout;
417
+ SplashScreen.preventAutoHideAsync();
418
+
419
+ export default function RootLayout() {
420
+ const [loaded, error] = useFonts({
421
+ SpaceMono: require("../../assets/fonts/SpaceMono-Regular.ttf"),
422
+ ...FontAwesome.font,
423
+ });
424
+
425
+ useEffect(() => {
426
+ if (error) throw error;
427
+ }, [error]);
428
+
429
+ useEffect(() => {
430
+ if (loaded) {
431
+ SplashScreen.hideAsync();
432
+ }
433
+ }, [loaded]);
434
+
435
+ if (!loaded) {
436
+ return null;
437
+ }
438
+
439
+ return <RootLayoutNav />;
440
+ }
441
+
442
+ function RootLayoutNav() {
443
+ return (
444
+ <Stack>
445
+ <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
446
+ <Stack.Screen name="modal" options={{ presentation: "modal" }} />
447
+ </Stack>
448
+ );
449
+ }
327
450
  `;
328
451
  await fs.writeFile(path.join(appDir, "_layout.tsx"), rootLayout);
329
452
  const tabsDir = path.join(appDir, "(tabs)");
330
453
  await fs.ensureDir(tabsDir);
331
- const layout = `import { Tabs } from 'expo-router';
454
+ const layout = `import React from 'react';
455
+ import FontAwesome from '@expo/vector-icons/FontAwesome';
456
+ import { Link, Tabs } from 'expo-router';
457
+ import { Pressable } from 'react-native';
458
+ import '../../global.css';
332
459
 
333
- const TabsLayout = () => {
334
- return <Tabs screenOptions={{ headerShown: false }} />;
335
- };
460
+ function TabBarIcon(props: { name: React.ComponentProps<typeof FontAwesome>['name']; color: string; }) {
461
+ return <FontAwesome size={28} style={{ marginBottom: -3 }} {...props} />;
462
+ }
336
463
 
337
- export default TabsLayout;
464
+ export default function TabLayout() {
465
+ return (
466
+ <Tabs screenOptions={{ headerShown: true }}>
467
+ <Tabs.Screen
468
+ name="index"
469
+ options={{
470
+ title: 'Tab One',
471
+ tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
472
+ headerRight: () => (
473
+ <Link href="/modal" asChild>
474
+ <Pressable>
475
+ {({ pressed }) => (
476
+ <FontAwesome name="info-circle" size={25} color={"#1f2937"} style={{ marginRight: 15, opacity: pressed ? 0.5 : 1 }} />
477
+ )}
478
+ </Pressable>
479
+ </Link>
480
+ ),
481
+ }}
482
+ />
483
+ <Tabs.Screen
484
+ name="two"
485
+ options={{
486
+ title: 'Tab Two',
487
+ tabBarIcon: ({ color }) => <TabBarIcon name="code" color={color} />,
488
+ }}
489
+ />
490
+ </Tabs>
491
+ );
492
+ }
338
493
  `;
339
494
  await fs.writeFile(path.join(tabsDir, "_layout.tsx"), layout);
340
- const home = `import { View, Text } from 'react-native';
341
-
342
- const Home = () => (
343
- <View className="flex-1 items-center justify-center bg-background">
344
- <Text className="text-foreground text-xl">Home Tab</Text>
345
- </View>
346
- );
495
+ const tabOne = `import { View, Text } from 'react-native';
347
496
 
348
- export default Home;
497
+ export default function Index() {
498
+ return (
499
+ <View className="flex-1 items-center justify-center bg-background">
500
+ <Text className="text-foreground text-xl">Tab One</Text>
501
+ </View>
502
+ );
503
+ }
349
504
  `;
350
- await fs.writeFile(path.join(tabsDir, "index.tsx"), home);
351
- const settings = `import { View, Text } from 'react-native';
505
+ await fs.writeFile(path.join(tabsDir, "index.tsx"), tabOne);
506
+ const tabTwo = `import { View, Text } from 'react-native';
352
507
 
353
- const Settings = () => (
354
- <View className="flex-1 items-center justify-center bg-background">
355
- <Text className="text-foreground text-xl">Settings Tab</Text>
356
- </View>
357
- );
508
+ export default function Two() {
509
+ return (
510
+ <View className="flex-1 items-center justify-center bg-background">
511
+ <Text className="text-foreground text-xl">Tab Two</Text>
512
+ </View>
513
+ );
514
+ }
515
+ `;
516
+ await fs.writeFile(path.join(tabsDir, "two.tsx"), tabTwo);
517
+ const modal = `import { View, Text } from 'react-native';
358
518
 
359
- export default Settings;
519
+ export default function Modal() {
520
+ return (
521
+ <View className="flex-1 items-center justify-center bg-background">
522
+ <Text className="text-foreground text-xl">Modal screen</Text>
523
+ </View>
524
+ );
525
+ }
360
526
  `;
361
- await fs.writeFile(path.join(tabsDir, "settings.tsx"), settings);
527
+ await fs.writeFile(path.join(appDir, "modal.tsx"), modal);
362
528
  }
363
529
  }
364
530
  async function applyTemplateOverlay({ dest, type, template, category, variant }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newcandies",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Scaffold Expo Router + Uniwind React Native apps with layered templates.",
5
5
  "type": "module",
6
6
  "bin": {