@beyondcorp/beyond-ui-native 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/package.json +11 -5
  2. package/src/components/accordion/accordion.tsx +245 -0
  3. package/src/components/accordion/index.ts +1 -0
  4. package/src/components/alert/alert.tsx +118 -0
  5. package/src/components/alert/index.ts +1 -0
  6. package/src/components/avatar/avatar.tsx +125 -0
  7. package/src/components/avatar/index.ts +1 -0
  8. package/src/components/badge/badge.tsx +89 -0
  9. package/src/components/badge/index.ts +1 -0
  10. package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
  11. package/src/components/bottom-sheet/index.ts +1 -0
  12. package/src/components/button/button.tsx +172 -0
  13. package/src/components/button/index.ts +1 -0
  14. package/src/components/card/card.tsx +121 -0
  15. package/src/components/card/index.ts +1 -0
  16. package/src/components/checkbox/checkbox.tsx +100 -0
  17. package/src/components/checkbox/index.ts +1 -0
  18. package/src/components/data-table/data-table.tsx +277 -0
  19. package/src/components/data-table/index.ts +1 -0
  20. package/src/components/dialog/dialog.tsx +278 -0
  21. package/src/components/dialog/index.ts +1 -0
  22. package/src/components/input/index.ts +1 -0
  23. package/src/components/input/input.tsx +130 -0
  24. package/src/components/radio/index.ts +1 -0
  25. package/src/components/radio/radio.tsx +117 -0
  26. package/src/components/select/index.ts +1 -0
  27. package/src/components/select/select.tsx +375 -0
  28. package/src/components/separator/index.ts +1 -0
  29. package/src/components/separator/separator.tsx +28 -0
  30. package/src/components/skeleton/index.ts +1 -0
  31. package/src/components/skeleton/skeleton.tsx +43 -0
  32. package/src/components/spinner/index.ts +1 -0
  33. package/src/components/spinner/spinner.tsx +46 -0
  34. package/src/components/stats-card/index.ts +1 -0
  35. package/src/components/stats-card/stats-card.tsx +92 -0
  36. package/src/components/switch/index.ts +1 -0
  37. package/src/components/switch/switch.tsx +80 -0
  38. package/src/components/tabs/index.ts +1 -0
  39. package/src/components/tabs/tabs.tsx +172 -0
  40. package/src/components/text/heading.tsx +50 -0
  41. package/src/components/text/index.ts +2 -0
  42. package/src/components/text/text.tsx +62 -0
  43. package/src/components/textarea/index.ts +1 -0
  44. package/src/components/textarea/textarea.tsx +87 -0
  45. package/src/components/toast/index.ts +2 -0
  46. package/src/components/toast/toast.tsx +136 -0
  47. package/src/components/toast/types.ts +32 -0
  48. package/src/index.ts +42 -0
  49. package/src/primitives/box.tsx +25 -0
  50. package/src/primitives/index.ts +2 -0
  51. package/src/primitives/pressable.tsx +49 -0
  52. package/src/providers/BeyondUIProvider.tsx +100 -0
  53. package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
  54. package/src/templates/auth/SignInTemplate.tsx +213 -0
  55. package/src/templates/auth/SignUpTemplate.tsx +233 -0
  56. package/src/templates/auth/index.ts +3 -0
  57. package/src/templates/dashboard/DashboardTemplate.tsx +205 -0
  58. package/src/templates/dashboard/index.ts +1 -0
  59. package/src/templates/index.ts +3 -0
  60. package/src/templates/settings/SettingsTemplate.tsx +226 -0
  61. package/src/templates/settings/index.ts +1 -0
  62. package/src/theme/preset.ts +45 -0
  63. package/src/theme/tokens.ts +144 -0
  64. package/src/types/nativewind-env.d.ts +26 -0
  65. package/src/utilities/accessibility.ts +43 -0
  66. package/src/utilities/cn.ts +9 -0
  67. package/src/utilities/index.ts +2 -0
@@ -0,0 +1,136 @@
1
+ import * as React from "react";
2
+ import { View, Text, Pressable, Animated } from "react-native";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "../../utilities/cn";
5
+ import { ToastItem, ToastVariant } from "./types";
6
+
7
+ const toastVariants = cva(
8
+ "w-full max-w-sm rounded-xl p-4 shadow-lg border flex-row items-center justify-between mb-2",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-white border-neutral-200 dark:bg-neutral-900 dark:border-neutral-800",
13
+ success: "bg-success-50 border-success-200 dark:bg-success-950/80 dark:border-success-800",
14
+ danger: "bg-danger-50 border-danger-200 dark:bg-danger-950/80 dark:border-danger-800",
15
+ warning: "bg-warning-50 border-warning-200 dark:bg-warning-950/80 dark:border-warning-800",
16
+ info: "bg-primary-50 border-primary-200 dark:bg-primary-950/80 dark:border-primary-800",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "default",
21
+ },
22
+ }
23
+ );
24
+
25
+ const toastTitleVariants = cva("text-sm font-semibold", {
26
+ variants: {
27
+ variant: {
28
+ default: "text-neutral-900 dark:text-white",
29
+ success: "text-success-700 dark:text-success-300",
30
+ danger: "text-danger-700 dark:text-danger-300",
31
+ warning: "text-warning-800 dark:text-warning-300",
32
+ info: "text-primary-700 dark:text-primary-300",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: "default",
37
+ },
38
+ });
39
+
40
+ interface ToastBannerProps {
41
+ toast: ToastItem;
42
+ onDismiss: (id: string) => void;
43
+ }
44
+
45
+ export function ToastBanner({ toast, onDismiss }: ToastBannerProps) {
46
+ const opacity = React.useRef(new Animated.Value(0)).current;
47
+ const translateY = React.useRef(new Animated.Value(-16)).current;
48
+
49
+ React.useEffect(() => {
50
+ Animated.parallel([
51
+ Animated.timing(opacity, {
52
+ toValue: 1,
53
+ duration: 200,
54
+ useNativeDriver: true,
55
+ }),
56
+ Animated.timing(translateY, {
57
+ toValue: 0,
58
+ duration: 200,
59
+ useNativeDriver: true,
60
+ }),
61
+ ]).start();
62
+
63
+ const timer = setTimeout(() => {
64
+ handleDismiss();
65
+ }, toast.duration || 4000);
66
+
67
+ return () => clearTimeout(timer);
68
+ }, []);
69
+
70
+ const handleDismiss = () => {
71
+ Animated.parallel([
72
+ Animated.timing(opacity, {
73
+ toValue: 0,
74
+ duration: 200,
75
+ useNativeDriver: true,
76
+ }),
77
+ Animated.timing(translateY, {
78
+ toValue: -16,
79
+ duration: 200,
80
+ useNativeDriver: true,
81
+ }),
82
+ ]).start(() => {
83
+ onDismiss(toast.id);
84
+ });
85
+ };
86
+
87
+ const variant: ToastVariant = toast.variant || "default";
88
+
89
+ return (
90
+ <Animated.View
91
+ style={{
92
+ opacity,
93
+ transform: [{ translateY }],
94
+ }}
95
+ className="w-full items-center px-4"
96
+ >
97
+ <Pressable
98
+ onPress={handleDismiss}
99
+ className={cn(toastVariants({ variant }))}
100
+ >
101
+ <View className="flex-1 pr-2">
102
+ <Text className={cn(toastTitleVariants({ variant }))}>
103
+ {toast.title}
104
+ </Text>
105
+ {toast.description ? (
106
+ <Text className="text-xs text-neutral-600 dark:text-neutral-400 mt-0.5">
107
+ {toast.description}
108
+ </Text>
109
+ ) : null}
110
+ </View>
111
+ <Text className="text-xs text-neutral-400">✕</Text>
112
+ </Pressable>
113
+ </Animated.View>
114
+ );
115
+ }
116
+
117
+ export function ToastContainer({
118
+ toasts,
119
+ onDismiss,
120
+ }: {
121
+ toasts: ToastItem[];
122
+ onDismiss: (id: string) => void;
123
+ }) {
124
+ if (toasts.length === 0) return null;
125
+
126
+ return (
127
+ <View
128
+ pointerEvents="box-none"
129
+ className="absolute top-12 left-0 right-0 z-50 items-center"
130
+ >
131
+ {toasts.map((toast) => (
132
+ <ToastBanner key={toast.id} toast={toast} onDismiss={onDismiss} />
133
+ ))}
134
+ </View>
135
+ );
136
+ }
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+
3
+ export type ToastVariant = "default" | "success" | "danger" | "warning" | "info";
4
+
5
+ export interface ToastItem {
6
+ id: string;
7
+ title: string;
8
+ description?: string;
9
+ variant?: ToastVariant;
10
+ duration?: number;
11
+ }
12
+
13
+ export interface ToastContextValue {
14
+ toasts: ToastItem[];
15
+ show: (toast: Omit<ToastItem, "id">) => string;
16
+ dismiss: (id: string) => void;
17
+ success: (title: string, description?: string) => string;
18
+ danger: (title: string, description?: string) => string;
19
+ error: (title: string, description?: string) => string;
20
+ warning: (title: string, description?: string) => string;
21
+ info: (title: string, description?: string) => string;
22
+ }
23
+
24
+ export const ToastContext = React.createContext<ToastContextValue | null>(null);
25
+
26
+ export function useToast(): ToastContextValue {
27
+ const context = React.useContext(ToastContext);
28
+ if (!context) {
29
+ throw new Error("useToast must be used within a BeyondUIProvider");
30
+ }
31
+ return context;
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ /// <reference path="./types/nativewind-env.d.ts" />
2
+
3
+ // Primitives
4
+ export * from "./primitives";
5
+
6
+ // Components
7
+ export * from "./components/button";
8
+ export * from "./components/text";
9
+ export * from "./components/input";
10
+ export * from "./components/textarea";
11
+ export * from "./components/card";
12
+ export * from "./components/badge";
13
+ export * from "./components/avatar";
14
+ export * from "./components/switch";
15
+ export * from "./components/checkbox";
16
+ export * from "./components/radio";
17
+ export * from "./components/spinner";
18
+ export * from "./components/skeleton";
19
+ export * from "./components/alert";
20
+ export * from "./components/separator";
21
+ export * from "./components/dialog";
22
+ export * from "./components/tabs";
23
+ export * from "./components/toast";
24
+ export * from "./components/stats-card";
25
+ export * from "./components/accordion";
26
+ export * from "./components/bottom-sheet";
27
+ export * from "./components/select";
28
+ export * from "./components/data-table";
29
+
30
+ // Screen Templates
31
+ export * from "./templates";
32
+
33
+ // Providers & Context
34
+ export * from "./providers/BeyondUIProvider";
35
+
36
+ // Theme & Tokens
37
+ export * from "./theme/tokens";
38
+ export { default as beyondUIPreset } from "./theme/preset";
39
+
40
+ // Utilities
41
+ export * from "./utilities/cn";
42
+ export * from "./utilities/accessibility";
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { cn } from "../utilities/cn";
4
+
5
+ export interface BoxProps extends ViewProps {
6
+ className?: string;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ export const Box = React.forwardRef<View, BoxProps>(
11
+ ({ className, style, children, ...props }, ref) => {
12
+ return (
13
+ <View
14
+ ref={ref}
15
+ className={cn(className)}
16
+ style={style}
17
+ {...props}
18
+ >
19
+ {children}
20
+ </View>
21
+ );
22
+ }
23
+ );
24
+
25
+ Box.displayName = "Box";
@@ -0,0 +1,2 @@
1
+ export * from "./box";
2
+ export * from "./pressable";
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ import {
3
+ Pressable as RNPressable,
4
+ type PressableProps as RNPressableProps,
5
+ type View,
6
+ type GestureResponderEvent,
7
+ } from "react-native";
8
+ import { cn } from "../utilities/cn";
9
+
10
+ export interface PressableProps extends Omit<RNPressableProps, "children"> {
11
+ className?: string;
12
+ children?: React.ReactNode | ((state: { pressed: boolean }) => React.ReactNode);
13
+ feedback?: "opacity" | "scale" | "none";
14
+ }
15
+
16
+ export const Pressable = React.forwardRef<View, PressableProps>(
17
+ (
18
+ {
19
+ className,
20
+ style,
21
+ disabled,
22
+ feedback = "opacity",
23
+ children,
24
+ ...props
25
+ },
26
+ ref
27
+ ) => {
28
+ return (
29
+ <RNPressable
30
+ ref={ref}
31
+ disabled={disabled}
32
+ accessibilityRole="button"
33
+ accessibilityState={{ disabled: !!disabled }}
34
+ className={cn(
35
+ feedback === "opacity" && "active:opacity-70",
36
+ feedback === "scale" && "active:scale-[0.98]",
37
+ disabled && "opacity-50",
38
+ className
39
+ )}
40
+ style={style}
41
+ {...props}
42
+ >
43
+ {typeof children === "function" ? children : children}
44
+ </RNPressable>
45
+ );
46
+ }
47
+ );
48
+
49
+ Pressable.displayName = "Pressable";
@@ -0,0 +1,100 @@
1
+ import * as React from "react";
2
+ import { View, useColorScheme as useRNColorScheme } from "react-native";
3
+ import { PortalHost } from "@rn-primitives/portal";
4
+ import { ToastContext, ToastItem, ToastContextValue } from "../components/toast/types";
5
+ import { ToastContainer } from "../components/toast/toast";
6
+
7
+ export type ColorScheme = "light" | "dark" | "system";
8
+
9
+ export interface BeyondUIContextValue {
10
+ colorScheme: "light" | "dark";
11
+ setColorScheme: (scheme: ColorScheme) => void;
12
+ toggleColorScheme: () => void;
13
+ }
14
+
15
+ export const BeyondUIContext = React.createContext<BeyondUIContextValue>({
16
+ colorScheme: "light",
17
+ setColorScheme: () => {},
18
+ toggleColorScheme: () => {},
19
+ });
20
+
21
+ export function useBeyondUI() {
22
+ return React.useContext(BeyondUIContext);
23
+ }
24
+
25
+ export interface BeyondUIProviderProps {
26
+ children: React.ReactNode;
27
+ defaultColorScheme?: ColorScheme;
28
+ }
29
+
30
+ export function BeyondUIProvider({
31
+ children,
32
+ defaultColorScheme = "system",
33
+ }: BeyondUIProviderProps) {
34
+ const systemScheme = useRNColorScheme() ?? "light";
35
+ const [selectedScheme, setSelectedScheme] = React.useState<ColorScheme>(defaultColorScheme);
36
+ const [toasts, setToasts] = React.useState<ToastItem[]>([]);
37
+
38
+ const activeScheme: "light" | "dark" =
39
+ selectedScheme === "system" ? systemScheme : selectedScheme;
40
+
41
+ const toggleColorScheme = React.useCallback(() => {
42
+ setSelectedScheme((prev) => {
43
+ const current = prev === "system" ? systemScheme : prev;
44
+ return current === "dark" ? "light" : "dark";
45
+ });
46
+ }, [systemScheme]);
47
+
48
+ const dismissToast = React.useCallback((id: string) => {
49
+ setToasts((prev) => prev.filter((t) => t.id !== id));
50
+ }, []);
51
+
52
+ const showToast = React.useCallback((toast: Omit<ToastItem, "id">) => {
53
+ const id = Math.random().toString(36).substring(2, 9);
54
+ setToasts((prev) => [...prev, { ...toast, id }]);
55
+ return id;
56
+ }, []);
57
+
58
+ const toastContextValue: ToastContextValue = React.useMemo(
59
+ () => ({
60
+ toasts,
61
+ show: showToast,
62
+ dismiss: dismissToast,
63
+ success: (title, description) =>
64
+ showToast({ title, description, variant: "success" }),
65
+ danger: (title, description) =>
66
+ showToast({ title, description, variant: "danger" }),
67
+ error: (title, description) =>
68
+ showToast({ title, description, variant: "danger" }),
69
+ warning: (title, description) =>
70
+ showToast({ title, description, variant: "warning" }),
71
+ info: (title, description) =>
72
+ showToast({ title, description, variant: "info" }),
73
+ }),
74
+ [toasts, showToast, dismissToast]
75
+ );
76
+
77
+ const contextValue: BeyondUIContextValue = React.useMemo(
78
+ () => ({
79
+ colorScheme: activeScheme,
80
+ setColorScheme: setSelectedScheme,
81
+ toggleColorScheme,
82
+ }),
83
+ [activeScheme, toggleColorScheme]
84
+ );
85
+
86
+ return (
87
+ <BeyondUIContext.Provider value={contextValue}>
88
+ <ToastContext.Provider value={toastContextValue}>
89
+ <View
90
+ style={{ flex: 1 }}
91
+ className={activeScheme === "dark" ? "dark flex-1" : "flex-1"}
92
+ >
93
+ {children}
94
+ <PortalHost />
95
+ <ToastContainer toasts={toasts} onDismiss={dismissToast} />
96
+ </View>
97
+ </ToastContext.Provider>
98
+ </BeyondUIContext.Provider>
99
+ );
100
+ }
@@ -0,0 +1,132 @@
1
+ import * as React from "react";
2
+ import {
3
+ View,
4
+ ScrollView,
5
+ KeyboardAvoidingView,
6
+ Platform,
7
+ Pressable,
8
+ } from "react-native";
9
+ import { cn } from "../../utilities/cn";
10
+ import { Text } from "../../components/text/text";
11
+ import { Heading } from "../../components/text/heading";
12
+ import { Input } from "../../components/input/input";
13
+ import { Button } from "../../components/button/button";
14
+ import { Alert, AlertDescription } from "../../components/alert/alert";
15
+
16
+ export interface ForgotPasswordTemplateProps {
17
+ title?: string;
18
+ subtitle?: string;
19
+ onSubmit?: (data: { email: string }) => void;
20
+ onBackToSignIn?: () => void;
21
+ isLoading?: boolean;
22
+ isSuccess?: boolean;
23
+ error?: string | null;
24
+ className?: string;
25
+ }
26
+
27
+ export const ForgotPasswordTemplate: React.FC<ForgotPasswordTemplateProps> = ({
28
+ title = "Reset Password",
29
+ subtitle = "Enter your email address and we'll send you recovery instructions",
30
+ onSubmit,
31
+ onBackToSignIn,
32
+ isLoading = false,
33
+ isSuccess = false,
34
+ error,
35
+ className,
36
+ }) => {
37
+ const [email, setEmail] = React.useState("");
38
+ const [emailError, setEmailError] = React.useState<string | undefined>();
39
+
40
+ const handleSubmit = () => {
41
+ if (!email || !email.includes("@")) {
42
+ setEmailError("Please enter a valid email address");
43
+ return;
44
+ }
45
+ setEmailError(undefined);
46
+ onSubmit?.({ email });
47
+ };
48
+
49
+ return (
50
+ <KeyboardAvoidingView
51
+ behavior={Platform.OS === "ios" ? "padding" : "height"}
52
+ style={{ flex: 1 }}
53
+ className={cn("flex-1 bg-background", className)}
54
+ >
55
+ <ScrollView
56
+ style={{ flex: 1 }}
57
+ contentContainerStyle={{ flexGrow: 1, justifyContent: "center" }}
58
+ className="px-6 py-8"
59
+ keyboardShouldPersistTaps="handled"
60
+ showsVerticalScrollIndicator={false}
61
+ >
62
+ <View className="w-full max-w-md mx-auto gap-6">
63
+ <View className="items-center gap-2">
64
+ <Heading level={2} className="text-3xl font-extrabold text-foreground text-center">
65
+ {title}
66
+ </Heading>
67
+ <Text className="text-muted text-center text-base">
68
+ {subtitle}
69
+ </Text>
70
+ </View>
71
+
72
+ {error && (
73
+ <Alert variant="danger">
74
+ <AlertDescription>{error}</AlertDescription>
75
+ </Alert>
76
+ )}
77
+
78
+ {isSuccess ? (
79
+ <View className="gap-4">
80
+ <Alert variant="success">
81
+ <AlertDescription>
82
+ Password reset link sent! Check your email inbox.
83
+ </AlertDescription>
84
+ </Alert>
85
+
86
+ {onBackToSignIn && (
87
+ <Button variant="primary" size="lg" onPress={onBackToSignIn}>
88
+ Back to Sign In
89
+ </Button>
90
+ )}
91
+ </View>
92
+ ) : (
93
+ <View className="gap-4">
94
+ <Input
95
+ label="Email Address"
96
+ placeholder="you@company.com"
97
+ keyboardType="email-address"
98
+ autoCapitalize="none"
99
+ value={email}
100
+ onChangeText={(text) => {
101
+ setEmail(text);
102
+ if (emailError) setEmailError(undefined);
103
+ }}
104
+ error={emailError}
105
+ />
106
+
107
+ <Button
108
+ variant="primary"
109
+ size="lg"
110
+ loading={isLoading}
111
+ onPress={handleSubmit}
112
+ className="mt-2"
113
+ >
114
+ Send Reset Link
115
+ </Button>
116
+
117
+ {onBackToSignIn && (
118
+ <View className="flex-row justify-center items-center gap-1.5 pt-2">
119
+ <Pressable onPress={onBackToSignIn}>
120
+ <Text className="text-sm font-semibold text-primary">
121
+ Back to Sign In
122
+ </Text>
123
+ </Pressable>
124
+ </View>
125
+ )}
126
+ </View>
127
+ )}
128
+ </View>
129
+ </ScrollView>
130
+ </KeyboardAvoidingView>
131
+ );
132
+ };