@nativetail/ui 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # NativeTail
2
+
3
+ This is the nativetail package
4
+
5
+ This packages uses the parser from twrnc package
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [`module:metro-react-native-babel-preset`],
3
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@nativetail/ui",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "src/index.ts",
6
+ "scripts": {},
7
+ "author": "Imtiaj Bin Aoual <imtiajbinaoual@gmail.com>",
8
+ "license": "MIT",
9
+ "keywords": [
10
+ "react",
11
+ "react-native",
12
+ "ui",
13
+ "nativetail ui",
14
+ "tailwind ui",
15
+ "tailwindcss-react-native",
16
+ "twrnc",
17
+ "tailwind-react-native",
18
+ "tailwind-rn",
19
+ "@nativetail/ui"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/Imtiajrex/nativetail.git"
24
+ },
25
+ "peerDependencies": {
26
+ "react-native": ">=0.63.0"
27
+ },
28
+ "dependencies": {
29
+ "@gorhom/bottom-sheet": "^5.0.0-alpha.10",
30
+ "@shopify/flash-list": "^1.7.0",
31
+ "expo-blur": "^13.0.2",
32
+ "expo-linear-gradient": "~13.0.2",
33
+ "moti": "^0.29.0",
34
+ "@nativetail/core": "*",
35
+ "react": "18.2.0",
36
+ "react-hook-form": "^7.51.0",
37
+ "react-native": "0.74.3",
38
+ "react-native-actions-sheet": "^0.9.6",
39
+ "react-native-gesture-handler": "^2.17.1",
40
+ "react-native-reanimated": "~3.10.1",
41
+ "react-native-safe-area-context": "4.10.1",
42
+ "tailwind-merge": "^2.3.0",
43
+ "zustand": "^4.5.2"
44
+ },
45
+ "devDependencies": {
46
+ "metro-react-native-babel-preset": "^0.77.0",
47
+ "tailwindcss": "^3.4.4"
48
+ },
49
+ "private": false
50
+ }
@@ -0,0 +1,98 @@
1
+ import { cn, useTw, View } from "@nativetail/core";
2
+ import { forwardRef } from "react";
3
+ import { Blur } from "../blur";
4
+ import { Button } from "../button";
5
+ import { Dialog, DialogMethods } from "../dialog";
6
+
7
+ type OptionType = {
8
+ text: string;
9
+ onPress: () => void;
10
+ className?: string;
11
+ };
12
+ export type ActionSheetProps = {
13
+ containerClassName?: string;
14
+ contentClassName?: string;
15
+ onCancel: () => void;
16
+ useBlur?: boolean;
17
+ children?: React.ReactNode;
18
+ options?: OptionType[];
19
+ };
20
+ export type ActionSheetRef = DialogMethods;
21
+ export const ActionSheet = forwardRef<DialogMethods, ActionSheetProps>(
22
+ function ActionSheet(
23
+ {
24
+ containerClassName,
25
+ contentClassName,
26
+ useBlur = true,
27
+ onCancel,
28
+ children,
29
+ options,
30
+ },
31
+ ref
32
+ ) {
33
+ const tw = useTw();
34
+
35
+ return (
36
+ <Dialog
37
+ containerClassName={cn("justify-end", containerClassName)}
38
+ contentClassName={cn(
39
+ "bg-transparent border-transparent gap-2 pb-4",
40
+ contentClassName
41
+ )}
42
+ useBlur={false}
43
+ ref={ref}
44
+ >
45
+ <View className="w-full rounded-xl bg-card/95">
46
+ {useBlur && (
47
+ <Blur
48
+ style={tw`absolute top-0 flex-1 left-0 right-0 bottom-0 rounded-xl bg-card`}
49
+ />
50
+ )}
51
+ {options?.map((action, index) => (
52
+ <ActionSheetItem
53
+ key={index}
54
+ text={action.text}
55
+ onPress={action.onPress}
56
+ className={action.className}
57
+ last={index == options.length - 1}
58
+ />
59
+ ))}
60
+ {children}
61
+ </View>
62
+ <Button
63
+ variant="link"
64
+ className="w-full active:opacity-75 opacity-100 text-blue-500 bg-card rounded-xl"
65
+ onPress={onCancel}
66
+ >
67
+ Cancel
68
+ </Button>
69
+ </Dialog>
70
+ );
71
+ }
72
+ );
73
+
74
+ const ActionSheetItem = ({
75
+ className,
76
+ text,
77
+ onPress,
78
+ last = false,
79
+ }: {
80
+ className?: string;
81
+ text: string;
82
+ onPress: () => void;
83
+ last?: boolean;
84
+ }) => {
85
+ return (
86
+ <Button
87
+ variant="link"
88
+ className={cn(
89
+ "w-full items-center opacity-100 active:opacity-50 text-[16px] h-14 text-blue-500 rounded-none border-b ",
90
+ last ? "border-transparent" : "border-muted/15",
91
+ className
92
+ )}
93
+ onPress={onPress}
94
+ >
95
+ {text}
96
+ </Button>
97
+ );
98
+ };
@@ -0,0 +1,58 @@
1
+ import { Text, View } from "@nativetail/core";
2
+ import { forwardRef } from "react";
3
+ import { Button } from "../button";
4
+ import { Dialog, DialogMethods } from "../dialog";
5
+
6
+ export type AlertDialogProps = {
7
+ containerClassName?: string;
8
+ onConfirm: () => void;
9
+ onCancel: () => void;
10
+ title?: string;
11
+ description?: string;
12
+ useBlur?: boolean;
13
+ };
14
+ export type AlertDialogRef = DialogMethods;
15
+ export const AlertDialog = forwardRef<DialogMethods, AlertDialogProps>(
16
+ function AlertDialog(
17
+ { containerClassName, onConfirm, title, description, useBlur, onCancel },
18
+ ref
19
+ ) {
20
+ return (
21
+ <Dialog
22
+ containerClassName={containerClassName}
23
+ useBlur={useBlur}
24
+ ref={ref}
25
+ >
26
+ <View className="p-4 py-6">
27
+ {title && (
28
+ <Text className="text-foreground text-lg text-center font-semibold">
29
+ {title}
30
+ </Text>
31
+ )}
32
+ {description && (
33
+ <Text className="text-muted/75 text-center w-full">
34
+ {description}
35
+ </Text>
36
+ )}
37
+ </View>
38
+
39
+ <View className="flex-row items-center border-t border-muted/15">
40
+ <Button
41
+ variant="link"
42
+ className="flex-1 active:opacity-75 text-foreground"
43
+ onPress={onCancel}
44
+ >
45
+ Cancel
46
+ </Button>
47
+ <Button
48
+ variant="link"
49
+ className="flex-1 border active:opacity-75 border-transparent rounded-none border-l-muted/15"
50
+ onPress={onConfirm}
51
+ >
52
+ Confirm
53
+ </Button>
54
+ </View>
55
+ </Dialog>
56
+ );
57
+ }
58
+ );
@@ -0,0 +1,15 @@
1
+ import { BlurView, BlurViewProps } from "expo-blur";
2
+ import { useColorScheme } from "@nativetail/core";
3
+
4
+ export type BlurProps = BlurViewProps;
5
+ export const Blur = (props: BlurProps) => {
6
+ const [colorScheme] = useColorScheme();
7
+ return (
8
+ <BlurView
9
+ intensity={50}
10
+ tint={colorScheme === "dark" ? "dark" : "light"}
11
+ experimentalBlurMethod="dimezisBlurView"
12
+ {...props}
13
+ />
14
+ );
15
+ };
@@ -0,0 +1,46 @@
1
+ import { BlurView } from "expo-blur";
2
+ import { cn, useTw, View } from "@nativetail/core";
3
+ import { forwardRef } from "react";
4
+ import ActionSheet, {
5
+ ActionSheetProps,
6
+ ActionSheetRef,
7
+ } from "react-native-actions-sheet";
8
+ import { Blur } from "../blur";
9
+ export type BottomSheetProps = ActionSheetProps & {
10
+ containerClassName?: string;
11
+ contentClassName?: string;
12
+ indicatorClassName?: string;
13
+ useBlur?: boolean;
14
+ };
15
+ export const BottomSheet = forwardRef<ActionSheetRef, BottomSheetProps>(
16
+ function BottomSheet(
17
+ {
18
+ containerClassName,
19
+ contentClassName,
20
+ children,
21
+ useBlur,
22
+ indicatorClassName,
23
+ ...props
24
+ },
25
+ ref
26
+ ) {
27
+ const tw = useTw();
28
+ return (
29
+ <ActionSheet
30
+ ref={ref}
31
+ containerStyle={tw.style("bg-background/75", containerClassName)}
32
+ gestureEnabled
33
+ indicatorStyle={tw.style("bg-muted/15", indicatorClassName)}
34
+ {...props}
35
+ >
36
+ {useBlur && (
37
+ <Blur
38
+ style={tw`absolute top-0 left-0 right-0 bottom-0 rounded-xl flex-1 bg-card`}
39
+ />
40
+ )}
41
+ <View className={cn("p-4 ", contentClassName)}>{children}</View>
42
+ </ActionSheet>
43
+ );
44
+ }
45
+ );
46
+ export { ActionSheetRef as BottomSheetRef };
@@ -0,0 +1,109 @@
1
+ import React, { useMemo } from "react";
2
+
3
+ import { MotiPressableProps } from "moti/interactions";
4
+ import {
5
+ ActivityIndicator,
6
+ cva,
7
+ mergeClasses,
8
+ Pressable,
9
+ separateClasses,
10
+ useTw,
11
+ VariantProps,
12
+ } from "@nativetail/core";
13
+
14
+ const buttonVariants = cva(
15
+ "flex-row gap-2 items-center justify-center rounded text-sm font-medium hover:opacity-90 active:opacity-80 opacity-100 select-none",
16
+ {
17
+ variants: {
18
+ variant: {
19
+ default: "bg-primary text-foreground ",
20
+ destructive: "bg-red-500 text-foreground ",
21
+ outline: "border border-primary text-foreground bg-black/0 ",
22
+ secondary: "bg-secondary text-foreground ",
23
+ ghost: "",
24
+ link: "text-primary ",
25
+ card: " bg-card ",
26
+ },
27
+ size: {
28
+ default: "h-12 px-4 py-2",
29
+ sm: "h-8 px-3 text-xs",
30
+ lg: "h-10 px-8",
31
+ icon: "h-9 w-9",
32
+ },
33
+ disabled: {
34
+ true: "opacity-80",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ size: "default",
40
+ },
41
+ }
42
+ );
43
+ type ButtonProps = MotiPressableProps &
44
+ VariantProps<typeof buttonVariants> & {
45
+ text?: string;
46
+ disabled?: boolean;
47
+ isLoading?: boolean;
48
+ textClass?: string;
49
+ leftElement?: React.ReactNode;
50
+ rightElement?: React.ReactNode;
51
+ className?: string;
52
+ children?: React.ReactNode;
53
+ loadingIndicatorClassName?: string;
54
+ };
55
+
56
+ const Button = ({
57
+ text,
58
+ children,
59
+ isLoading,
60
+ className,
61
+ disabled,
62
+ variant,
63
+ leftElement,
64
+ rightElement,
65
+ size,
66
+ loadingIndicatorClassName,
67
+
68
+ ...props
69
+ }: ButtonProps) => {
70
+ const tw = useTw();
71
+
72
+ const loading = isLoading;
73
+
74
+ const isDisabled = disabled || loading;
75
+ const variantClass = useMemo(
76
+ () =>
77
+ buttonVariants({
78
+ variant,
79
+ size,
80
+ className,
81
+ disabled: isDisabled,
82
+ }),
83
+ [variant, size, className, isDisabled, tw]
84
+ );
85
+
86
+ const { textClasses } = separateClasses(variantClass);
87
+
88
+ return (
89
+ <Pressable
90
+ disabled={disabled || loading}
91
+ className={variantClass}
92
+ {...props}
93
+ >
94
+ {leftElement}
95
+ {loading && (
96
+ <ActivityIndicator
97
+ className={mergeClasses(
98
+ "mr-2 h-5 w-5 text-foreground ",
99
+ textClasses,
100
+ loadingIndicatorClassName
101
+ )}
102
+ />
103
+ )}
104
+ {children}
105
+ {rightElement}
106
+ </Pressable>
107
+ );
108
+ };
109
+ export { Button };
@@ -0,0 +1,74 @@
1
+ import React, { useMemo } from "react";
2
+
3
+ import {
4
+ cn,
5
+ cva,
6
+ separateClasses,
7
+ Text,
8
+ VariantProps,
9
+ View,
10
+ } from "@nativetail/core";
11
+
12
+ const chipVariants = cva(
13
+ "items-center justify-center self-start text-foreground rounded-full",
14
+ {
15
+ variants: {
16
+ variant: {
17
+ default: "bg-primary ",
18
+ destructive: "bg-danger ",
19
+ success: "bg-success",
20
+ outline: "border border-primary bg-background ",
21
+ secondary: "bg-secondary ",
22
+ card: " bg-card ",
23
+ },
24
+ size: {
25
+ default: "px-3 py-2 text-[16px]",
26
+ sm: " px-2 py-1 text-sm",
27
+ lg: "px-4 py-3 text-lg",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ );
36
+ type ChipProps = VariantProps<typeof chipVariants> & {
37
+ text?: string;
38
+ textClass?: string;
39
+ leftElement?: React.ReactNode;
40
+ rightElement?: React.ReactNode;
41
+ className?: string;
42
+ };
43
+
44
+ const Chip = ({
45
+ text,
46
+ variant,
47
+ size = "sm",
48
+ className,
49
+ leftElement,
50
+ rightElement,
51
+
52
+ ...props
53
+ }: ChipProps) => {
54
+ const variantClass = useMemo(
55
+ () =>
56
+ chipVariants({
57
+ variant,
58
+ size,
59
+ className: className,
60
+ }),
61
+ [variant, size, className]
62
+ );
63
+
64
+ const { textClasses } = separateClasses(variantClass);
65
+
66
+ return (
67
+ <View className={variantClass} {...props}>
68
+ {leftElement}
69
+ <Text className={cn(textClasses, "p-0 pt-0")}>{text}</Text>
70
+ {rightElement}
71
+ </View>
72
+ );
73
+ };
74
+ export { Chip };
@@ -0,0 +1,101 @@
1
+ import { TextInput, View, cn, useTw } from "@nativetail/core";
2
+ import { useCallback, useRef } from "react";
3
+ import { Iconify } from "react-native-iconify";
4
+ import { Button } from "../button";
5
+ export type CounterProps = {
6
+ value: number;
7
+ max?: number;
8
+ min?: number;
9
+ setValue: React.Dispatch<React.SetStateAction<number>>;
10
+ containerClassName?: string;
11
+ };
12
+ export function Counter({
13
+ value,
14
+ setValue,
15
+ min,
16
+ max,
17
+ containerClassName,
18
+ }: CounterProps) {
19
+ const tw = useTw();
20
+ const increment = useCallback(() => {
21
+ setValue((prev) => {
22
+ if (max && prev >= max) return;
23
+ return prev + 1;
24
+ });
25
+ }, [setValue, max]);
26
+ const decrement = useCallback(() => {
27
+ setValue((prev) => {
28
+ if (min && prev <= min) return;
29
+ return prev - 1;
30
+ });
31
+ }, [setValue, min]);
32
+ const onChangeText = useCallback(
33
+ (text) => {
34
+ const number = parseInt(text);
35
+
36
+ if (isNaN(number)) return;
37
+ setValue(number);
38
+ },
39
+ [min, max, setValue]
40
+ );
41
+
42
+ return (
43
+ <View
44
+ className={cn(
45
+ "flex-row items-center gap-2 select-none w-full",
46
+ containerClassName
47
+ )}
48
+ >
49
+ <CounterButton disabled={!!(min && value <= min)} onPress={decrement}>
50
+ <Iconify
51
+ icon="ic:round-minus"
52
+ size={15}
53
+ color={tw.color("foreground")}
54
+ />
55
+ </CounterButton>
56
+ <View className="flex-1 h-full">
57
+ <TextInput
58
+ className="flex-1 items-center text-center justify-center bg-card rounded-xl h-full text-foreground font-medium select-none"
59
+ onChangeText={onChangeText}
60
+ value={value.toString()}
61
+ />
62
+ </View>
63
+ <CounterButton disabled={!!(max && value >= max)} onPress={increment}>
64
+ <Iconify
65
+ icon="ic:round-plus"
66
+ size={15}
67
+ color={tw.color("foreground")}
68
+ />
69
+ </CounterButton>
70
+ </View>
71
+ );
72
+ }
73
+ const CounterButton = ({
74
+ onPress,
75
+ children,
76
+ disabled,
77
+ }: {
78
+ onPress: () => void;
79
+ children: React.ReactNode;
80
+ disabled?: boolean;
81
+ }) => {
82
+ const functionInterval = useRef<NodeJS.Timeout>();
83
+ const onPressIn = useCallback(() => {
84
+ onPress();
85
+ functionInterval.current = setInterval(onPress, 100);
86
+ }, [onPress]);
87
+ const onPressOut = useCallback(() => {
88
+ if (functionInterval.current) clearInterval(functionInterval.current);
89
+ }, []);
90
+
91
+ return (
92
+ <Button
93
+ disabled={disabled}
94
+ onPressIn={onPressIn}
95
+ onPressOut={onPressOut}
96
+ size="sm"
97
+ >
98
+ {children}
99
+ </Button>
100
+ );
101
+ };
@@ -0,0 +1,111 @@
1
+ import { BlurView } from "expo-blur";
2
+ import { AnimatePresence } from "moti";
3
+ import { cn, mergeClasses, Pressable, useTw, View } from "@nativetail/core";
4
+ import {
5
+ forwardRef,
6
+ useCallback,
7
+ useEffect,
8
+ useImperativeHandle,
9
+ useState,
10
+ } from "react";
11
+ import { Modal } from "react-native";
12
+ import { Blur } from "../blur";
13
+
14
+ export type DialogProps = {
15
+ containerClassName?: string;
16
+ contentClassName?: string;
17
+ useBlur?: boolean;
18
+ children: React.ReactNode;
19
+ onRequestClose?: () => void;
20
+ };
21
+ let timeout: NodeJS.Timeout;
22
+ export type DialogMethods = {
23
+ show: () => void;
24
+ hide: () => void;
25
+ };
26
+ export const Dialog = forwardRef<DialogMethods, DialogProps>(function Dialog(
27
+ {
28
+ containerClassName,
29
+ contentClassName,
30
+ useBlur = true,
31
+ children,
32
+ onRequestClose,
33
+ }: DialogProps,
34
+ ref
35
+ ) {
36
+ const [isOpen, setOpen] = useState(false);
37
+
38
+ const getRef = useCallback(() => {
39
+ return {
40
+ show: () => {
41
+ setOpen(true);
42
+ },
43
+ hide: () => {
44
+ setOpen(false);
45
+ },
46
+ };
47
+ }, [setOpen]);
48
+ useImperativeHandle(ref, getRef, []);
49
+ const tw = useTw();
50
+ const [modalOpen, setModalOpen] = useState(isOpen);
51
+ useEffect(() => {
52
+ if (isOpen) {
53
+ setModalOpen(true);
54
+ } else {
55
+ timeout = setTimeout(
56
+ () => {
57
+ setModalOpen(false);
58
+ },
59
+ isOpen ? 0 : 200
60
+ );
61
+ }
62
+ return () => {
63
+ if (timeout) clearTimeout(timeout);
64
+ };
65
+ }, [isOpen]);
66
+
67
+ const onDidAnimate = useCallback(() => {
68
+ if (!isOpen) {
69
+ setModalOpen(false);
70
+ }
71
+ }, [isOpen]);
72
+
73
+ return (
74
+ <Modal
75
+ visible={modalOpen}
76
+ transparent
77
+ onRequestClose={onRequestClose}
78
+ animationType="fade"
79
+ statusBarTranslucent
80
+ >
81
+ <Pressable
82
+ className={cn(
83
+ "flex-1 items-center justify-center p-4 w-full h-full bg-black/35",
84
+ containerClassName
85
+ )}
86
+ onPress={onRequestClose}
87
+ disabled={!onRequestClose}
88
+ >
89
+ <AnimatePresence exitBeforeEnter>
90
+ {isOpen && (
91
+ <View
92
+ className={mergeClasses(
93
+ "absolute overflow-hidden in:opacity-0 opacity-100 out:opacity-0 in:scale-0 scale-100 out:scale-0 z-10 bg-card/95 rounded-xl max-w-sm w-full border border-muted/10",
94
+ contentClassName
95
+ )}
96
+ onDidAnimate={onDidAnimate}
97
+ >
98
+ {useBlur && (
99
+ <Blur
100
+ style={tw`absolute top-0 left-0 right-0 bottom-0 rounded-xl flex-1 bg-card`}
101
+ />
102
+ )}
103
+
104
+ {children}
105
+ </View>
106
+ )}
107
+ </AnimatePresence>
108
+ </Pressable>
109
+ </Modal>
110
+ );
111
+ });