@nativetail/ui 0.0.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 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
+ });