expo-bbase 1.2.0 → 1.3.1
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/README.md +147 -48
- package/dist/index.js +1077 -230
- 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
|
+
"}",
|
|
2782
3035
|
"",
|
|
2783
|
-
"
|
|
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
|
+
"}",
|
|
3061
|
+
"",
|
|
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";
|
|
@@ -3187,10 +3515,8 @@ export {};
|
|
|
3187
3515
|
"slug": "${projectName}",
|
|
3188
3516
|
"version": "1.0.0",
|
|
3189
3517
|
"orientation": "portrait",
|
|
3190
|
-
"icon": "./assets/icon.png",
|
|
3191
3518
|
"userInterfaceStyle": "light",
|
|
3192
3519
|
"splash": {
|
|
3193
|
-
"image": "./assets/splash.png",
|
|
3194
3520
|
"resizeMode": "contain",
|
|
3195
3521
|
"backgroundColor": "#ffffff"
|
|
3196
3522
|
},
|
|
@@ -3199,15 +3525,8 @@ export {};
|
|
|
3199
3525
|
"bundleIdentifier": "com.${projectName}.app"
|
|
3200
3526
|
},
|
|
3201
3527
|
"android": {
|
|
3202
|
-
"adaptiveIcon": {
|
|
3203
|
-
"foregroundImage": "./assets/adaptive-icon.png",
|
|
3204
|
-
"backgroundColor": "#ffffff"
|
|
3205
|
-
},
|
|
3206
3528
|
"package": "com.${projectName}.app"
|
|
3207
3529
|
},
|
|
3208
|
-
"web": {
|
|
3209
|
-
"favicon": "./assets/favicon.png"
|
|
3210
|
-
},
|
|
3211
3530
|
"plugins": [
|
|
3212
3531
|
"expo-router",
|
|
3213
3532
|
"expo-splash-screen"
|
|
@@ -3251,8 +3570,45 @@ module.exports = {
|
|
|
3251
3570
|
"./app/**/*.{js,jsx,ts,tsx}",
|
|
3252
3571
|
"./src/**/*.{js,jsx,ts,tsx}",
|
|
3253
3572
|
],
|
|
3573
|
+
presets: [require("nativewind/preset")],
|
|
3254
3574
|
theme: {
|
|
3255
|
-
extend: {
|
|
3575
|
+
extend: {
|
|
3576
|
+
colors: {
|
|
3577
|
+
border: "hsl(var(--border))",
|
|
3578
|
+
input: "hsl(var(--input))",
|
|
3579
|
+
ring: "hsl(var(--ring))",
|
|
3580
|
+
background: "hsl(var(--background))",
|
|
3581
|
+
foreground: "hsl(var(--foreground))",
|
|
3582
|
+
primary: {
|
|
3583
|
+
DEFAULT: "hsl(var(--primary))",
|
|
3584
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
3585
|
+
},
|
|
3586
|
+
secondary: {
|
|
3587
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
3588
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
3589
|
+
},
|
|
3590
|
+
destructive: {
|
|
3591
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
3592
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
3593
|
+
},
|
|
3594
|
+
muted: {
|
|
3595
|
+
DEFAULT: "hsl(var(--muted))",
|
|
3596
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
3597
|
+
},
|
|
3598
|
+
accent: {
|
|
3599
|
+
DEFAULT: "hsl(var(--accent))",
|
|
3600
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
3601
|
+
},
|
|
3602
|
+
popover: {
|
|
3603
|
+
DEFAULT: "hsl(var(--popover))",
|
|
3604
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
3605
|
+
},
|
|
3606
|
+
card: {
|
|
3607
|
+
DEFAULT: "hsl(var(--card))",
|
|
3608
|
+
foreground: "hsl(var(--card-foreground))",
|
|
3609
|
+
},
|
|
3610
|
+
},
|
|
3611
|
+
},
|
|
3256
3612
|
},
|
|
3257
3613
|
plugins: [],
|
|
3258
3614
|
};
|
|
@@ -3262,10 +3618,11 @@ module.exports = {
|
|
|
3262
3618
|
{
|
|
3263
3619
|
path: "metro.config.js",
|
|
3264
3620
|
content: `const { getDefaultConfig } = require("expo/metro-config");
|
|
3621
|
+
const { withNativeWind } = require("nativewind/metro");
|
|
3265
3622
|
|
|
3266
3623
|
const config = getDefaultConfig(__dirname);
|
|
3267
3624
|
|
|
3268
|
-
module.exports = config;
|
|
3625
|
+
module.exports = withNativeWind(config, { input: "./global.css" });
|
|
3269
3626
|
`
|
|
3270
3627
|
},
|
|
3271
3628
|
// ─── babel.config.js ─────────────────────────────────────────────────
|
|
@@ -3274,10 +3631,67 @@ module.exports = config;
|
|
|
3274
3631
|
content: `module.exports = function (api) {
|
|
3275
3632
|
api.cache(true);
|
|
3276
3633
|
return {
|
|
3277
|
-
presets: [
|
|
3278
|
-
|
|
3634
|
+
presets: [
|
|
3635
|
+
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
|
3636
|
+
"nativewind/babel",
|
|
3637
|
+
],
|
|
3638
|
+
plugins: [],
|
|
3279
3639
|
};
|
|
3280
3640
|
};
|
|
3641
|
+
`
|
|
3642
|
+
},
|
|
3643
|
+
// ─── global.css (NativeWind CSS variables for rnr components) ─────────
|
|
3644
|
+
{
|
|
3645
|
+
path: "global.css",
|
|
3646
|
+
content: `@tailwind base;
|
|
3647
|
+
@tailwind components;
|
|
3648
|
+
@tailwind utilities;
|
|
3649
|
+
|
|
3650
|
+
@layer base {
|
|
3651
|
+
:root {
|
|
3652
|
+
--background: 0 0% 100%;
|
|
3653
|
+
--foreground: 240 10% 3.9%;
|
|
3654
|
+
--card: 0 0% 100%;
|
|
3655
|
+
--card-foreground: 240 10% 3.9%;
|
|
3656
|
+
--popover: 0 0% 100%;
|
|
3657
|
+
--popover-foreground: 240 10% 3.9%;
|
|
3658
|
+
--primary: 240 5.9% 10%;
|
|
3659
|
+
--primary-foreground: 0 0% 98%;
|
|
3660
|
+
--secondary: 240 4.8% 95.9%;
|
|
3661
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
3662
|
+
--muted: 240 4.8% 95.9%;
|
|
3663
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
3664
|
+
--accent: 240 4.8% 95.9%;
|
|
3665
|
+
--accent-foreground: 240 5.9% 10%;
|
|
3666
|
+
--destructive: 0 84.2% 60.2%;
|
|
3667
|
+
--destructive-foreground: 0 0% 98%;
|
|
3668
|
+
--border: 240 5.9% 90%;
|
|
3669
|
+
--input: 240 5.9% 90%;
|
|
3670
|
+
--ring: 240 5.9% 10%;
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
.dark {
|
|
3674
|
+
--background: 240 10% 3.9%;
|
|
3675
|
+
--foreground: 0 0% 98%;
|
|
3676
|
+
--card: 240 10% 3.9%;
|
|
3677
|
+
--card-foreground: 0 0% 98%;
|
|
3678
|
+
--popover: 240 10% 3.9%;
|
|
3679
|
+
--popover-foreground: 0 0% 98%;
|
|
3680
|
+
--primary: 0 0% 98%;
|
|
3681
|
+
--primary-foreground: 240 5.9% 10%;
|
|
3682
|
+
--secondary: 240 3.7% 15.9%;
|
|
3683
|
+
--secondary-foreground: 0 0% 98%;
|
|
3684
|
+
--muted: 240 3.7% 15.9%;
|
|
3685
|
+
--muted-foreground: 240 5% 64.9%;
|
|
3686
|
+
--accent: 240 3.7% 15.9%;
|
|
3687
|
+
--accent-foreground: 0 0% 98%;
|
|
3688
|
+
--destructive: 0 62.8% 30.6%;
|
|
3689
|
+
--destructive-foreground: 0 0% 98%;
|
|
3690
|
+
--border: 240 3.7% 15.9%;
|
|
3691
|
+
--input: 240 3.7% 15.9%;
|
|
3692
|
+
--ring: 240 4.9% 83.9%;
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3281
3695
|
`
|
|
3282
3696
|
},
|
|
3283
3697
|
// ─── .gitignore ─────────────────────────────────────────────────────
|
|
@@ -3326,6 +3740,392 @@ expo-env.d.ts
|
|
|
3326
3740
|
];
|
|
3327
3741
|
}
|
|
3328
3742
|
|
|
3743
|
+
// src/templates/login-tabs.ts
|
|
3744
|
+
function generateLoginTabsTemplates(projectName) {
|
|
3745
|
+
return [
|
|
3746
|
+
// ─── app/_layout.tsx (overwrite base) ────────────────────────────────
|
|
3747
|
+
{
|
|
3748
|
+
path: "app/_layout.tsx",
|
|
3749
|
+
content: lines(
|
|
3750
|
+
'import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";',
|
|
3751
|
+
'import { useFonts } from "expo-font";',
|
|
3752
|
+
'import { Stack } from "expo-router";',
|
|
3753
|
+
'import * as SplashScreen from "expo-splash-screen";',
|
|
3754
|
+
'import { useEffect } from "react";',
|
|
3755
|
+
'import { useColorScheme } from "react-native";',
|
|
3756
|
+
"",
|
|
3757
|
+
'import { Colors } from "@/src/constants/Colors";',
|
|
3758
|
+
"",
|
|
3759
|
+
"SplashScreen.preventAutoHideAsync();",
|
|
3760
|
+
"",
|
|
3761
|
+
"export default function RootLayout() {",
|
|
3762
|
+
" const colorScheme = useColorScheme();",
|
|
3763
|
+
" const [loaded] = useFonts({",
|
|
3764
|
+
' SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),',
|
|
3765
|
+
" });",
|
|
3766
|
+
"",
|
|
3767
|
+
" useEffect(() => {",
|
|
3768
|
+
" if (loaded) {",
|
|
3769
|
+
" SplashScreen.hideAsync();",
|
|
3770
|
+
" }",
|
|
3771
|
+
" }, [loaded]);",
|
|
3772
|
+
"",
|
|
3773
|
+
" if (!loaded) {",
|
|
3774
|
+
" return null;",
|
|
3775
|
+
" }",
|
|
3776
|
+
"",
|
|
3777
|
+
" return (",
|
|
3778
|
+
' <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>',
|
|
3779
|
+
" <Stack>",
|
|
3780
|
+
' <Stack.Screen name="login" options={{ headerShown: false }} />',
|
|
3781
|
+
' <Stack.Screen name="(tabs)" options={{ headerShown: false }} />',
|
|
3782
|
+
' <Stack.Screen name="+not-found" />',
|
|
3783
|
+
" </Stack>",
|
|
3784
|
+
" </ThemeProvider>",
|
|
3785
|
+
" );",
|
|
3786
|
+
"}",
|
|
3787
|
+
""
|
|
3788
|
+
)
|
|
3789
|
+
},
|
|
3790
|
+
// ─── app/login.tsx ───────────────────────────────────────────────────
|
|
3791
|
+
{
|
|
3792
|
+
path: "app/login.tsx",
|
|
3793
|
+
content: lines(
|
|
3794
|
+
'import { SignInForm } from "@/src/components/SignInForm";',
|
|
3795
|
+
'import { View } from "react-native";',
|
|
3796
|
+
"",
|
|
3797
|
+
"export default function LoginScreen() {",
|
|
3798
|
+
" return (",
|
|
3799
|
+
' <View className="flex-1 items-center justify-center p-4">',
|
|
3800
|
+
" <SignInForm />",
|
|
3801
|
+
" </View>",
|
|
3802
|
+
" );",
|
|
3803
|
+
"}",
|
|
3804
|
+
""
|
|
3805
|
+
)
|
|
3806
|
+
},
|
|
3807
|
+
// ─── app/(tabs)/_layout.tsx (overwrite base) ───────────────────────
|
|
3808
|
+
{
|
|
3809
|
+
path: "app/(tabs)/_layout.tsx",
|
|
3810
|
+
content: lines(
|
|
3811
|
+
'import { Tabs } from "expo-router";',
|
|
3812
|
+
'import { Platform } from "react-native";',
|
|
3813
|
+
"",
|
|
3814
|
+
'import { Colors } from "@/src/constants/Colors";',
|
|
3815
|
+
'import { useColorScheme } from "@/src/hooks/useColorScheme";',
|
|
3816
|
+
"",
|
|
3817
|
+
"export default function TabLayout() {",
|
|
3818
|
+
" const colorScheme = useColorScheme();",
|
|
3819
|
+
"",
|
|
3820
|
+
" return (",
|
|
3821
|
+
" <Tabs",
|
|
3822
|
+
" screenOptions={{",
|
|
3823
|
+
' tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,',
|
|
3824
|
+
" headerStyle: {",
|
|
3825
|
+
' backgroundColor: Colors[colorScheme ?? "light"].background,',
|
|
3826
|
+
" },",
|
|
3827
|
+
" headerShadowVisible: false,",
|
|
3828
|
+
" tabBarStyle: Platform.select({",
|
|
3829
|
+
" ios: {",
|
|
3830
|
+
' position: "absolute",',
|
|
3831
|
+
" },",
|
|
3832
|
+
" default: {},",
|
|
3833
|
+
" }),",
|
|
3834
|
+
" }}",
|
|
3835
|
+
" >",
|
|
3836
|
+
" <Tabs.Screen",
|
|
3837
|
+
' name="home"',
|
|
3838
|
+
" options={{",
|
|
3839
|
+
' title: "Home",',
|
|
3840
|
+
" tabBarIcon: () => null,",
|
|
3841
|
+
" }}",
|
|
3842
|
+
" />",
|
|
3843
|
+
" <Tabs.Screen",
|
|
3844
|
+
' name="list"',
|
|
3845
|
+
" options={{",
|
|
3846
|
+
' title: "List",',
|
|
3847
|
+
" tabBarIcon: () => null,",
|
|
3848
|
+
" }}",
|
|
3849
|
+
" />",
|
|
3850
|
+
" <Tabs.Screen",
|
|
3851
|
+
' name="mine"',
|
|
3852
|
+
" options={{",
|
|
3853
|
+
' title: "Mine",',
|
|
3854
|
+
" tabBarIcon: () => null,",
|
|
3855
|
+
" }}",
|
|
3856
|
+
" />",
|
|
3857
|
+
" </Tabs>",
|
|
3858
|
+
" );",
|
|
3859
|
+
"}",
|
|
3860
|
+
""
|
|
3861
|
+
)
|
|
3862
|
+
},
|
|
3863
|
+
// ─── app/(tabs)/home.tsx ────────────────────────────────────────────
|
|
3864
|
+
{
|
|
3865
|
+
path: "app/(tabs)/home.tsx",
|
|
3866
|
+
content: lines(
|
|
3867
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
3868
|
+
"import {",
|
|
3869
|
+
" AlertDialog,",
|
|
3870
|
+
" AlertDialogAction,",
|
|
3871
|
+
" AlertDialogCancel,",
|
|
3872
|
+
" AlertDialogContent,",
|
|
3873
|
+
" AlertDialogDescription,",
|
|
3874
|
+
" AlertDialogFooter,",
|
|
3875
|
+
" AlertDialogHeader,",
|
|
3876
|
+
" AlertDialogTitle,",
|
|
3877
|
+
" AlertDialogTrigger,",
|
|
3878
|
+
'} from "@/src/components/ui/alert-dialog";',
|
|
3879
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3880
|
+
'import { View } from "react-native";',
|
|
3881
|
+
"",
|
|
3882
|
+
"export default function HomeScreen() {",
|
|
3883
|
+
" return (",
|
|
3884
|
+
' <View className="flex-1 gap-6 p-6">',
|
|
3885
|
+
' <Text variant="h3">UI Components</Text>',
|
|
3886
|
+
' <Text variant="muted">React Native Reusables components showcase</Text>',
|
|
3887
|
+
"",
|
|
3888
|
+
" {/* Button variants */}",
|
|
3889
|
+
' <View className="gap-3">',
|
|
3890
|
+
' <Text variant="large">Buttons</Text>',
|
|
3891
|
+
' <View className="flex-row flex-wrap gap-2">',
|
|
3892
|
+
" <Button onPress={() => {}}>",
|
|
3893
|
+
" <Text>Default</Text>",
|
|
3894
|
+
" </Button>",
|
|
3895
|
+
' <Button variant="secondary" onPress={() => {}}>',
|
|
3896
|
+
" <Text>Secondary</Text>",
|
|
3897
|
+
" </Button>",
|
|
3898
|
+
' <Button variant="destructive" onPress={() => {}}>',
|
|
3899
|
+
" <Text>Destructive</Text>",
|
|
3900
|
+
" </Button>",
|
|
3901
|
+
' <Button variant="outline" onPress={() => {}}>',
|
|
3902
|
+
" <Text>Outline</Text>",
|
|
3903
|
+
" </Button>",
|
|
3904
|
+
' <Button variant="ghost" onPress={() => {}}>',
|
|
3905
|
+
" <Text>Ghost</Text>",
|
|
3906
|
+
" </Button>",
|
|
3907
|
+
' <Button variant="link" onPress={() => {}}>',
|
|
3908
|
+
" <Text>Link</Text>",
|
|
3909
|
+
" </Button>",
|
|
3910
|
+
" </View>",
|
|
3911
|
+
" </View>",
|
|
3912
|
+
"",
|
|
3913
|
+
" {/* AlertDialog */}",
|
|
3914
|
+
' <View className="gap-3">',
|
|
3915
|
+
' <Text variant="large">Alert Dialog</Text>',
|
|
3916
|
+
" <AlertDialog>",
|
|
3917
|
+
" <AlertDialogTrigger asChild>",
|
|
3918
|
+
' <Button variant="outline">',
|
|
3919
|
+
" <Text>Show Alert</Text>",
|
|
3920
|
+
" </Button>",
|
|
3921
|
+
" </AlertDialogTrigger>",
|
|
3922
|
+
" <AlertDialogContent>",
|
|
3923
|
+
" <AlertDialogHeader>",
|
|
3924
|
+
" <AlertDialogTitle>Are you sure?</AlertDialogTitle>",
|
|
3925
|
+
" <AlertDialogDescription>",
|
|
3926
|
+
" This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
|
3927
|
+
" </AlertDialogDescription>",
|
|
3928
|
+
" </AlertDialogHeader>",
|
|
3929
|
+
" <AlertDialogFooter>",
|
|
3930
|
+
" <AlertDialogCancel>",
|
|
3931
|
+
" <Text>Cancel</Text>",
|
|
3932
|
+
" </AlertDialogCancel>",
|
|
3933
|
+
" <AlertDialogAction>",
|
|
3934
|
+
" <Text>Continue</Text>",
|
|
3935
|
+
" </AlertDialogAction>",
|
|
3936
|
+
" </AlertDialogFooter>",
|
|
3937
|
+
" </AlertDialogContent>",
|
|
3938
|
+
" </AlertDialog>",
|
|
3939
|
+
" </View>",
|
|
3940
|
+
" </View>",
|
|
3941
|
+
" );",
|
|
3942
|
+
"}",
|
|
3943
|
+
""
|
|
3944
|
+
)
|
|
3945
|
+
},
|
|
3946
|
+
// ─── app/(tabs)/list.tsx ────────────────────────────────────────────
|
|
3947
|
+
{
|
|
3948
|
+
path: "app/(tabs)/list.tsx",
|
|
3949
|
+
content: lines(
|
|
3950
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3951
|
+
'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/src/components/ui/card";',
|
|
3952
|
+
'import { View, ScrollView } from "react-native";',
|
|
3953
|
+
"",
|
|
3954
|
+
"const ITEMS = Array.from({ length: 20 }, (_, i) => ({",
|
|
3955
|
+
" id: String(i + 1),",
|
|
3956
|
+
" title: `Item ${i + 1}`,",
|
|
3957
|
+
" description: `Description for item ${i + 1}`,",
|
|
3958
|
+
"}));",
|
|
3959
|
+
"",
|
|
3960
|
+
"export default function ListScreen() {",
|
|
3961
|
+
" return (",
|
|
3962
|
+
' <ScrollView className="flex-1 p-4">',
|
|
3963
|
+
' <Text variant="h3" className="mb-4">List</Text>',
|
|
3964
|
+
' <View className="gap-3">',
|
|
3965
|
+
" {ITEMS.map((item) => (",
|
|
3966
|
+
" <Card key={item.id}>",
|
|
3967
|
+
" <CardHeader>",
|
|
3968
|
+
" <CardTitle>{item.title}</CardTitle>",
|
|
3969
|
+
" <CardDescription>{item.description}</CardDescription>",
|
|
3970
|
+
" </CardHeader>",
|
|
3971
|
+
" <CardContent />",
|
|
3972
|
+
" </Card>",
|
|
3973
|
+
" ))}",
|
|
3974
|
+
" </View>",
|
|
3975
|
+
" </ScrollView>",
|
|
3976
|
+
" );",
|
|
3977
|
+
"}",
|
|
3978
|
+
""
|
|
3979
|
+
)
|
|
3980
|
+
},
|
|
3981
|
+
// ─── app/(tabs)/mine.tsx ────────────────────────────────────────────
|
|
3982
|
+
{
|
|
3983
|
+
path: "app/(tabs)/mine.tsx",
|
|
3984
|
+
content: lines(
|
|
3985
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
3986
|
+
'import { Card, CardContent, CardHeader, CardTitle } from "@/src/components/ui/card";',
|
|
3987
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3988
|
+
'import { View } from "react-native";',
|
|
3989
|
+
'import { router } from "expo-router";',
|
|
3990
|
+
"",
|
|
3991
|
+
"export default function MineScreen() {",
|
|
3992
|
+
" return (",
|
|
3993
|
+
' <View className="flex-1 p-6">',
|
|
3994
|
+
' <Text variant="h3" className="mb-6">Mine</Text>',
|
|
3995
|
+
"",
|
|
3996
|
+
' <Card className="mb-6">',
|
|
3997
|
+
" <CardHeader>",
|
|
3998
|
+
" <CardTitle>Profile</CardTitle>",
|
|
3999
|
+
" </CardHeader>",
|
|
4000
|
+
" <CardContent>",
|
|
4001
|
+
' <View className="gap-2">',
|
|
4002
|
+
' <Text variant="muted">User Name</Text>',
|
|
4003
|
+
" <Text>user@example.com</Text>",
|
|
4004
|
+
" </View>",
|
|
4005
|
+
" </CardContent>",
|
|
4006
|
+
" </Card>",
|
|
4007
|
+
"",
|
|
4008
|
+
" <Button",
|
|
4009
|
+
' variant="destructive"',
|
|
4010
|
+
' className="w-full"',
|
|
4011
|
+
" onPress={() => {",
|
|
4012
|
+
' router.replace("/login");',
|
|
4013
|
+
" }}",
|
|
4014
|
+
" >",
|
|
4015
|
+
" <Text>Sign Out</Text>",
|
|
4016
|
+
" </Button>",
|
|
4017
|
+
" </View>",
|
|
4018
|
+
" );",
|
|
4019
|
+
"}",
|
|
4020
|
+
""
|
|
4021
|
+
)
|
|
4022
|
+
},
|
|
4023
|
+
// ─── src/components/SignInForm.tsx ───────────────────────────────────
|
|
4024
|
+
{
|
|
4025
|
+
path: "src/components/SignInForm.tsx",
|
|
4026
|
+
content: lines(
|
|
4027
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
4028
|
+
"import {",
|
|
4029
|
+
" Card,",
|
|
4030
|
+
" CardContent,",
|
|
4031
|
+
" CardDescription,",
|
|
4032
|
+
" CardHeader,",
|
|
4033
|
+
" CardTitle,",
|
|
4034
|
+
'} from "@/src/components/ui/card";',
|
|
4035
|
+
'import { Input } from "@/src/components/ui/input";',
|
|
4036
|
+
'import { Label } from "@/src/components/ui/label";',
|
|
4037
|
+
'import { Separator } from "@/src/components/ui/separator";',
|
|
4038
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
4039
|
+
'import * as React from "react";',
|
|
4040
|
+
'import { Pressable, type TextInput, View } from "react-native";',
|
|
4041
|
+
'import { router } from "expo-router";',
|
|
4042
|
+
"",
|
|
4043
|
+
"export function SignInForm() {",
|
|
4044
|
+
" const passwordInputRef = React.useRef<TextInput>(null);",
|
|
4045
|
+
"",
|
|
4046
|
+
" function onEmailSubmitEditing() {",
|
|
4047
|
+
" passwordInputRef.current?.focus();",
|
|
4048
|
+
" }",
|
|
4049
|
+
"",
|
|
4050
|
+
" function onSubmit() {",
|
|
4051
|
+
" // TODO: Submit form and navigate to protected screen if successful",
|
|
4052
|
+
' router.replace("/(tabs)/home");',
|
|
4053
|
+
" }",
|
|
4054
|
+
"",
|
|
4055
|
+
" return (",
|
|
4056
|
+
' <View className="gap-6 w-full max-w-sm">',
|
|
4057
|
+
' <Card className="shadow-none sm:shadow-sm sm:shadow-black/5">',
|
|
4058
|
+
" <CardHeader>",
|
|
4059
|
+
' <CardTitle className="text-center text-xl sm:text-left">Sign in to your app</CardTitle>',
|
|
4060
|
+
' <CardDescription className="text-center sm:text-left">',
|
|
4061
|
+
" Welcome back! Please sign in to continue",
|
|
4062
|
+
" </CardDescription>",
|
|
4063
|
+
" </CardHeader>",
|
|
4064
|
+
' <CardContent className="gap-6">',
|
|
4065
|
+
' <View className="gap-6">',
|
|
4066
|
+
' <View className="gap-1.5">',
|
|
4067
|
+
' <Label nativeID="email">Email</Label>',
|
|
4068
|
+
" <Input",
|
|
4069
|
+
' placeholder="m@example.com"',
|
|
4070
|
+
' keyboardType="email-address"',
|
|
4071
|
+
' autoComplete="email"',
|
|
4072
|
+
' autoCapitalize="none"',
|
|
4073
|
+
" onSubmitEditing={onEmailSubmitEditing}",
|
|
4074
|
+
' returnKeyType="next"',
|
|
4075
|
+
' submitBehavior="submit"',
|
|
4076
|
+
" />",
|
|
4077
|
+
" </View>",
|
|
4078
|
+
' <View className="gap-1.5">',
|
|
4079
|
+
' <View className="flex-row items-center">',
|
|
4080
|
+
' <Label nativeID="password">Password</Label>',
|
|
4081
|
+
" <Button",
|
|
4082
|
+
' variant="link"',
|
|
4083
|
+
' size="sm"',
|
|
4084
|
+
' className="ml-auto h-4 px-1 py-0 sm:h-4"',
|
|
4085
|
+
" onPress={() => {",
|
|
4086
|
+
" // TODO: Navigate to forgot password screen",
|
|
4087
|
+
" }}",
|
|
4088
|
+
" >",
|
|
4089
|
+
' <Text className="font-normal leading-4">Forgot your password?</Text>',
|
|
4090
|
+
" </Button>",
|
|
4091
|
+
" </View>",
|
|
4092
|
+
" <Input",
|
|
4093
|
+
" ref={passwordInputRef}",
|
|
4094
|
+
" secureTextEntry",
|
|
4095
|
+
' returnKeyType="send"',
|
|
4096
|
+
" onSubmitEditing={onSubmit}",
|
|
4097
|
+
" />",
|
|
4098
|
+
" </View>",
|
|
4099
|
+
' <Button className="w-full" onPress={onSubmit}>',
|
|
4100
|
+
" <Text>Continue</Text>",
|
|
4101
|
+
" </Button>",
|
|
4102
|
+
" </View>",
|
|
4103
|
+
' <Text className="text-center text-sm">',
|
|
4104
|
+
' Don't have an account?{" "}',
|
|
4105
|
+
" <Pressable",
|
|
4106
|
+
" onPress={() => {",
|
|
4107
|
+
" // TODO: Navigate to sign up screen",
|
|
4108
|
+
" }}",
|
|
4109
|
+
" >",
|
|
4110
|
+
' <Text className="text-sm underline underline-offset-4">Sign up</Text>',
|
|
4111
|
+
" </Pressable>",
|
|
4112
|
+
" </Text>",
|
|
4113
|
+
' <View className="flex-row items-center">',
|
|
4114
|
+
' <Separator className="flex-1" />',
|
|
4115
|
+
' <Text className="text-muted-foreground px-4 text-sm">or</Text>',
|
|
4116
|
+
' <Separator className="flex-1" />',
|
|
4117
|
+
" </View>",
|
|
4118
|
+
" </CardContent>",
|
|
4119
|
+
" </Card>",
|
|
4120
|
+
" </View>",
|
|
4121
|
+
" );",
|
|
4122
|
+
"}",
|
|
4123
|
+
""
|
|
4124
|
+
)
|
|
4125
|
+
}
|
|
4126
|
+
];
|
|
4127
|
+
}
|
|
4128
|
+
|
|
3329
4129
|
// src/utils/file.ts
|
|
3330
4130
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
3331
4131
|
var import_path = __toESM(require("path"));
|
|
@@ -3388,7 +4188,8 @@ function generateBasePackageJson(projectName) {
|
|
|
3388
4188
|
"react-native-safe-area-context": "~5.6.0",
|
|
3389
4189
|
"react-native-screens": "~4.16.0",
|
|
3390
4190
|
nativewind: "^4.1.0",
|
|
3391
|
-
tailwindcss: "^3.4.0"
|
|
4191
|
+
tailwindcss: "^3.4.0",
|
|
4192
|
+
"react-native-svg": "^15.8.0"
|
|
3392
4193
|
},
|
|
3393
4194
|
devDependencies: {
|
|
3394
4195
|
"@types/react": "~19.1.0",
|
|
@@ -3399,7 +4200,7 @@ function generateBasePackageJson(projectName) {
|
|
|
3399
4200
|
|
|
3400
4201
|
// src/index.ts
|
|
3401
4202
|
var import_execa = require("execa");
|
|
3402
|
-
var CLI_VERSION = "1.
|
|
4203
|
+
var CLI_VERSION = "1.3.1";
|
|
3403
4204
|
var CONFIG_FILE = ".expo-bbase.json";
|
|
3404
4205
|
async function run() {
|
|
3405
4206
|
const program = new import_commander.Command();
|
|
@@ -3440,10 +4241,34 @@ async function createProject(projectName) {
|
|
|
3440
4241
|
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
4242
|
);
|
|
3442
4243
|
console.log();
|
|
4244
|
+
const { uiTemplate } = await (0, import_prompts.default)({
|
|
4245
|
+
type: "select",
|
|
4246
|
+
name: "uiTemplate",
|
|
4247
|
+
message: "Choose a UI template",
|
|
4248
|
+
choices: [
|
|
4249
|
+
{
|
|
4250
|
+
title: `${import_chalk.default.bold("Login + Tabs")} \u2014 Login page, Home/List/Mine tabs with rnr components`,
|
|
4251
|
+
value: "login-tabs",
|
|
4252
|
+
description: "Pre-built login form, 3-tab layout with Button & AlertDialog demos"
|
|
4253
|
+
},
|
|
4254
|
+
{
|
|
4255
|
+
title: `${import_chalk.default.bold("Default")} \u2014 Blank tabs (Home + Explore)`,
|
|
4256
|
+
value: "default",
|
|
4257
|
+
description: "Minimal starter with basic tab navigation"
|
|
4258
|
+
}
|
|
4259
|
+
],
|
|
4260
|
+
initial: 0
|
|
4261
|
+
});
|
|
4262
|
+
if (uiTemplate === void 0) {
|
|
4263
|
+
console.log(import_chalk.default.yellow("\nCancelled."));
|
|
4264
|
+
process.exit(0);
|
|
4265
|
+
}
|
|
4266
|
+
const isLoginTabs = uiTemplate === "login-tabs";
|
|
3443
4267
|
const choices = modules.map((m) => ({
|
|
3444
4268
|
title: `${import_chalk.default.bold(m.name)} \u2014 ${import_chalk.default.gray(m.description)}`,
|
|
3445
4269
|
value: m.id,
|
|
3446
|
-
|
|
4270
|
+
// Auto-select ui-reusables when login-tabs template is chosen
|
|
4271
|
+
selected: isLoginTabs && m.id === "ui-reusables" ? true : m.defaultChecked
|
|
3447
4272
|
}));
|
|
3448
4273
|
const { selectedModules } = await (0, import_prompts.default)({
|
|
3449
4274
|
type: "multiselect",
|
|
@@ -3457,11 +4282,20 @@ async function createProject(projectName) {
|
|
|
3457
4282
|
console.log(import_chalk.default.yellow("\nCancelled."));
|
|
3458
4283
|
process.exit(0);
|
|
3459
4284
|
}
|
|
3460
|
-
|
|
4285
|
+
let finalModuleIds = selectedModules;
|
|
4286
|
+
if (isLoginTabs && !finalModuleIds.includes("ui-reusables")) {
|
|
4287
|
+
finalModuleIds = ["ui-reusables", ...finalModuleIds];
|
|
4288
|
+
}
|
|
4289
|
+
const selectedModuleDefs = getModulesByIds(finalModuleIds);
|
|
3461
4290
|
const targetDir = import_path2.default.resolve(process.cwd(), projectName);
|
|
3462
4291
|
console.log();
|
|
3463
4292
|
console.log(import_chalk.default.white(` \u{1F4E6} Project: ${import_chalk.default.bold(projectName)}`));
|
|
3464
4293
|
console.log(import_chalk.default.white(` \u{1F4C2} Path: ${import_chalk.default.gray(targetDir)}`));
|
|
4294
|
+
console.log(
|
|
4295
|
+
import_chalk.default.white(
|
|
4296
|
+
` \u{1F3A8} Template: ${import_chalk.default.green(isLoginTabs ? "Login + Tabs" : "Default")}`
|
|
4297
|
+
)
|
|
4298
|
+
);
|
|
3465
4299
|
console.log(
|
|
3466
4300
|
import_chalk.default.white(
|
|
3467
4301
|
` \u{1F9E9} Modules: ${import_chalk.default.green(selectedModuleDefs.map((m) => m.name).join(", ") || "none")}`
|
|
@@ -3484,6 +4318,15 @@ async function createProject(projectName) {
|
|
|
3484
4318
|
await writeFile(filePath, content);
|
|
3485
4319
|
}
|
|
3486
4320
|
}
|
|
4321
|
+
if (isLoginTabs) {
|
|
4322
|
+
spinner.text = "Writing UI template files...";
|
|
4323
|
+
const loginTabsTemplates = generateLoginTabsTemplates(projectName);
|
|
4324
|
+
for (const file of loginTabsTemplates) {
|
|
4325
|
+
const filePath = import_path2.default.join(targetDir, file.path);
|
|
4326
|
+
const content = replaceTemplateVars(file.content, { projectName });
|
|
4327
|
+
await writeFile(filePath, content);
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
3487
4330
|
spinner.text = "Generating package.json...";
|
|
3488
4331
|
const pkgJson = generateBasePackageJson(projectName);
|
|
3489
4332
|
const allDeps = {};
|
|
@@ -3507,8 +4350,9 @@ async function createProject(projectName) {
|
|
|
3507
4350
|
await updateLayoutFile(targetDir, selectedModuleDefs);
|
|
3508
4351
|
await writeProjectConfig(targetDir, {
|
|
3509
4352
|
projectName,
|
|
3510
|
-
selectedModules,
|
|
3511
|
-
cliVersion: CLI_VERSION
|
|
4353
|
+
selectedModules: finalModuleIds,
|
|
4354
|
+
cliVersion: CLI_VERSION,
|
|
4355
|
+
uiTemplate: isLoginTabs ? "login-tabs" : "default"
|
|
3512
4356
|
});
|
|
3513
4357
|
spinner.text = "Installing dependencies (yarn install)...";
|
|
3514
4358
|
try {
|
|
@@ -3527,6 +4371,9 @@ async function createProject(projectName) {
|
|
|
3527
4371
|
console.log(import_chalk.default.bold(" \u{1F389} Next steps:"));
|
|
3528
4372
|
console.log(import_chalk.default.white(` cd ${projectName}`));
|
|
3529
4373
|
console.log(import_chalk.default.white(" npx expo start"));
|
|
4374
|
+
if (isLoginTabs) {
|
|
4375
|
+
console.log(import_chalk.default.gray(" \u2192 App starts at login page, sign in to see tabs"));
|
|
4376
|
+
}
|
|
3530
4377
|
console.log();
|
|
3531
4378
|
if (selectedModuleDefs.length > 0) {
|
|
3532
4379
|
console.log(import_chalk.default.bold(" \u{1F4CB} Selected modules:"));
|