expo-bbase 1.2.0 → 1.3.0
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.js +1077 -221
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2520,267 +2520,594 @@ var flashlist_default = flashlistModule;
|
|
|
2520
2520
|
var uiReusablesModule = {
|
|
2521
2521
|
id: "ui-reusables",
|
|
2522
2522
|
name: "reactnative.reusables UI",
|
|
2523
|
-
description: "\u9884\u7F6E UI \u7EC4\u4EF6",
|
|
2523
|
+
description: "\u9884\u7F6E UI \u7EC4\u4EF6 (Button, AlertDialog, Card, Input, Label, Text, Separator)",
|
|
2524
2524
|
defaultChecked: false,
|
|
2525
2525
|
dependencies: {
|
|
2526
|
-
"
|
|
2527
|
-
"
|
|
2526
|
+
"class-variance-authority": "^0.7.1",
|
|
2527
|
+
"clsx": "^2.1.1",
|
|
2528
|
+
"tailwind-merge": "^2.6.0",
|
|
2528
2529
|
"@rn-primitives/slot": "^1.1.0",
|
|
2529
|
-
"@rn-primitives/types": "^1.1.0"
|
|
2530
|
+
"@rn-primitives/types": "^1.1.0",
|
|
2531
|
+
"@rn-primitives/label": "^1.1.0",
|
|
2532
|
+
"@rn-primitives/separator": "^1.1.0",
|
|
2533
|
+
"@rn-primitives/alert-dialog": "^1.1.0",
|
|
2534
|
+
"react-native-svg": "^15.8.0"
|
|
2530
2535
|
},
|
|
2531
2536
|
devDependencies: {},
|
|
2532
2537
|
files: [
|
|
2538
|
+
// ─── lib/utils.ts ────────────────────────────────────────────────────
|
|
2533
2539
|
{
|
|
2534
|
-
path: "src/
|
|
2540
|
+
path: "src/lib/utils.ts",
|
|
2535
2541
|
content: lines(
|
|
2536
|
-
'import
|
|
2537
|
-
'import {
|
|
2538
|
-
'import { Slot } from "@rn-primitives/slot";',
|
|
2539
|
-
"",
|
|
2540
|
-
'type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";',
|
|
2541
|
-
'type ButtonSize = "default" | "sm" | "lg" | "icon";',
|
|
2542
|
+
'import { type ClassValue, clsx } from "clsx";',
|
|
2543
|
+
'import { twMerge } from "tailwind-merge";',
|
|
2542
2544
|
"",
|
|
2543
|
-
"
|
|
2544
|
-
"
|
|
2545
|
-
" size?: ButtonSize;",
|
|
2546
|
-
" asChild?: boolean;",
|
|
2547
|
-
" style?: StyleProp<ViewStyle>;",
|
|
2548
|
-
" textStyle?: StyleProp<TextStyle>;",
|
|
2549
|
-
" children: React.ReactNode;",
|
|
2545
|
+
"export function cn(...inputs: ClassValue[]) {",
|
|
2546
|
+
" return twMerge(clsx(inputs));",
|
|
2550
2547
|
"}",
|
|
2548
|
+
""
|
|
2549
|
+
)
|
|
2550
|
+
},
|
|
2551
|
+
// ─── components/ui/text.tsx ──────────────────────────────────────────
|
|
2552
|
+
{
|
|
2553
|
+
path: "src/components/ui/text.tsx",
|
|
2554
|
+
content: lines(
|
|
2555
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2556
|
+
'import { Slot } from "@rn-primitives/slot";',
|
|
2557
|
+
'import { cva, type VariantProps } from "class-variance-authority";',
|
|
2558
|
+
'import * as React from "react";',
|
|
2559
|
+
'import { Platform, Text as RNText, type Role } from "react-native";',
|
|
2560
|
+
"",
|
|
2561
|
+
"const textVariants = cva(",
|
|
2562
|
+
" cn(",
|
|
2563
|
+
' "text-foreground text-base",',
|
|
2564
|
+
" Platform.select({",
|
|
2565
|
+
' web: "select-text",',
|
|
2566
|
+
" })",
|
|
2567
|
+
" ),",
|
|
2568
|
+
" {",
|
|
2569
|
+
" variants: {",
|
|
2570
|
+
" variant: {",
|
|
2571
|
+
' default: "",',
|
|
2572
|
+
" h1: cn(",
|
|
2573
|
+
' "text-center text-4xl font-extrabold tracking-tight",',
|
|
2574
|
+
' Platform.select({ web: "scroll-m-20 text-balance" })',
|
|
2575
|
+
" ),",
|
|
2576
|
+
" h2: cn(",
|
|
2577
|
+
' "border-border border-b pb-2 text-3xl font-semibold tracking-tight",',
|
|
2578
|
+
' Platform.select({ web: "scroll-m-20 first:mt-0" })',
|
|
2579
|
+
" ),",
|
|
2580
|
+
" h3: cn(",
|
|
2581
|
+
' "text-2xl font-semibold tracking-tight",',
|
|
2582
|
+
' Platform.select({ web: "scroll-m-20" })',
|
|
2583
|
+
" ),",
|
|
2584
|
+
" h4: cn(",
|
|
2585
|
+
' "text-xl font-semibold tracking-tight",',
|
|
2586
|
+
' Platform.select({ web: "scroll-m-20" })',
|
|
2587
|
+
" ),",
|
|
2588
|
+
' p: "mt-3 leading-7 sm:mt-6",',
|
|
2589
|
+
' blockquote: "mt-4 border-l-2 pl-3 italic sm:mt-6 sm:pl-6",',
|
|
2590
|
+
" code: cn(",
|
|
2591
|
+
' "bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"',
|
|
2592
|
+
" ),",
|
|
2593
|
+
' lead: "text-muted-foreground text-xl",',
|
|
2594
|
+
' large: "text-lg font-semibold",',
|
|
2595
|
+
' small: "text-sm font-medium leading-none",',
|
|
2596
|
+
' muted: "text-muted-foreground text-sm",',
|
|
2597
|
+
" },",
|
|
2598
|
+
" },",
|
|
2599
|
+
" defaultVariants: {",
|
|
2600
|
+
' variant: "default",',
|
|
2601
|
+
" },",
|
|
2602
|
+
" }",
|
|
2603
|
+
");",
|
|
2551
2604
|
"",
|
|
2552
|
-
"
|
|
2553
|
-
' default: { backgroundColor: "#0f172a" },',
|
|
2554
|
-
' destructive: { backgroundColor: "#ef4444" },',
|
|
2555
|
-
' outline: { backgroundColor: "transparent", borderWidth: 1, borderColor: "#d4d4d8" },',
|
|
2556
|
-
' secondary: { backgroundColor: "#f4f4f5" },',
|
|
2557
|
-
' ghost: { backgroundColor: "transparent" },',
|
|
2558
|
-
' link: { backgroundColor: "transparent" },',
|
|
2559
|
-
"};",
|
|
2605
|
+
"type TextVariantProps = VariantProps<typeof textVariants>;",
|
|
2560
2606
|
"",
|
|
2561
|
-
|
|
2562
|
-
' default: { color: "#fafafa" },',
|
|
2563
|
-
' destructive: { color: "#fafafa" },',
|
|
2564
|
-
' outline: { color: "#18181b" },',
|
|
2565
|
-
' secondary: { color: "#18181b" },',
|
|
2566
|
-
' ghost: { color: "#18181b" },',
|
|
2567
|
-
' link: { color: "#2563eb", textDecorationLine: "underline" },',
|
|
2568
|
-
"};",
|
|
2607
|
+
'type TextVariant = NonNullable<TextVariantProps["variant"]>;',
|
|
2569
2608
|
"",
|
|
2570
|
-
"const
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2609
|
+
"const ROLE: Partial<Record<TextVariant, Role>> = {",
|
|
2610
|
+
' h1: "heading",',
|
|
2611
|
+
' h2: "heading",',
|
|
2612
|
+
' h3: "heading",',
|
|
2613
|
+
' h4: "heading",',
|
|
2614
|
+
' blockquote: Platform.select({ web: "blockquote" as Role }),',
|
|
2615
|
+
' code: Platform.select({ web: "code" as Role }),',
|
|
2575
2616
|
"};",
|
|
2576
2617
|
"",
|
|
2577
|
-
"const
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2618
|
+
"const ARIA_LEVEL: Partial<Record<TextVariant, string>> = {",
|
|
2619
|
+
' h1: "1",',
|
|
2620
|
+
' h2: "2",',
|
|
2621
|
+
' h3: "3",',
|
|
2622
|
+
' h4: "4",',
|
|
2582
2623
|
"};",
|
|
2583
2624
|
"",
|
|
2584
|
-
"
|
|
2585
|
-
|
|
2586
|
-
|
|
2625
|
+
"const TextClassContext = React.createContext<string | undefined>(undefined);",
|
|
2626
|
+
"",
|
|
2627
|
+
"function Text({",
|
|
2628
|
+
" className,",
|
|
2587
2629
|
" asChild = false,",
|
|
2588
|
-
|
|
2589
|
-
"
|
|
2590
|
-
"
|
|
2591
|
-
"
|
|
2592
|
-
"}
|
|
2593
|
-
" const
|
|
2594
|
-
"
|
|
2595
|
-
"
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
"
|
|
2600
|
-
"
|
|
2601
|
-
"
|
|
2602
|
-
"
|
|
2603
|
-
"
|
|
2630
|
+
' variant = "default",',
|
|
2631
|
+
" ...props",
|
|
2632
|
+
"}: React.ComponentProps<typeof RNText> &",
|
|
2633
|
+
" React.RefAttributes<typeof RNText> &",
|
|
2634
|
+
" TextVariantProps & { asChild?: boolean }) {",
|
|
2635
|
+
" const textClass = React.useContext(TextClassContext);",
|
|
2636
|
+
" const Component = asChild ? Slot : RNText;",
|
|
2637
|
+
" return (",
|
|
2638
|
+
" <Component",
|
|
2639
|
+
" className={cn(textVariants({ variant }), textClass, className)}",
|
|
2640
|
+
" role={variant ? ROLE[variant] : undefined}",
|
|
2641
|
+
" aria-level={variant ? ARIA_LEVEL[variant] : undefined}",
|
|
2642
|
+
" {...props}",
|
|
2643
|
+
" />",
|
|
2644
|
+
" );",
|
|
2645
|
+
"}",
|
|
2604
2646
|
"",
|
|
2605
|
-
"
|
|
2606
|
-
"
|
|
2607
|
-
|
|
2608
|
-
|
|
2647
|
+
"export { Text, TextClassContext };",
|
|
2648
|
+
""
|
|
2649
|
+
)
|
|
2650
|
+
},
|
|
2651
|
+
// ─── components/ui/button.tsx ────────────────────────────────────────
|
|
2652
|
+
{
|
|
2653
|
+
path: "src/components/ui/button.tsx",
|
|
2654
|
+
content: lines(
|
|
2655
|
+
'import { TextClassContext } from "@/src/components/ui/text";',
|
|
2656
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2657
|
+
'import { cva, type VariantProps } from "class-variance-authority";',
|
|
2658
|
+
'import { Platform, Pressable } from "react-native";',
|
|
2659
|
+
"",
|
|
2660
|
+
"const buttonVariants = cva(",
|
|
2661
|
+
" cn(",
|
|
2662
|
+
' "group shrink-0 flex-row items-center justify-center gap-2 rounded-md shadow-none",',
|
|
2663
|
+
" Platform.select({",
|
|
2664
|
+
` web: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",`,
|
|
2665
|
+
" })",
|
|
2666
|
+
" ),",
|
|
2667
|
+
" {",
|
|
2668
|
+
" variants: {",
|
|
2669
|
+
" variant: {",
|
|
2670
|
+
" default: cn(",
|
|
2671
|
+
' "bg-primary active:bg-primary/90 shadow-sm shadow-black/5",',
|
|
2672
|
+
' Platform.select({ web: "hover:bg-primary/90" })',
|
|
2673
|
+
" ),",
|
|
2674
|
+
" destructive: cn(",
|
|
2675
|
+
' "bg-destructive active:bg-destructive/90 dark:bg-destructive/60 shadow-sm shadow-black/5",',
|
|
2676
|
+
" Platform.select({",
|
|
2677
|
+
' web: "hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",',
|
|
2678
|
+
" })",
|
|
2679
|
+
" ),",
|
|
2680
|
+
" outline: cn(",
|
|
2681
|
+
' "border-border bg-background active:bg-accent dark:bg-input/30 dark:border-input dark:active:bg-input/50 border shadow-sm shadow-black/5",',
|
|
2682
|
+
" Platform.select({",
|
|
2683
|
+
' web: "hover:bg-accent dark:hover:bg-input/50",',
|
|
2684
|
+
" })",
|
|
2685
|
+
" ),",
|
|
2686
|
+
" secondary: cn(",
|
|
2687
|
+
' "bg-secondary active:bg-secondary/80 shadow-sm shadow-black/5",',
|
|
2688
|
+
' Platform.select({ web: "hover:bg-secondary/80" })',
|
|
2689
|
+
" ),",
|
|
2690
|
+
" ghost: cn(",
|
|
2691
|
+
' "active:bg-accent dark:active:bg-accent/50",',
|
|
2692
|
+
' Platform.select({ web: "hover:bg-accent dark:hover:bg-accent/50" })',
|
|
2693
|
+
" ),",
|
|
2694
|
+
' link: "",',
|
|
2695
|
+
" },",
|
|
2696
|
+
" size: {",
|
|
2697
|
+
" default: cn(",
|
|
2698
|
+
' "h-10 px-4 py-2 sm:h-9",',
|
|
2699
|
+
' Platform.select({ web: "has-[>svg]:px-3" })',
|
|
2700
|
+
" ),",
|
|
2701
|
+
" sm: cn(",
|
|
2702
|
+
' "h-9 gap-1.5 rounded-md px-3 sm:h-8",',
|
|
2703
|
+
' Platform.select({ web: "has-[>svg]:px-2.5" })',
|
|
2704
|
+
" ),",
|
|
2705
|
+
" lg: cn(",
|
|
2706
|
+
' "h-11 rounded-md px-6 sm:h-10",',
|
|
2707
|
+
' Platform.select({ web: "has-[>svg]:px-4" })',
|
|
2708
|
+
" ),",
|
|
2709
|
+
' icon: "h-10 w-10 sm:h-9 sm:w-9",',
|
|
2710
|
+
" },",
|
|
2711
|
+
" },",
|
|
2712
|
+
" defaultVariants: {",
|
|
2713
|
+
' variant: "default",',
|
|
2714
|
+
' size: "default",',
|
|
2609
2715
|
" },",
|
|
2610
|
-
"
|
|
2611
|
-
"
|
|
2612
|
-
" textStyle,",
|
|
2613
|
-
" ];",
|
|
2716
|
+
" }",
|
|
2717
|
+
");",
|
|
2614
2718
|
"",
|
|
2615
|
-
"
|
|
2616
|
-
"
|
|
2617
|
-
"
|
|
2618
|
-
"
|
|
2619
|
-
"
|
|
2620
|
-
"
|
|
2719
|
+
"const buttonTextVariants = cva(",
|
|
2720
|
+
" cn(",
|
|
2721
|
+
' "text-foreground text-sm font-medium",',
|
|
2722
|
+
' Platform.select({ web: "pointer-events-none transition-colors" })',
|
|
2723
|
+
" ),",
|
|
2724
|
+
" {",
|
|
2725
|
+
" variants: {",
|
|
2726
|
+
" variant: {",
|
|
2727
|
+
' default: "text-primary-foreground",',
|
|
2728
|
+
' destructive: "text-white",',
|
|
2729
|
+
" outline: cn(",
|
|
2730
|
+
' "group-active:text-accent-foreground",',
|
|
2731
|
+
' Platform.select({ web: "group-hover:text-accent-foreground" })',
|
|
2732
|
+
" ),",
|
|
2733
|
+
' secondary: "text-secondary-foreground",',
|
|
2734
|
+
' ghost: "group-active:text-accent-foreground",',
|
|
2735
|
+
" link: cn(",
|
|
2736
|
+
' "text-primary group-active:underline",',
|
|
2737
|
+
' Platform.select({ web: "underline-offset-4 hover:underline group-hover:underline" })',
|
|
2738
|
+
" ),",
|
|
2739
|
+
" },",
|
|
2740
|
+
" size: {",
|
|
2741
|
+
' default: "",',
|
|
2742
|
+
' sm: "",',
|
|
2743
|
+
' lg: "",',
|
|
2744
|
+
' icon: "",',
|
|
2745
|
+
" },",
|
|
2746
|
+
" },",
|
|
2747
|
+
" defaultVariants: {",
|
|
2748
|
+
' variant: "default",',
|
|
2749
|
+
' size: "default",',
|
|
2750
|
+
" },",
|
|
2621
2751
|
" }",
|
|
2752
|
+
");",
|
|
2753
|
+
"",
|
|
2754
|
+
"type ButtonProps = React.ComponentProps<typeof Pressable> &",
|
|
2755
|
+
" React.RefAttributes<typeof Pressable> &",
|
|
2756
|
+
" VariantProps<typeof buttonVariants>;",
|
|
2622
2757
|
"",
|
|
2758
|
+
"function Button({ className, variant, size, ...props }: ButtonProps) {",
|
|
2623
2759
|
" return (",
|
|
2624
|
-
" <
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
"
|
|
2628
|
-
"
|
|
2629
|
-
"
|
|
2630
|
-
" </
|
|
2760
|
+
" <TextClassContext.Provider value={buttonTextVariants({ variant, size })}>",
|
|
2761
|
+
" <Pressable",
|
|
2762
|
+
' className={cn(props.disabled && "opacity-50", buttonVariants({ variant, size }), className)}',
|
|
2763
|
+
' role="button"',
|
|
2764
|
+
" {...props}",
|
|
2765
|
+
" />",
|
|
2766
|
+
" </TextClassContext.Provider>",
|
|
2631
2767
|
" );",
|
|
2632
2768
|
"}",
|
|
2633
2769
|
"",
|
|
2634
|
-
"export
|
|
2770
|
+
"export { Button, buttonTextVariants, buttonVariants };",
|
|
2771
|
+
"export type { ButtonProps };",
|
|
2772
|
+
""
|
|
2635
2773
|
)
|
|
2636
2774
|
},
|
|
2775
|
+
// ─── components/ui/input.tsx ──────────────────────────────────────────
|
|
2637
2776
|
{
|
|
2638
2777
|
path: "src/components/ui/input.tsx",
|
|
2639
2778
|
content: lines(
|
|
2640
|
-
'import
|
|
2641
|
-
|
|
2642
|
-
"
|
|
2643
|
-
"
|
|
2644
|
-
"
|
|
2645
|
-
"
|
|
2646
|
-
"
|
|
2647
|
-
"
|
|
2648
|
-
"
|
|
2649
|
-
|
|
2650
|
-
"",
|
|
2651
|
-
"
|
|
2652
|
-
"
|
|
2653
|
-
"
|
|
2654
|
-
"
|
|
2655
|
-
"
|
|
2656
|
-
"
|
|
2657
|
-
"",
|
|
2658
|
-
"
|
|
2659
|
-
"
|
|
2660
|
-
"
|
|
2661
|
-
"
|
|
2662
|
-
|
|
2663
|
-
"
|
|
2664
|
-
"
|
|
2665
|
-
"
|
|
2666
|
-
"
|
|
2667
|
-
"
|
|
2668
|
-
"
|
|
2669
|
-
"
|
|
2670
|
-
' placeholderTextColor="#a1a1aa"',
|
|
2671
|
-
" {...rest}",
|
|
2672
|
-
" />",
|
|
2673
|
-
" {error && <Text style={styles.errorText}>{error}</Text>}",
|
|
2674
|
-
" </View>",
|
|
2675
|
-
" );",
|
|
2676
|
-
" }",
|
|
2677
|
-
");",
|
|
2779
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2780
|
+
'import { Platform, TextInput } from "react-native";',
|
|
2781
|
+
"",
|
|
2782
|
+
"function Input({",
|
|
2783
|
+
" className,",
|
|
2784
|
+
" ...props",
|
|
2785
|
+
"}: React.ComponentProps<typeof TextInput> & React.RefAttributes<TextInput>) {",
|
|
2786
|
+
" return (",
|
|
2787
|
+
" <TextInput",
|
|
2788
|
+
" className={cn(",
|
|
2789
|
+
' "dark:bg-input/30 border-input bg-background text-foreground flex h-10 w-full min-w-0 flex-row items-center rounded-md border px-3 py-1 text-base leading-5 shadow-sm shadow-black/5 sm:h-9",',
|
|
2790
|
+
" props.editable === false &&",
|
|
2791
|
+
" cn(",
|
|
2792
|
+
' "opacity-50",',
|
|
2793
|
+
' Platform.select({ web: "disabled:pointer-events-none disabled:cursor-not-allowed" })',
|
|
2794
|
+
" ),",
|
|
2795
|
+
" Platform.select({",
|
|
2796
|
+
" web: cn(",
|
|
2797
|
+
' "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground outline-none transition-[color,box-shadow] md:text-sm",',
|
|
2798
|
+
' "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",',
|
|
2799
|
+
' "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"',
|
|
2800
|
+
" ),",
|
|
2801
|
+
' native: "placeholder:text-muted-foreground/50",',
|
|
2802
|
+
" }),",
|
|
2803
|
+
" className",
|
|
2804
|
+
" )}",
|
|
2805
|
+
" {...props}",
|
|
2806
|
+
" />",
|
|
2807
|
+
" );",
|
|
2808
|
+
"}",
|
|
2678
2809
|
"",
|
|
2679
|
-
|
|
2810
|
+
"export { Input };",
|
|
2811
|
+
""
|
|
2812
|
+
)
|
|
2813
|
+
},
|
|
2814
|
+
// ─── components/ui/label.tsx ─────────────────────────────────────────
|
|
2815
|
+
{
|
|
2816
|
+
path: "src/components/ui/label.tsx",
|
|
2817
|
+
content: lines(
|
|
2818
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2819
|
+
'import * as LabelPrimitive from "@rn-primitives/label";',
|
|
2820
|
+
'import { Platform } from "react-native";',
|
|
2680
2821
|
"",
|
|
2681
|
-
"
|
|
2682
|
-
"
|
|
2683
|
-
"
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
"
|
|
2687
|
-
"
|
|
2688
|
-
"
|
|
2689
|
-
"
|
|
2690
|
-
"
|
|
2691
|
-
|
|
2692
|
-
"
|
|
2693
|
-
"
|
|
2694
|
-
"
|
|
2695
|
-
'
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
"
|
|
2699
|
-
|
|
2700
|
-
"
|
|
2701
|
-
"
|
|
2702
|
-
"
|
|
2703
|
-
|
|
2704
|
-
"
|
|
2705
|
-
"
|
|
2706
|
-
"
|
|
2822
|
+
"function Label({",
|
|
2823
|
+
" className,",
|
|
2824
|
+
" onPress,",
|
|
2825
|
+
" onLongPress,",
|
|
2826
|
+
" onPressIn,",
|
|
2827
|
+
" onPressOut,",
|
|
2828
|
+
" disabled,",
|
|
2829
|
+
" ...props",
|
|
2830
|
+
"}: React.ComponentProps<typeof LabelPrimitive.Text>) {",
|
|
2831
|
+
" return (",
|
|
2832
|
+
" <LabelPrimitive.Root",
|
|
2833
|
+
" className={cn(",
|
|
2834
|
+
' "flex select-none flex-row items-center gap-2",',
|
|
2835
|
+
" Platform.select({",
|
|
2836
|
+
' web: "cursor-default leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",',
|
|
2837
|
+
" }),",
|
|
2838
|
+
' disabled && "opacity-50"',
|
|
2839
|
+
" )}",
|
|
2840
|
+
" onPress={onPress}",
|
|
2841
|
+
" onLongPress={onLongPress}",
|
|
2842
|
+
" onPressIn={onPressIn}",
|
|
2843
|
+
" onPressOut={onPressOut}",
|
|
2844
|
+
" disabled={disabled}",
|
|
2845
|
+
" >",
|
|
2846
|
+
" <LabelPrimitive.Text",
|
|
2847
|
+
" className={cn(",
|
|
2848
|
+
' "text-foreground text-sm font-medium",',
|
|
2849
|
+
' Platform.select({ web: "leading-none" }),',
|
|
2850
|
+
" className",
|
|
2851
|
+
" )}",
|
|
2852
|
+
" {...props}",
|
|
2853
|
+
" />",
|
|
2854
|
+
" </LabelPrimitive.Root>",
|
|
2855
|
+
" );",
|
|
2856
|
+
"}",
|
|
2707
2857
|
"",
|
|
2708
|
-
"export
|
|
2858
|
+
"export { Label };",
|
|
2859
|
+
""
|
|
2709
2860
|
)
|
|
2710
2861
|
},
|
|
2862
|
+
// ─── components/ui/card.tsx ──────────────────────────────────────────
|
|
2711
2863
|
{
|
|
2712
2864
|
path: "src/components/ui/card.tsx",
|
|
2713
2865
|
content: lines(
|
|
2714
|
-
'import
|
|
2715
|
-
'import {
|
|
2866
|
+
'import { Text, TextClassContext } from "@/src/components/ui/text";',
|
|
2867
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2868
|
+
'import { View } from "react-native";',
|
|
2869
|
+
"",
|
|
2870
|
+
"function Card({",
|
|
2871
|
+
" className,",
|
|
2872
|
+
" ...props",
|
|
2873
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2874
|
+
" return (",
|
|
2875
|
+
' <TextClassContext.Provider value="text-card-foreground">',
|
|
2876
|
+
" <View",
|
|
2877
|
+
" className={cn(",
|
|
2878
|
+
' "bg-card border-border flex flex-col gap-6 rounded-xl border py-6 shadow-sm shadow-black/5",',
|
|
2879
|
+
" className",
|
|
2880
|
+
" )}",
|
|
2881
|
+
" {...props}",
|
|
2882
|
+
" />",
|
|
2883
|
+
" </TextClassContext.Provider>",
|
|
2884
|
+
" );",
|
|
2885
|
+
"}",
|
|
2716
2886
|
"",
|
|
2717
|
-
"
|
|
2718
|
-
"
|
|
2719
|
-
"
|
|
2720
|
-
"
|
|
2721
|
-
"
|
|
2722
|
-
"
|
|
2723
|
-
"
|
|
2724
|
-
' padding?: "none" | "sm" | "md" | "lg";',
|
|
2887
|
+
"function CardHeader({",
|
|
2888
|
+
" className,",
|
|
2889
|
+
" ...props",
|
|
2890
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2891
|
+
" return (",
|
|
2892
|
+
' <View className={cn("flex flex-col gap-1.5 px-6", className)} {...props} />',
|
|
2893
|
+
" );",
|
|
2725
2894
|
"}",
|
|
2726
2895
|
"",
|
|
2727
|
-
|
|
2728
|
-
"
|
|
2729
|
-
"
|
|
2730
|
-
"
|
|
2731
|
-
"
|
|
2732
|
-
"
|
|
2896
|
+
"function CardTitle({",
|
|
2897
|
+
" className,",
|
|
2898
|
+
" ref,",
|
|
2899
|
+
" ...props",
|
|
2900
|
+
"}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) {",
|
|
2901
|
+
" return (",
|
|
2902
|
+
" <Text",
|
|
2903
|
+
" ref={ref}",
|
|
2904
|
+
' role="heading"',
|
|
2905
|
+
" aria-level={3}",
|
|
2906
|
+
' className={cn("font-semibold leading-none", className)}',
|
|
2907
|
+
" {...props}",
|
|
2908
|
+
" />",
|
|
2909
|
+
" );",
|
|
2910
|
+
"}",
|
|
2733
2911
|
"",
|
|
2734
|
-
"
|
|
2735
|
-
"
|
|
2736
|
-
"
|
|
2737
|
-
"
|
|
2738
|
-
" style,",
|
|
2739
|
-
" titleStyle,",
|
|
2740
|
-
" descriptionStyle,",
|
|
2741
|
-
' padding = "md",',
|
|
2742
|
-
"}: CardProps) {",
|
|
2912
|
+
"function CardDescription({",
|
|
2913
|
+
" className,",
|
|
2914
|
+
" ...props",
|
|
2915
|
+
"}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) {",
|
|
2743
2916
|
" return (",
|
|
2744
|
-
|
|
2745
|
-
"
|
|
2746
|
-
"
|
|
2747
|
-
|
|
2748
|
-
"
|
|
2749
|
-
"
|
|
2750
|
-
|
|
2751
|
-
"
|
|
2752
|
-
"
|
|
2753
|
-
"
|
|
2754
|
-
"
|
|
2755
|
-
"
|
|
2756
|
-
"
|
|
2757
|
-
"
|
|
2917
|
+
' <Text className={cn("text-muted-foreground text-sm", className)} {...props} />',
|
|
2918
|
+
" );",
|
|
2919
|
+
"}",
|
|
2920
|
+
"",
|
|
2921
|
+
"function CardContent({",
|
|
2922
|
+
" className,",
|
|
2923
|
+
" ...props",
|
|
2924
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2925
|
+
" return (",
|
|
2926
|
+
' <View className={cn("px-6", className)} {...props} />',
|
|
2927
|
+
" );",
|
|
2928
|
+
"}",
|
|
2929
|
+
"",
|
|
2930
|
+
"function CardFooter({",
|
|
2931
|
+
" className,",
|
|
2932
|
+
" ...props",
|
|
2933
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2934
|
+
" return (",
|
|
2935
|
+
' <View className={cn("flex flex-row items-center px-6", className)} {...props} />',
|
|
2936
|
+
" );",
|
|
2937
|
+
"}",
|
|
2938
|
+
"",
|
|
2939
|
+
"export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };",
|
|
2940
|
+
""
|
|
2941
|
+
)
|
|
2942
|
+
},
|
|
2943
|
+
// ─── components/ui/separator.tsx ──────────────────────────────────────
|
|
2944
|
+
{
|
|
2945
|
+
path: "src/components/ui/separator.tsx",
|
|
2946
|
+
content: lines(
|
|
2947
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2948
|
+
'import * as SeparatorPrimitive from "@rn-primitives/separator";',
|
|
2949
|
+
"",
|
|
2950
|
+
"function Separator({",
|
|
2951
|
+
" className,",
|
|
2952
|
+
' orientation = "horizontal",',
|
|
2953
|
+
" decorative = true,",
|
|
2954
|
+
" ...props",
|
|
2955
|
+
"}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {",
|
|
2956
|
+
" return (",
|
|
2957
|
+
" <SeparatorPrimitive.Root",
|
|
2958
|
+
" decorative={decorative}",
|
|
2959
|
+
" orientation={orientation}",
|
|
2960
|
+
" className={cn(",
|
|
2961
|
+
' "bg-border shrink-0",',
|
|
2962
|
+
' orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",',
|
|
2963
|
+
" className",
|
|
2758
2964
|
" )}",
|
|
2759
|
-
" {
|
|
2760
|
-
"
|
|
2761
|
-
"
|
|
2762
|
-
"
|
|
2965
|
+
" {...props}",
|
|
2966
|
+
" />",
|
|
2967
|
+
" );",
|
|
2968
|
+
"}",
|
|
2969
|
+
"",
|
|
2970
|
+
"export { Separator };",
|
|
2971
|
+
""
|
|
2972
|
+
)
|
|
2973
|
+
},
|
|
2974
|
+
// ─── components/ui/alert-dialog.tsx ──────────────────────────────────
|
|
2975
|
+
{
|
|
2976
|
+
path: "src/components/ui/alert-dialog.tsx",
|
|
2977
|
+
content: lines(
|
|
2978
|
+
'import { buttonTextVariants, buttonVariants } from "@/src/components/ui/button";',
|
|
2979
|
+
'import { TextClassContext } from "@/src/components/ui/text";',
|
|
2980
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2981
|
+
'import * as AlertDialogPrimitive from "@rn-primitives/alert-dialog";',
|
|
2982
|
+
'import * as React from "react";',
|
|
2983
|
+
'import { Platform, View, type ViewProps } from "react-native";',
|
|
2984
|
+
"",
|
|
2985
|
+
"const AlertDialog = AlertDialogPrimitive.Root;",
|
|
2986
|
+
"",
|
|
2987
|
+
"const AlertDialogTrigger = AlertDialogPrimitive.Trigger;",
|
|
2988
|
+
"",
|
|
2989
|
+
"const AlertDialogPortal = AlertDialogPrimitive.Portal;",
|
|
2990
|
+
"",
|
|
2991
|
+
"function AlertDialogOverlay({",
|
|
2992
|
+
" className,",
|
|
2993
|
+
" ...props",
|
|
2994
|
+
'}: Omit<React.ComponentProps<typeof AlertDialogPrimitive.Overlay>, "asChild"> & {',
|
|
2995
|
+
" children?: React.ReactNode;",
|
|
2996
|
+
"}) {",
|
|
2997
|
+
" return (",
|
|
2998
|
+
" <AlertDialogPrimitive.Overlay",
|
|
2999
|
+
" className={cn(",
|
|
3000
|
+
' "absolute bottom-0 left-0 right-0 top-0 z-50 flex items-center justify-center bg-black/50 p-2",',
|
|
3001
|
+
" Platform.select({",
|
|
3002
|
+
' web: "animate-in fade-in-0 fixed",',
|
|
3003
|
+
" }),",
|
|
3004
|
+
" className",
|
|
2763
3005
|
" )}",
|
|
2764
|
-
" {
|
|
2765
|
-
"
|
|
3006
|
+
" {...props}",
|
|
3007
|
+
" />",
|
|
2766
3008
|
" );",
|
|
2767
3009
|
"}",
|
|
2768
3010
|
"",
|
|
2769
|
-
"
|
|
2770
|
-
"
|
|
2771
|
-
"
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
"
|
|
2775
|
-
"
|
|
2776
|
-
"
|
|
2777
|
-
"
|
|
2778
|
-
|
|
2779
|
-
"
|
|
2780
|
-
"
|
|
2781
|
-
"
|
|
3011
|
+
"function AlertDialogContent({",
|
|
3012
|
+
" className,",
|
|
3013
|
+
" portalHost,",
|
|
3014
|
+
" ...props",
|
|
3015
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {",
|
|
3016
|
+
" portalHost?: string;",
|
|
3017
|
+
"}) {",
|
|
3018
|
+
" return (",
|
|
3019
|
+
" <AlertDialogPortal hostName={portalHost}>",
|
|
3020
|
+
" <AlertDialogOverlay>",
|
|
3021
|
+
" <AlertDialogPrimitive.Content",
|
|
3022
|
+
" className={cn(",
|
|
3023
|
+
' "bg-background border-border z-50 flex w-full max-w-[calc(100%-2rem)] flex-col gap-4 rounded-lg border p-6 shadow-lg shadow-black/5 sm:max-w-lg",',
|
|
3024
|
+
" Platform.select({",
|
|
3025
|
+
' web: "animate-in fade-in-0 zoom-in-95 duration-200",',
|
|
3026
|
+
" }),",
|
|
3027
|
+
" className",
|
|
3028
|
+
" )}",
|
|
3029
|
+
" {...props}",
|
|
3030
|
+
" />",
|
|
3031
|
+
" </AlertDialogOverlay>",
|
|
3032
|
+
" </AlertDialogPortal>",
|
|
3033
|
+
" );",
|
|
3034
|
+
"}",
|
|
3035
|
+
"",
|
|
3036
|
+
"function AlertDialogHeader({ className, ...props }: ViewProps) {",
|
|
3037
|
+
" return (",
|
|
3038
|
+
' <TextClassContext.Provider value="text-center sm:text-left">',
|
|
3039
|
+
' <View className={cn("flex flex-col gap-2", className)} {...props} />',
|
|
3040
|
+
" </TextClassContext.Provider>",
|
|
3041
|
+
" );",
|
|
3042
|
+
"}",
|
|
3043
|
+
"",
|
|
3044
|
+
"function AlertDialogFooter({ className, ...props }: ViewProps) {",
|
|
3045
|
+
" return (",
|
|
3046
|
+
' <View className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} {...props} />',
|
|
3047
|
+
" );",
|
|
3048
|
+
"}",
|
|
3049
|
+
"",
|
|
3050
|
+
"function AlertDialogTitle({",
|
|
3051
|
+
" className,",
|
|
3052
|
+
" ...props",
|
|
3053
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {",
|
|
3054
|
+
" return (",
|
|
3055
|
+
" <AlertDialogPrimitive.Title",
|
|
3056
|
+
' className={cn("text-foreground text-lg font-semibold", className)}',
|
|
3057
|
+
" {...props}",
|
|
3058
|
+
" />",
|
|
3059
|
+
" );",
|
|
3060
|
+
"}",
|
|
2782
3061
|
"",
|
|
2783
|
-
"
|
|
3062
|
+
"function AlertDialogDescription({",
|
|
3063
|
+
" className,",
|
|
3064
|
+
" ...props",
|
|
3065
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {",
|
|
3066
|
+
" return (",
|
|
3067
|
+
" <AlertDialogPrimitive.Description",
|
|
3068
|
+
' className={cn("text-muted-foreground text-sm", className)}',
|
|
3069
|
+
" {...props}",
|
|
3070
|
+
" />",
|
|
3071
|
+
" );",
|
|
3072
|
+
"}",
|
|
3073
|
+
"",
|
|
3074
|
+
"function AlertDialogAction({",
|
|
3075
|
+
" className,",
|
|
3076
|
+
" ...props",
|
|
3077
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {",
|
|
3078
|
+
" return (",
|
|
3079
|
+
" <TextClassContext.Provider value={buttonTextVariants({ className })}>",
|
|
3080
|
+
" <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />",
|
|
3081
|
+
" </TextClassContext.Provider>",
|
|
3082
|
+
" );",
|
|
3083
|
+
"}",
|
|
3084
|
+
"",
|
|
3085
|
+
"function AlertDialogCancel({",
|
|
3086
|
+
" className,",
|
|
3087
|
+
" ...props",
|
|
3088
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {",
|
|
3089
|
+
" return (",
|
|
3090
|
+
' <TextClassContext.Provider value={buttonTextVariants({ className, variant: "outline" })}>',
|
|
3091
|
+
" <AlertDialogPrimitive.Cancel",
|
|
3092
|
+
' className={cn(buttonVariants({ variant: "outline" }), className)} {...props} />',
|
|
3093
|
+
" </TextClassContext.Provider>",
|
|
3094
|
+
" );",
|
|
3095
|
+
"}",
|
|
3096
|
+
"",
|
|
3097
|
+
"export {",
|
|
3098
|
+
" AlertDialog,",
|
|
3099
|
+
" AlertDialogAction,",
|
|
3100
|
+
" AlertDialogCancel,",
|
|
3101
|
+
" AlertDialogContent,",
|
|
3102
|
+
" AlertDialogDescription,",
|
|
3103
|
+
" AlertDialogFooter,",
|
|
3104
|
+
" AlertDialogHeader,",
|
|
3105
|
+
" AlertDialogOverlay,",
|
|
3106
|
+
" AlertDialogPortal,",
|
|
3107
|
+
" AlertDialogTitle,",
|
|
3108
|
+
" AlertDialogTrigger,",
|
|
3109
|
+
"};",
|
|
3110
|
+
""
|
|
2784
3111
|
)
|
|
2785
3112
|
}
|
|
2786
3113
|
]
|
|
@@ -2826,7 +3153,8 @@ function generateBaseTemplates(projectName) {
|
|
|
2826
3153
|
// ─── app/_layout.tsx ────────────────────────────────────────────────
|
|
2827
3154
|
{
|
|
2828
3155
|
path: "app/_layout.tsx",
|
|
2829
|
-
content: `import
|
|
3156
|
+
content: `import "../global.css";
|
|
3157
|
+
import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
|
|
2830
3158
|
import { useFonts } from "expo-font";
|
|
2831
3159
|
import { Stack } from "expo-router";
|
|
2832
3160
|
import * as SplashScreen from "expo-splash-screen";
|
|
@@ -3251,8 +3579,45 @@ module.exports = {
|
|
|
3251
3579
|
"./app/**/*.{js,jsx,ts,tsx}",
|
|
3252
3580
|
"./src/**/*.{js,jsx,ts,tsx}",
|
|
3253
3581
|
],
|
|
3582
|
+
presets: [require("nativewind/preset")],
|
|
3254
3583
|
theme: {
|
|
3255
|
-
extend: {
|
|
3584
|
+
extend: {
|
|
3585
|
+
colors: {
|
|
3586
|
+
border: "hsl(var(--border))",
|
|
3587
|
+
input: "hsl(var(--input))",
|
|
3588
|
+
ring: "hsl(var(--ring))",
|
|
3589
|
+
background: "hsl(var(--background))",
|
|
3590
|
+
foreground: "hsl(var(--foreground))",
|
|
3591
|
+
primary: {
|
|
3592
|
+
DEFAULT: "hsl(var(--primary))",
|
|
3593
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
3594
|
+
},
|
|
3595
|
+
secondary: {
|
|
3596
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
3597
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
3598
|
+
},
|
|
3599
|
+
destructive: {
|
|
3600
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
3601
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
3602
|
+
},
|
|
3603
|
+
muted: {
|
|
3604
|
+
DEFAULT: "hsl(var(--muted))",
|
|
3605
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
3606
|
+
},
|
|
3607
|
+
accent: {
|
|
3608
|
+
DEFAULT: "hsl(var(--accent))",
|
|
3609
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
3610
|
+
},
|
|
3611
|
+
popover: {
|
|
3612
|
+
DEFAULT: "hsl(var(--popover))",
|
|
3613
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
3614
|
+
},
|
|
3615
|
+
card: {
|
|
3616
|
+
DEFAULT: "hsl(var(--card))",
|
|
3617
|
+
foreground: "hsl(var(--card-foreground))",
|
|
3618
|
+
},
|
|
3619
|
+
},
|
|
3620
|
+
},
|
|
3256
3621
|
},
|
|
3257
3622
|
plugins: [],
|
|
3258
3623
|
};
|
|
@@ -3262,10 +3627,11 @@ module.exports = {
|
|
|
3262
3627
|
{
|
|
3263
3628
|
path: "metro.config.js",
|
|
3264
3629
|
content: `const { getDefaultConfig } = require("expo/metro-config");
|
|
3630
|
+
const { withNativeWind } = require("nativewind/metro");
|
|
3265
3631
|
|
|
3266
3632
|
const config = getDefaultConfig(__dirname);
|
|
3267
3633
|
|
|
3268
|
-
module.exports = config;
|
|
3634
|
+
module.exports = withNativeWind(config, { input: "./global.css" });
|
|
3269
3635
|
`
|
|
3270
3636
|
},
|
|
3271
3637
|
// ─── babel.config.js ─────────────────────────────────────────────────
|
|
@@ -3274,10 +3640,67 @@ module.exports = config;
|
|
|
3274
3640
|
content: `module.exports = function (api) {
|
|
3275
3641
|
api.cache(true);
|
|
3276
3642
|
return {
|
|
3277
|
-
presets: [
|
|
3278
|
-
|
|
3643
|
+
presets: [
|
|
3644
|
+
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
|
3645
|
+
"nativewind/babel",
|
|
3646
|
+
],
|
|
3647
|
+
plugins: ["react-native-reanimated/plugin"],
|
|
3279
3648
|
};
|
|
3280
3649
|
};
|
|
3650
|
+
`
|
|
3651
|
+
},
|
|
3652
|
+
// ─── global.css (NativeWind CSS variables for rnr components) ─────────
|
|
3653
|
+
{
|
|
3654
|
+
path: "global.css",
|
|
3655
|
+
content: `@tailwind base;
|
|
3656
|
+
@tailwind components;
|
|
3657
|
+
@tailwind utilities;
|
|
3658
|
+
|
|
3659
|
+
@layer base {
|
|
3660
|
+
:root {
|
|
3661
|
+
--background: 0 0% 100%;
|
|
3662
|
+
--foreground: 240 10% 3.9%;
|
|
3663
|
+
--card: 0 0% 100%;
|
|
3664
|
+
--card-foreground: 240 10% 3.9%;
|
|
3665
|
+
--popover: 0 0% 100%;
|
|
3666
|
+
--popover-foreground: 240 10% 3.9%;
|
|
3667
|
+
--primary: 240 5.9% 10%;
|
|
3668
|
+
--primary-foreground: 0 0% 98%;
|
|
3669
|
+
--secondary: 240 4.8% 95.9%;
|
|
3670
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
3671
|
+
--muted: 240 4.8% 95.9%;
|
|
3672
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
3673
|
+
--accent: 240 4.8% 95.9%;
|
|
3674
|
+
--accent-foreground: 240 5.9% 10%;
|
|
3675
|
+
--destructive: 0 84.2% 60.2%;
|
|
3676
|
+
--destructive-foreground: 0 0% 98%;
|
|
3677
|
+
--border: 240 5.9% 90%;
|
|
3678
|
+
--input: 240 5.9% 90%;
|
|
3679
|
+
--ring: 240 5.9% 10%;
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
.dark {
|
|
3683
|
+
--background: 240 10% 3.9%;
|
|
3684
|
+
--foreground: 0 0% 98%;
|
|
3685
|
+
--card: 240 10% 3.9%;
|
|
3686
|
+
--card-foreground: 0 0% 98%;
|
|
3687
|
+
--popover: 240 10% 3.9%;
|
|
3688
|
+
--popover-foreground: 0 0% 98%;
|
|
3689
|
+
--primary: 0 0% 98%;
|
|
3690
|
+
--primary-foreground: 240 5.9% 10%;
|
|
3691
|
+
--secondary: 240 3.7% 15.9%;
|
|
3692
|
+
--secondary-foreground: 0 0% 98%;
|
|
3693
|
+
--muted: 240 3.7% 15.9%;
|
|
3694
|
+
--muted-foreground: 240 5% 64.9%;
|
|
3695
|
+
--accent: 240 3.7% 15.9%;
|
|
3696
|
+
--accent-foreground: 0 0% 98%;
|
|
3697
|
+
--destructive: 0 62.8% 30.6%;
|
|
3698
|
+
--destructive-foreground: 0 0% 98%;
|
|
3699
|
+
--border: 240 3.7% 15.9%;
|
|
3700
|
+
--input: 240 3.7% 15.9%;
|
|
3701
|
+
--ring: 240 4.9% 83.9%;
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3281
3704
|
`
|
|
3282
3705
|
},
|
|
3283
3706
|
// ─── .gitignore ─────────────────────────────────────────────────────
|
|
@@ -3326,6 +3749,392 @@ expo-env.d.ts
|
|
|
3326
3749
|
];
|
|
3327
3750
|
}
|
|
3328
3751
|
|
|
3752
|
+
// src/templates/login-tabs.ts
|
|
3753
|
+
function generateLoginTabsTemplates(projectName) {
|
|
3754
|
+
return [
|
|
3755
|
+
// ─── app/_layout.tsx (overwrite base) ────────────────────────────────
|
|
3756
|
+
{
|
|
3757
|
+
path: "app/_layout.tsx",
|
|
3758
|
+
content: lines(
|
|
3759
|
+
'import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";',
|
|
3760
|
+
'import { useFonts } from "expo-font";',
|
|
3761
|
+
'import { Stack } from "expo-router";',
|
|
3762
|
+
'import * as SplashScreen from "expo-splash-screen";',
|
|
3763
|
+
'import { useEffect } from "react";',
|
|
3764
|
+
'import { useColorScheme } from "react-native";',
|
|
3765
|
+
"",
|
|
3766
|
+
'import { Colors } from "@/src/constants/Colors";',
|
|
3767
|
+
"",
|
|
3768
|
+
"SplashScreen.preventAutoHideAsync();",
|
|
3769
|
+
"",
|
|
3770
|
+
"export default function RootLayout() {",
|
|
3771
|
+
" const colorScheme = useColorScheme();",
|
|
3772
|
+
" const [loaded] = useFonts({",
|
|
3773
|
+
' SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),',
|
|
3774
|
+
" });",
|
|
3775
|
+
"",
|
|
3776
|
+
" useEffect(() => {",
|
|
3777
|
+
" if (loaded) {",
|
|
3778
|
+
" SplashScreen.hideAsync();",
|
|
3779
|
+
" }",
|
|
3780
|
+
" }, [loaded]);",
|
|
3781
|
+
"",
|
|
3782
|
+
" if (!loaded) {",
|
|
3783
|
+
" return null;",
|
|
3784
|
+
" }",
|
|
3785
|
+
"",
|
|
3786
|
+
" return (",
|
|
3787
|
+
' <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>',
|
|
3788
|
+
" <Stack>",
|
|
3789
|
+
' <Stack.Screen name="login" options={{ headerShown: false }} />',
|
|
3790
|
+
' <Stack.Screen name="(tabs)" options={{ headerShown: false }} />',
|
|
3791
|
+
' <Stack.Screen name="+not-found" />',
|
|
3792
|
+
" </Stack>",
|
|
3793
|
+
" </ThemeProvider>",
|
|
3794
|
+
" );",
|
|
3795
|
+
"}",
|
|
3796
|
+
""
|
|
3797
|
+
)
|
|
3798
|
+
},
|
|
3799
|
+
// ─── app/login.tsx ───────────────────────────────────────────────────
|
|
3800
|
+
{
|
|
3801
|
+
path: "app/login.tsx",
|
|
3802
|
+
content: lines(
|
|
3803
|
+
'import { SignInForm } from "@/src/components/SignInForm";',
|
|
3804
|
+
'import { View } from "react-native";',
|
|
3805
|
+
"",
|
|
3806
|
+
"export default function LoginScreen() {",
|
|
3807
|
+
" return (",
|
|
3808
|
+
' <View className="flex-1 items-center justify-center p-4">',
|
|
3809
|
+
" <SignInForm />",
|
|
3810
|
+
" </View>",
|
|
3811
|
+
" );",
|
|
3812
|
+
"}",
|
|
3813
|
+
""
|
|
3814
|
+
)
|
|
3815
|
+
},
|
|
3816
|
+
// ─── app/(tabs)/_layout.tsx (overwrite base) ───────────────────────
|
|
3817
|
+
{
|
|
3818
|
+
path: "app/(tabs)/_layout.tsx",
|
|
3819
|
+
content: lines(
|
|
3820
|
+
'import { Tabs } from "expo-router";',
|
|
3821
|
+
'import { Platform } from "react-native";',
|
|
3822
|
+
"",
|
|
3823
|
+
'import { Colors } from "@/src/constants/Colors";',
|
|
3824
|
+
'import { useColorScheme } from "@/src/hooks/useColorScheme";',
|
|
3825
|
+
"",
|
|
3826
|
+
"export default function TabLayout() {",
|
|
3827
|
+
" const colorScheme = useColorScheme();",
|
|
3828
|
+
"",
|
|
3829
|
+
" return (",
|
|
3830
|
+
" <Tabs",
|
|
3831
|
+
" screenOptions={{",
|
|
3832
|
+
' tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,',
|
|
3833
|
+
" headerStyle: {",
|
|
3834
|
+
' backgroundColor: Colors[colorScheme ?? "light"].background,',
|
|
3835
|
+
" },",
|
|
3836
|
+
" headerShadowVisible: false,",
|
|
3837
|
+
" tabBarStyle: Platform.select({",
|
|
3838
|
+
" ios: {",
|
|
3839
|
+
' position: "absolute",',
|
|
3840
|
+
" },",
|
|
3841
|
+
" default: {},",
|
|
3842
|
+
" }),",
|
|
3843
|
+
" }}",
|
|
3844
|
+
" >",
|
|
3845
|
+
" <Tabs.Screen",
|
|
3846
|
+
' name="home"',
|
|
3847
|
+
" options={{",
|
|
3848
|
+
' title: "Home",',
|
|
3849
|
+
" tabBarIcon: () => null,",
|
|
3850
|
+
" }}",
|
|
3851
|
+
" />",
|
|
3852
|
+
" <Tabs.Screen",
|
|
3853
|
+
' name="list"',
|
|
3854
|
+
" options={{",
|
|
3855
|
+
' title: "List",',
|
|
3856
|
+
" tabBarIcon: () => null,",
|
|
3857
|
+
" }}",
|
|
3858
|
+
" />",
|
|
3859
|
+
" <Tabs.Screen",
|
|
3860
|
+
' name="mine"',
|
|
3861
|
+
" options={{",
|
|
3862
|
+
' title: "Mine",',
|
|
3863
|
+
" tabBarIcon: () => null,",
|
|
3864
|
+
" }}",
|
|
3865
|
+
" />",
|
|
3866
|
+
" </Tabs>",
|
|
3867
|
+
" );",
|
|
3868
|
+
"}",
|
|
3869
|
+
""
|
|
3870
|
+
)
|
|
3871
|
+
},
|
|
3872
|
+
// ─── app/(tabs)/home.tsx ────────────────────────────────────────────
|
|
3873
|
+
{
|
|
3874
|
+
path: "app/(tabs)/home.tsx",
|
|
3875
|
+
content: lines(
|
|
3876
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
3877
|
+
"import {",
|
|
3878
|
+
" AlertDialog,",
|
|
3879
|
+
" AlertDialogAction,",
|
|
3880
|
+
" AlertDialogCancel,",
|
|
3881
|
+
" AlertDialogContent,",
|
|
3882
|
+
" AlertDialogDescription,",
|
|
3883
|
+
" AlertDialogFooter,",
|
|
3884
|
+
" AlertDialogHeader,",
|
|
3885
|
+
" AlertDialogTitle,",
|
|
3886
|
+
" AlertDialogTrigger,",
|
|
3887
|
+
'} from "@/src/components/ui/alert-dialog";',
|
|
3888
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3889
|
+
'import { View } from "react-native";',
|
|
3890
|
+
"",
|
|
3891
|
+
"export default function HomeScreen() {",
|
|
3892
|
+
" return (",
|
|
3893
|
+
' <View className="flex-1 gap-6 p-6">',
|
|
3894
|
+
' <Text variant="h3">UI Components</Text>',
|
|
3895
|
+
' <Text variant="muted">React Native Reusables components showcase</Text>',
|
|
3896
|
+
"",
|
|
3897
|
+
" {/* Button variants */}",
|
|
3898
|
+
' <View className="gap-3">',
|
|
3899
|
+
' <Text variant="large">Buttons</Text>',
|
|
3900
|
+
' <View className="flex-row flex-wrap gap-2">',
|
|
3901
|
+
" <Button onPress={() => {}}>",
|
|
3902
|
+
" <Text>Default</Text>",
|
|
3903
|
+
" </Button>",
|
|
3904
|
+
' <Button variant="secondary" onPress={() => {}}>',
|
|
3905
|
+
" <Text>Secondary</Text>",
|
|
3906
|
+
" </Button>",
|
|
3907
|
+
' <Button variant="destructive" onPress={() => {}}>',
|
|
3908
|
+
" <Text>Destructive</Text>",
|
|
3909
|
+
" </Button>",
|
|
3910
|
+
' <Button variant="outline" onPress={() => {}}>',
|
|
3911
|
+
" <Text>Outline</Text>",
|
|
3912
|
+
" </Button>",
|
|
3913
|
+
' <Button variant="ghost" onPress={() => {}}>',
|
|
3914
|
+
" <Text>Ghost</Text>",
|
|
3915
|
+
" </Button>",
|
|
3916
|
+
' <Button variant="link" onPress={() => {}}>',
|
|
3917
|
+
" <Text>Link</Text>",
|
|
3918
|
+
" </Button>",
|
|
3919
|
+
" </View>",
|
|
3920
|
+
" </View>",
|
|
3921
|
+
"",
|
|
3922
|
+
" {/* AlertDialog */}",
|
|
3923
|
+
' <View className="gap-3">',
|
|
3924
|
+
' <Text variant="large">Alert Dialog</Text>',
|
|
3925
|
+
" <AlertDialog>",
|
|
3926
|
+
" <AlertDialogTrigger asChild>",
|
|
3927
|
+
' <Button variant="outline">',
|
|
3928
|
+
" <Text>Show Alert</Text>",
|
|
3929
|
+
" </Button>",
|
|
3930
|
+
" </AlertDialogTrigger>",
|
|
3931
|
+
" <AlertDialogContent>",
|
|
3932
|
+
" <AlertDialogHeader>",
|
|
3933
|
+
" <AlertDialogTitle>Are you sure?</AlertDialogTitle>",
|
|
3934
|
+
" <AlertDialogDescription>",
|
|
3935
|
+
" This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
|
3936
|
+
" </AlertDialogDescription>",
|
|
3937
|
+
" </AlertDialogHeader>",
|
|
3938
|
+
" <AlertDialogFooter>",
|
|
3939
|
+
" <AlertDialogCancel>",
|
|
3940
|
+
" <Text>Cancel</Text>",
|
|
3941
|
+
" </AlertDialogCancel>",
|
|
3942
|
+
" <AlertDialogAction>",
|
|
3943
|
+
" <Text>Continue</Text>",
|
|
3944
|
+
" </AlertDialogAction>",
|
|
3945
|
+
" </AlertDialogFooter>",
|
|
3946
|
+
" </AlertDialogContent>",
|
|
3947
|
+
" </AlertDialog>",
|
|
3948
|
+
" </View>",
|
|
3949
|
+
" </View>",
|
|
3950
|
+
" );",
|
|
3951
|
+
"}",
|
|
3952
|
+
""
|
|
3953
|
+
)
|
|
3954
|
+
},
|
|
3955
|
+
// ─── app/(tabs)/list.tsx ────────────────────────────────────────────
|
|
3956
|
+
{
|
|
3957
|
+
path: "app/(tabs)/list.tsx",
|
|
3958
|
+
content: lines(
|
|
3959
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3960
|
+
'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/src/components/ui/card";',
|
|
3961
|
+
'import { View, ScrollView } from "react-native";',
|
|
3962
|
+
"",
|
|
3963
|
+
"const ITEMS = Array.from({ length: 20 }, (_, i) => ({",
|
|
3964
|
+
" id: String(i + 1),",
|
|
3965
|
+
" title: `Item ${i + 1}`,",
|
|
3966
|
+
" description: `Description for item ${i + 1}`,",
|
|
3967
|
+
"}));",
|
|
3968
|
+
"",
|
|
3969
|
+
"export default function ListScreen() {",
|
|
3970
|
+
" return (",
|
|
3971
|
+
' <ScrollView className="flex-1 p-4">',
|
|
3972
|
+
' <Text variant="h3" className="mb-4">List</Text>',
|
|
3973
|
+
' <View className="gap-3">',
|
|
3974
|
+
" {ITEMS.map((item) => (",
|
|
3975
|
+
" <Card key={item.id}>",
|
|
3976
|
+
" <CardHeader>",
|
|
3977
|
+
" <CardTitle>{item.title}</CardTitle>",
|
|
3978
|
+
" <CardDescription>{item.description}</CardDescription>",
|
|
3979
|
+
" </CardHeader>",
|
|
3980
|
+
" <CardContent />",
|
|
3981
|
+
" </Card>",
|
|
3982
|
+
" ))}",
|
|
3983
|
+
" </View>",
|
|
3984
|
+
" </ScrollView>",
|
|
3985
|
+
" );",
|
|
3986
|
+
"}",
|
|
3987
|
+
""
|
|
3988
|
+
)
|
|
3989
|
+
},
|
|
3990
|
+
// ─── app/(tabs)/mine.tsx ────────────────────────────────────────────
|
|
3991
|
+
{
|
|
3992
|
+
path: "app/(tabs)/mine.tsx",
|
|
3993
|
+
content: lines(
|
|
3994
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
3995
|
+
'import { Card, CardContent, CardHeader, CardTitle } from "@/src/components/ui/card";',
|
|
3996
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3997
|
+
'import { View } from "react-native";',
|
|
3998
|
+
'import { router } from "expo-router";',
|
|
3999
|
+
"",
|
|
4000
|
+
"export default function MineScreen() {",
|
|
4001
|
+
" return (",
|
|
4002
|
+
' <View className="flex-1 p-6">',
|
|
4003
|
+
' <Text variant="h3" className="mb-6">Mine</Text>',
|
|
4004
|
+
"",
|
|
4005
|
+
' <Card className="mb-6">',
|
|
4006
|
+
" <CardHeader>",
|
|
4007
|
+
" <CardTitle>Profile</CardTitle>",
|
|
4008
|
+
" </CardHeader>",
|
|
4009
|
+
" <CardContent>",
|
|
4010
|
+
' <View className="gap-2">',
|
|
4011
|
+
' <Text variant="muted">User Name</Text>',
|
|
4012
|
+
" <Text>user@example.com</Text>",
|
|
4013
|
+
" </View>",
|
|
4014
|
+
" </CardContent>",
|
|
4015
|
+
" </Card>",
|
|
4016
|
+
"",
|
|
4017
|
+
" <Button",
|
|
4018
|
+
' variant="destructive"',
|
|
4019
|
+
' className="w-full"',
|
|
4020
|
+
" onPress={() => {",
|
|
4021
|
+
' router.replace("/login");',
|
|
4022
|
+
" }}",
|
|
4023
|
+
" >",
|
|
4024
|
+
" <Text>Sign Out</Text>",
|
|
4025
|
+
" </Button>",
|
|
4026
|
+
" </View>",
|
|
4027
|
+
" );",
|
|
4028
|
+
"}",
|
|
4029
|
+
""
|
|
4030
|
+
)
|
|
4031
|
+
},
|
|
4032
|
+
// ─── src/components/SignInForm.tsx ───────────────────────────────────
|
|
4033
|
+
{
|
|
4034
|
+
path: "src/components/SignInForm.tsx",
|
|
4035
|
+
content: lines(
|
|
4036
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
4037
|
+
"import {",
|
|
4038
|
+
" Card,",
|
|
4039
|
+
" CardContent,",
|
|
4040
|
+
" CardDescription,",
|
|
4041
|
+
" CardHeader,",
|
|
4042
|
+
" CardTitle,",
|
|
4043
|
+
'} from "@/src/components/ui/card";',
|
|
4044
|
+
'import { Input } from "@/src/components/ui/input";',
|
|
4045
|
+
'import { Label } from "@/src/components/ui/label";',
|
|
4046
|
+
'import { Separator } from "@/src/components/ui/separator";',
|
|
4047
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
4048
|
+
'import * as React from "react";',
|
|
4049
|
+
'import { Pressable, type TextInput, View } from "react-native";',
|
|
4050
|
+
'import { router } from "expo-router";',
|
|
4051
|
+
"",
|
|
4052
|
+
"export function SignInForm() {",
|
|
4053
|
+
" const passwordInputRef = React.useRef<TextInput>(null);",
|
|
4054
|
+
"",
|
|
4055
|
+
" function onEmailSubmitEditing() {",
|
|
4056
|
+
" passwordInputRef.current?.focus();",
|
|
4057
|
+
" }",
|
|
4058
|
+
"",
|
|
4059
|
+
" function onSubmit() {",
|
|
4060
|
+
" // TODO: Submit form and navigate to protected screen if successful",
|
|
4061
|
+
' router.replace("/(tabs)/home");',
|
|
4062
|
+
" }",
|
|
4063
|
+
"",
|
|
4064
|
+
" return (",
|
|
4065
|
+
' <View className="gap-6 w-full max-w-sm">',
|
|
4066
|
+
' <Card className="shadow-none sm:shadow-sm sm:shadow-black/5">',
|
|
4067
|
+
" <CardHeader>",
|
|
4068
|
+
' <CardTitle className="text-center text-xl sm:text-left">Sign in to your app</CardTitle>',
|
|
4069
|
+
' <CardDescription className="text-center sm:text-left">',
|
|
4070
|
+
" Welcome back! Please sign in to continue",
|
|
4071
|
+
" </CardDescription>",
|
|
4072
|
+
" </CardHeader>",
|
|
4073
|
+
' <CardContent className="gap-6">',
|
|
4074
|
+
' <View className="gap-6">',
|
|
4075
|
+
' <View className="gap-1.5">',
|
|
4076
|
+
' <Label nativeID="email">Email</Label>',
|
|
4077
|
+
" <Input",
|
|
4078
|
+
' placeholder="m@example.com"',
|
|
4079
|
+
' keyboardType="email-address"',
|
|
4080
|
+
' autoComplete="email"',
|
|
4081
|
+
' autoCapitalize="none"',
|
|
4082
|
+
" onSubmitEditing={onEmailSubmitEditing}",
|
|
4083
|
+
' returnKeyType="next"',
|
|
4084
|
+
' submitBehavior="submit"',
|
|
4085
|
+
" />",
|
|
4086
|
+
" </View>",
|
|
4087
|
+
' <View className="gap-1.5">',
|
|
4088
|
+
' <View className="flex-row items-center">',
|
|
4089
|
+
' <Label nativeID="password">Password</Label>',
|
|
4090
|
+
" <Button",
|
|
4091
|
+
' variant="link"',
|
|
4092
|
+
' size="sm"',
|
|
4093
|
+
' className="ml-auto h-4 px-1 py-0 sm:h-4"',
|
|
4094
|
+
" onPress={() => {",
|
|
4095
|
+
" // TODO: Navigate to forgot password screen",
|
|
4096
|
+
" }}",
|
|
4097
|
+
" >",
|
|
4098
|
+
' <Text className="font-normal leading-4">Forgot your password?</Text>',
|
|
4099
|
+
" </Button>",
|
|
4100
|
+
" </View>",
|
|
4101
|
+
" <Input",
|
|
4102
|
+
" ref={passwordInputRef}",
|
|
4103
|
+
" secureTextEntry",
|
|
4104
|
+
' returnKeyType="send"',
|
|
4105
|
+
" onSubmitEditing={onSubmit}",
|
|
4106
|
+
" />",
|
|
4107
|
+
" </View>",
|
|
4108
|
+
' <Button className="w-full" onPress={onSubmit}>',
|
|
4109
|
+
" <Text>Continue</Text>",
|
|
4110
|
+
" </Button>",
|
|
4111
|
+
" </View>",
|
|
4112
|
+
' <Text className="text-center text-sm">',
|
|
4113
|
+
' Don't have an account?{" "}',
|
|
4114
|
+
" <Pressable",
|
|
4115
|
+
" onPress={() => {",
|
|
4116
|
+
" // TODO: Navigate to sign up screen",
|
|
4117
|
+
" }}",
|
|
4118
|
+
" >",
|
|
4119
|
+
' <Text className="text-sm underline underline-offset-4">Sign up</Text>',
|
|
4120
|
+
" </Pressable>",
|
|
4121
|
+
" </Text>",
|
|
4122
|
+
' <View className="flex-row items-center">',
|
|
4123
|
+
' <Separator className="flex-1" />',
|
|
4124
|
+
' <Text className="text-muted-foreground px-4 text-sm">or</Text>',
|
|
4125
|
+
' <Separator className="flex-1" />',
|
|
4126
|
+
" </View>",
|
|
4127
|
+
" </CardContent>",
|
|
4128
|
+
" </Card>",
|
|
4129
|
+
" </View>",
|
|
4130
|
+
" );",
|
|
4131
|
+
"}",
|
|
4132
|
+
""
|
|
4133
|
+
)
|
|
4134
|
+
}
|
|
4135
|
+
];
|
|
4136
|
+
}
|
|
4137
|
+
|
|
3329
4138
|
// src/utils/file.ts
|
|
3330
4139
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
3331
4140
|
var import_path = __toESM(require("path"));
|
|
@@ -3388,7 +4197,8 @@ function generateBasePackageJson(projectName) {
|
|
|
3388
4197
|
"react-native-safe-area-context": "~5.6.0",
|
|
3389
4198
|
"react-native-screens": "~4.16.0",
|
|
3390
4199
|
nativewind: "^4.1.0",
|
|
3391
|
-
tailwindcss: "^3.4.0"
|
|
4200
|
+
tailwindcss: "^3.4.0",
|
|
4201
|
+
"react-native-svg": "^15.8.0"
|
|
3392
4202
|
},
|
|
3393
4203
|
devDependencies: {
|
|
3394
4204
|
"@types/react": "~19.1.0",
|
|
@@ -3399,7 +4209,7 @@ function generateBasePackageJson(projectName) {
|
|
|
3399
4209
|
|
|
3400
4210
|
// src/index.ts
|
|
3401
4211
|
var import_execa = require("execa");
|
|
3402
|
-
var CLI_VERSION = "1.
|
|
4212
|
+
var CLI_VERSION = "1.3.0";
|
|
3403
4213
|
var CONFIG_FILE = ".expo-bbase.json";
|
|
3404
4214
|
async function run() {
|
|
3405
4215
|
const program = new import_commander.Command();
|
|
@@ -3440,10 +4250,34 @@ async function createProject(projectName) {
|
|
|
3440
4250
|
import_chalk.default.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")
|
|
3441
4251
|
);
|
|
3442
4252
|
console.log();
|
|
4253
|
+
const { uiTemplate } = await (0, import_prompts.default)({
|
|
4254
|
+
type: "select",
|
|
4255
|
+
name: "uiTemplate",
|
|
4256
|
+
message: "Choose a UI template",
|
|
4257
|
+
choices: [
|
|
4258
|
+
{
|
|
4259
|
+
title: `${import_chalk.default.bold("Login + Tabs")} \u2014 Login page, Home/List/Mine tabs with rnr components`,
|
|
4260
|
+
value: "login-tabs",
|
|
4261
|
+
description: "Pre-built login form, 3-tab layout with Button & AlertDialog demos"
|
|
4262
|
+
},
|
|
4263
|
+
{
|
|
4264
|
+
title: `${import_chalk.default.bold("Default")} \u2014 Blank tabs (Home + Explore)`,
|
|
4265
|
+
value: "default",
|
|
4266
|
+
description: "Minimal starter with basic tab navigation"
|
|
4267
|
+
}
|
|
4268
|
+
],
|
|
4269
|
+
initial: 0
|
|
4270
|
+
});
|
|
4271
|
+
if (uiTemplate === void 0) {
|
|
4272
|
+
console.log(import_chalk.default.yellow("\nCancelled."));
|
|
4273
|
+
process.exit(0);
|
|
4274
|
+
}
|
|
4275
|
+
const isLoginTabs = uiTemplate === "login-tabs";
|
|
3443
4276
|
const choices = modules.map((m) => ({
|
|
3444
4277
|
title: `${import_chalk.default.bold(m.name)} \u2014 ${import_chalk.default.gray(m.description)}`,
|
|
3445
4278
|
value: m.id,
|
|
3446
|
-
|
|
4279
|
+
// Auto-select ui-reusables when login-tabs template is chosen
|
|
4280
|
+
selected: isLoginTabs && m.id === "ui-reusables" ? true : m.defaultChecked
|
|
3447
4281
|
}));
|
|
3448
4282
|
const { selectedModules } = await (0, import_prompts.default)({
|
|
3449
4283
|
type: "multiselect",
|
|
@@ -3457,11 +4291,20 @@ async function createProject(projectName) {
|
|
|
3457
4291
|
console.log(import_chalk.default.yellow("\nCancelled."));
|
|
3458
4292
|
process.exit(0);
|
|
3459
4293
|
}
|
|
3460
|
-
|
|
4294
|
+
let finalModuleIds = selectedModules;
|
|
4295
|
+
if (isLoginTabs && !finalModuleIds.includes("ui-reusables")) {
|
|
4296
|
+
finalModuleIds = ["ui-reusables", ...finalModuleIds];
|
|
4297
|
+
}
|
|
4298
|
+
const selectedModuleDefs = getModulesByIds(finalModuleIds);
|
|
3461
4299
|
const targetDir = import_path2.default.resolve(process.cwd(), projectName);
|
|
3462
4300
|
console.log();
|
|
3463
4301
|
console.log(import_chalk.default.white(` \u{1F4E6} Project: ${import_chalk.default.bold(projectName)}`));
|
|
3464
4302
|
console.log(import_chalk.default.white(` \u{1F4C2} Path: ${import_chalk.default.gray(targetDir)}`));
|
|
4303
|
+
console.log(
|
|
4304
|
+
import_chalk.default.white(
|
|
4305
|
+
` \u{1F3A8} Template: ${import_chalk.default.green(isLoginTabs ? "Login + Tabs" : "Default")}`
|
|
4306
|
+
)
|
|
4307
|
+
);
|
|
3465
4308
|
console.log(
|
|
3466
4309
|
import_chalk.default.white(
|
|
3467
4310
|
` \u{1F9E9} Modules: ${import_chalk.default.green(selectedModuleDefs.map((m) => m.name).join(", ") || "none")}`
|
|
@@ -3484,6 +4327,15 @@ async function createProject(projectName) {
|
|
|
3484
4327
|
await writeFile(filePath, content);
|
|
3485
4328
|
}
|
|
3486
4329
|
}
|
|
4330
|
+
if (isLoginTabs) {
|
|
4331
|
+
spinner.text = "Writing UI template files...";
|
|
4332
|
+
const loginTabsTemplates = generateLoginTabsTemplates(projectName);
|
|
4333
|
+
for (const file of loginTabsTemplates) {
|
|
4334
|
+
const filePath = import_path2.default.join(targetDir, file.path);
|
|
4335
|
+
const content = replaceTemplateVars(file.content, { projectName });
|
|
4336
|
+
await writeFile(filePath, content);
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
3487
4339
|
spinner.text = "Generating package.json...";
|
|
3488
4340
|
const pkgJson = generateBasePackageJson(projectName);
|
|
3489
4341
|
const allDeps = {};
|
|
@@ -3507,8 +4359,9 @@ async function createProject(projectName) {
|
|
|
3507
4359
|
await updateLayoutFile(targetDir, selectedModuleDefs);
|
|
3508
4360
|
await writeProjectConfig(targetDir, {
|
|
3509
4361
|
projectName,
|
|
3510
|
-
selectedModules,
|
|
3511
|
-
cliVersion: CLI_VERSION
|
|
4362
|
+
selectedModules: finalModuleIds,
|
|
4363
|
+
cliVersion: CLI_VERSION,
|
|
4364
|
+
uiTemplate: isLoginTabs ? "login-tabs" : "default"
|
|
3512
4365
|
});
|
|
3513
4366
|
spinner.text = "Installing dependencies (yarn install)...";
|
|
3514
4367
|
try {
|
|
@@ -3527,6 +4380,9 @@ async function createProject(projectName) {
|
|
|
3527
4380
|
console.log(import_chalk.default.bold(" \u{1F389} Next steps:"));
|
|
3528
4381
|
console.log(import_chalk.default.white(` cd ${projectName}`));
|
|
3529
4382
|
console.log(import_chalk.default.white(" npx expo start"));
|
|
4383
|
+
if (isLoginTabs) {
|
|
4384
|
+
console.log(import_chalk.default.gray(" \u2192 App starts at login page, sign in to see tabs"));
|
|
4385
|
+
}
|
|
3530
4386
|
console.log();
|
|
3531
4387
|
if (selectedModuleDefs.length > 0) {
|
|
3532
4388
|
console.log(import_chalk.default.bold(" \u{1F4CB} Selected modules:"));
|