@beyondcorp/beyond-ui-native 0.1.1 → 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.
- package/dist/{chunk-2B6HTSGM.mjs → chunk-RNHEGFYR.mjs} +16 -2
- package/dist/chunk-RNHEGFYR.mjs.map +1 -0
- package/dist/index.d.mts +2 -145
- package/dist/index.d.ts +2 -145
- package/dist/index.js +37 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +23 -8
- package/dist/index.mjs.map +1 -1
- package/dist/preset-BmUHLoAa.d.mts +274 -0
- package/dist/preset-BmUHLoAa.d.ts +274 -0
- package/dist/preset.d.mts +1 -117
- package/dist/preset.d.ts +1 -117
- package/dist/preset.js +19 -3
- package/dist/preset.js.map +1 -1
- package/dist/preset.mjs +5 -3
- package/package.json +11 -5
- package/src/components/accordion/accordion.tsx +245 -0
- package/src/components/accordion/index.ts +1 -0
- package/src/components/alert/alert.tsx +118 -0
- package/src/components/alert/index.ts +1 -0
- package/src/components/avatar/avatar.tsx +125 -0
- package/src/components/avatar/index.ts +1 -0
- package/src/components/badge/badge.tsx +89 -0
- package/src/components/badge/index.ts +1 -0
- package/src/components/bottom-sheet/bottom-sheet.tsx +336 -0
- package/src/components/bottom-sheet/index.ts +1 -0
- package/src/components/button/button.tsx +172 -0
- package/src/components/button/index.ts +1 -0
- package/src/components/card/card.tsx +121 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/checkbox/checkbox.tsx +100 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/data-table/data-table.tsx +277 -0
- package/src/components/data-table/index.ts +1 -0
- package/src/components/dialog/dialog.tsx +278 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.tsx +130 -0
- package/src/components/radio/index.ts +1 -0
- package/src/components/radio/radio.tsx +117 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.tsx +375 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/separator.tsx +28 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.tsx +43 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/spinner/spinner.tsx +46 -0
- package/src/components/stats-card/index.ts +1 -0
- package/src/components/stats-card/stats-card.tsx +92 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +80 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.tsx +172 -0
- package/src/components/text/heading.tsx +50 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/text/text.tsx +62 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.tsx +87 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/toast.tsx +136 -0
- package/src/components/toast/types.ts +32 -0
- package/src/index.ts +42 -0
- package/src/primitives/box.tsx +25 -0
- package/src/primitives/index.ts +2 -0
- package/src/primitives/pressable.tsx +49 -0
- package/src/providers/BeyondUIProvider.tsx +100 -0
- package/src/templates/auth/ForgotPasswordTemplate.tsx +132 -0
- package/src/templates/auth/SignInTemplate.tsx +213 -0
- package/src/templates/auth/SignUpTemplate.tsx +233 -0
- package/src/templates/auth/index.ts +3 -0
- package/src/templates/dashboard/DashboardTemplate.tsx +205 -0
- package/src/templates/dashboard/index.ts +1 -0
- package/src/templates/index.ts +3 -0
- package/src/templates/settings/SettingsTemplate.tsx +226 -0
- package/src/templates/settings/index.ts +1 -0
- package/src/theme/preset.ts +45 -0
- package/src/theme/tokens.ts +144 -0
- package/src/types/nativewind-env.d.ts +26 -0
- package/src/utilities/accessibility.ts +43 -0
- package/src/utilities/cn.ts +9 -0
- package/src/utilities/index.ts +2 -0
- package/dist/chunk-2B6HTSGM.mjs.map +0 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
type ViewProps,
|
|
7
|
+
type PressableProps,
|
|
8
|
+
type GestureResponderEvent,
|
|
9
|
+
} from "react-native";
|
|
10
|
+
import { cn } from "../../utilities/cn";
|
|
11
|
+
|
|
12
|
+
export interface TabsContextValue {
|
|
13
|
+
value: string;
|
|
14
|
+
onValueChange: (value: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TabsContext = React.createContext<TabsContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
export function useTabsContext(): TabsContextValue {
|
|
20
|
+
const context = React.useContext(TabsContext);
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error("Tabs compound components must be rendered inside a Tabs component");
|
|
23
|
+
}
|
|
24
|
+
return context;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TabsProps extends ViewProps {
|
|
28
|
+
value?: string;
|
|
29
|
+
defaultValue?: string;
|
|
30
|
+
onValueChange?: (value: string) => void;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const Tabs = React.forwardRef<View, TabsProps>(function Tabs(
|
|
35
|
+
{ value: controlledValue, defaultValue = "", onValueChange, className, children, ...props },
|
|
36
|
+
ref
|
|
37
|
+
) {
|
|
38
|
+
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
39
|
+
const isControlled = controlledValue !== undefined;
|
|
40
|
+
const currentValue = isControlled ? controlledValue : internalValue;
|
|
41
|
+
|
|
42
|
+
const handleValueChange = React.useCallback(
|
|
43
|
+
(val: string) => {
|
|
44
|
+
if (!isControlled) {
|
|
45
|
+
setInternalValue(val);
|
|
46
|
+
}
|
|
47
|
+
onValueChange?.(val);
|
|
48
|
+
},
|
|
49
|
+
[isControlled, onValueChange]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<TabsContext.Provider value={{ value: currentValue, onValueChange: handleValueChange }}>
|
|
54
|
+
<View ref={ref} className={className} {...props}>
|
|
55
|
+
{children}
|
|
56
|
+
</View>
|
|
57
|
+
</TabsContext.Provider>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
Tabs.displayName = "Tabs";
|
|
61
|
+
|
|
62
|
+
export interface TabsListProps extends ViewProps {
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const TabsList = React.forwardRef<View, TabsListProps>(function TabsList(
|
|
67
|
+
{ className, ...props },
|
|
68
|
+
ref
|
|
69
|
+
) {
|
|
70
|
+
return (
|
|
71
|
+
<View
|
|
72
|
+
ref={ref}
|
|
73
|
+
role="tablist"
|
|
74
|
+
className={cn(
|
|
75
|
+
"flex-row items-center justify-center rounded-xl bg-neutral-100 dark:bg-neutral-800 p-1",
|
|
76
|
+
className
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
TabsList.displayName = "TabsList";
|
|
83
|
+
|
|
84
|
+
export interface TabsTriggerProps extends Omit<PressableProps, "onPress"> {
|
|
85
|
+
value: string;
|
|
86
|
+
className?: string;
|
|
87
|
+
textClassName?: string;
|
|
88
|
+
children?: React.ReactNode;
|
|
89
|
+
onPress?: (e: GestureResponderEvent) => void;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const TabsTrigger = React.forwardRef<
|
|
93
|
+
React.ElementRef<typeof Pressable>,
|
|
94
|
+
TabsTriggerProps
|
|
95
|
+
>(function TabsTrigger({ value, className, textClassName, children, onPress, disabled, ...props }, ref) {
|
|
96
|
+
const { value: selectedValue, onValueChange } = useTabsContext();
|
|
97
|
+
const isSelected = selectedValue === value;
|
|
98
|
+
|
|
99
|
+
const handlePress = (e: GestureResponderEvent) => {
|
|
100
|
+
if (disabled) return;
|
|
101
|
+
onValueChange(value);
|
|
102
|
+
onPress?.(e);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Pressable
|
|
107
|
+
ref={ref}
|
|
108
|
+
role="tab"
|
|
109
|
+
accessibilityRole="tab"
|
|
110
|
+
accessibilityState={{ selected: isSelected, disabled: !!disabled }}
|
|
111
|
+
disabled={disabled}
|
|
112
|
+
onPress={handlePress}
|
|
113
|
+
className={cn(
|
|
114
|
+
"flex-1 items-center justify-center rounded-lg py-2 px-3 transition-all",
|
|
115
|
+
isSelected
|
|
116
|
+
? "bg-white dark:bg-neutral-900 shadow-sm"
|
|
117
|
+
: "bg-transparent opacity-70",
|
|
118
|
+
disabled && "opacity-40",
|
|
119
|
+
className
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
>
|
|
123
|
+
{typeof children === "string" ? (
|
|
124
|
+
<Text
|
|
125
|
+
className={cn(
|
|
126
|
+
"text-sm font-semibold",
|
|
127
|
+
isSelected
|
|
128
|
+
? "text-neutral-900 dark:text-neutral-50"
|
|
129
|
+
: "text-neutral-600 dark:text-neutral-400",
|
|
130
|
+
textClassName
|
|
131
|
+
)}
|
|
132
|
+
>
|
|
133
|
+
{children}
|
|
134
|
+
</Text>
|
|
135
|
+
) : (
|
|
136
|
+
children
|
|
137
|
+
)}
|
|
138
|
+
</Pressable>
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
TabsTrigger.displayName = "TabsTrigger";
|
|
142
|
+
|
|
143
|
+
export interface TabsContentProps extends ViewProps {
|
|
144
|
+
value: string;
|
|
145
|
+
className?: string;
|
|
146
|
+
forceMount?: boolean;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const TabsContent = React.forwardRef<View, TabsContentProps>(function TabsContent(
|
|
150
|
+
{ value, className, forceMount, children, ...props },
|
|
151
|
+
ref
|
|
152
|
+
) {
|
|
153
|
+
const { value: selectedValue } = useTabsContext();
|
|
154
|
+
const isSelected = selectedValue === value;
|
|
155
|
+
|
|
156
|
+
if (!isSelected && !forceMount) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<View
|
|
162
|
+
ref={ref}
|
|
163
|
+
role="tabpanel"
|
|
164
|
+
accessibilityElementsHidden={!isSelected}
|
|
165
|
+
className={cn("mt-3", className)}
|
|
166
|
+
{...props}
|
|
167
|
+
>
|
|
168
|
+
{children}
|
|
169
|
+
</View>
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
TabsContent.displayName = "TabsContent";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Text as NativeText, type TextProps as NativeTextProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const headingVariants = cva("font-bold text-neutral-900 dark:text-neutral-50 tracking-tight", {
|
|
7
|
+
variants: {
|
|
8
|
+
level: {
|
|
9
|
+
h1: "text-3xl font-extrabold",
|
|
10
|
+
h2: "text-2xl font-bold",
|
|
11
|
+
h3: "text-xl font-bold",
|
|
12
|
+
h4: "text-lg font-semibold",
|
|
13
|
+
h5: "text-base font-semibold",
|
|
14
|
+
h6: "text-sm font-semibold uppercase tracking-wider",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultVariants: {
|
|
18
|
+
level: "h2",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | 1 | 2 | 3 | 4 | 5 | 6;
|
|
23
|
+
|
|
24
|
+
export interface HeadingProps
|
|
25
|
+
extends NativeTextProps,
|
|
26
|
+
Omit<VariantProps<typeof headingVariants>, "level"> {
|
|
27
|
+
level?: HeadingLevel;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Heading = React.forwardRef<NativeText, HeadingProps>(function Heading(
|
|
32
|
+
{ className, level = "h2", ...props },
|
|
33
|
+
ref
|
|
34
|
+
) {
|
|
35
|
+
const normalizedLevel =
|
|
36
|
+
typeof level === "number"
|
|
37
|
+
? (`h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6")
|
|
38
|
+
: level;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<NativeText
|
|
42
|
+
ref={ref}
|
|
43
|
+
accessibilityRole="header"
|
|
44
|
+
className={cn(headingVariants({ level: normalizedLevel }), className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
Heading.displayName = "Heading";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Text as NativeText, type TextProps as NativeTextProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "../../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export const textVariants = cva("text-neutral-900 dark:text-neutral-100", {
|
|
7
|
+
variants: {
|
|
8
|
+
variant: {
|
|
9
|
+
body: "text-base font-normal",
|
|
10
|
+
caption: "text-xs font-normal text-neutral-500 dark:text-neutral-400",
|
|
11
|
+
label: "text-sm font-medium text-neutral-700 dark:text-neutral-300",
|
|
12
|
+
muted: "text-sm font-normal text-neutral-500 dark:text-neutral-400",
|
|
13
|
+
lead: "text-lg font-normal text-neutral-600 dark:text-neutral-300",
|
|
14
|
+
},
|
|
15
|
+
color: {
|
|
16
|
+
default: "text-neutral-900 dark:text-neutral-100",
|
|
17
|
+
muted: "text-neutral-500 dark:text-neutral-400",
|
|
18
|
+
primary: "text-primary-600 dark:text-primary-400",
|
|
19
|
+
secondary: "text-secondary-600 dark:text-secondary-400",
|
|
20
|
+
danger: "text-danger-600 dark:text-danger-400",
|
|
21
|
+
success: "text-success-600 dark:text-success-400",
|
|
22
|
+
warning: "text-warning-600 dark:text-warning-400",
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
xs: "text-xs",
|
|
26
|
+
sm: "text-sm",
|
|
27
|
+
base: "text-base",
|
|
28
|
+
lg: "text-lg",
|
|
29
|
+
xl: "text-xl",
|
|
30
|
+
},
|
|
31
|
+
weight: {
|
|
32
|
+
normal: "font-normal",
|
|
33
|
+
medium: "font-medium",
|
|
34
|
+
semibold: "font-semibold",
|
|
35
|
+
bold: "font-bold",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
variant: "body",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export interface TextProps
|
|
44
|
+
extends NativeTextProps,
|
|
45
|
+
VariantProps<typeof textVariants> {
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Text = React.forwardRef<NativeText, TextProps>(function Text(
|
|
50
|
+
{ className, variant, color, size, weight, ...props },
|
|
51
|
+
ref
|
|
52
|
+
) {
|
|
53
|
+
return (
|
|
54
|
+
<NativeText
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn(textVariants({ variant, color, size, weight }), className)}
|
|
57
|
+
{...props}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
Text.displayName = "Text";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./textarea";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
TextInput,
|
|
5
|
+
View,
|
|
6
|
+
type TextInputProps,
|
|
7
|
+
} from "react-native";
|
|
8
|
+
import { cn } from "../../utilities/cn";
|
|
9
|
+
|
|
10
|
+
export interface TextareaProps
|
|
11
|
+
extends Omit<TextInputProps, "multiline"> {
|
|
12
|
+
label?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
helperText?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
rows?: number;
|
|
17
|
+
containerClassName?: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
labelClassName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Textarea = React.forwardRef<TextInput, TextareaProps>(function Textarea(
|
|
23
|
+
{
|
|
24
|
+
label,
|
|
25
|
+
error,
|
|
26
|
+
helperText,
|
|
27
|
+
disabled = false,
|
|
28
|
+
rows = 4,
|
|
29
|
+
containerClassName,
|
|
30
|
+
className,
|
|
31
|
+
labelClassName,
|
|
32
|
+
placeholderTextColor,
|
|
33
|
+
accessibilityLabel,
|
|
34
|
+
...props
|
|
35
|
+
},
|
|
36
|
+
ref
|
|
37
|
+
) {
|
|
38
|
+
const minHeight = Math.max(80, rows * 22);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View className={cn("w-full gap-1.5", containerClassName)}>
|
|
42
|
+
{label ? (
|
|
43
|
+
<Text
|
|
44
|
+
className={cn(
|
|
45
|
+
"text-sm font-medium text-neutral-800 dark:text-neutral-200",
|
|
46
|
+
labelClassName
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{label}
|
|
50
|
+
</Text>
|
|
51
|
+
) : null}
|
|
52
|
+
|
|
53
|
+
<TextInput
|
|
54
|
+
ref={ref}
|
|
55
|
+
multiline
|
|
56
|
+
textAlignVertical="top"
|
|
57
|
+
editable={!disabled}
|
|
58
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
59
|
+
accessibilityHint={error}
|
|
60
|
+
aria-invalid={!!error}
|
|
61
|
+
placeholderTextColor={placeholderTextColor ?? "#9ca3af"}
|
|
62
|
+
style={{ minHeight }}
|
|
63
|
+
className={cn(
|
|
64
|
+
"w-full rounded-xl border bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 p-3.5 text-base",
|
|
65
|
+
error
|
|
66
|
+
? "border-danger-500 dark:border-danger-500"
|
|
67
|
+
: "border-neutral-300 dark:border-neutral-700 focus:border-primary-500",
|
|
68
|
+
disabled && "opacity-50 bg-neutral-100 dark:bg-neutral-800",
|
|
69
|
+
className
|
|
70
|
+
)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
{error ? (
|
|
75
|
+
<Text accessibilityRole="alert" className="text-xs font-medium text-danger-600 dark:text-danger-400">
|
|
76
|
+
{error}
|
|
77
|
+
</Text>
|
|
78
|
+
) : helperText ? (
|
|
79
|
+
<Text className="text-xs text-neutral-500 dark:text-neutral-400">
|
|
80
|
+
{helperText}
|
|
81
|
+
</Text>
|
|
82
|
+
) : null}
|
|
83
|
+
</View>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
Textarea.displayName = "Textarea";
|
|
@@ -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,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";
|