@nativetail/ui 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nativetail/ui",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {},
@@ -12,3 +12,4 @@ export * from "./chip";
12
12
  export * from "./blur";
13
13
  export * from "./progress";
14
14
  export * from "./counter";
15
+ export * from "./tabs";
@@ -1,20 +1,23 @@
1
- import { cn, Text, TextInput, TextInputProps, View } from "@nativetail/core";
1
+ import {
2
+ cn,
3
+ Text,
4
+ TextInput,
5
+ TextInputProps,
6
+ useTw,
7
+ View,
8
+ } from "@nativetail/core";
2
9
  import { useCallback, useState } from "react";
3
- import { create } from "zustand";
10
+ import ShowPassword from "./show-password";
4
11
 
5
- type FloatingInputProps = TextInputProps & {
12
+ type FloatingInputProps = Omit<TextInputProps, "placeholder"> & {
6
13
  containerClassName?: string;
7
14
  label: string;
8
15
  error?: string;
9
16
  helperText?: string;
17
+ isSecretToggleable?: boolean;
18
+ leftElement?: React.ReactNode;
19
+ rightElement?: React.ReactNode;
10
20
  };
11
- const useFocusSate = create<{
12
- isFocused: boolean;
13
- setIsFocused: (isFocused: boolean) => void;
14
- }>((set) => ({
15
- isFocused: false,
16
- setIsFocused: (isFocused: boolean) => set({ isFocused }),
17
- }));
18
21
  export function FloatingInput({
19
22
  value,
20
23
  onChangeText,
@@ -22,40 +25,76 @@ export function FloatingInput({
22
25
  label,
23
26
  error,
24
27
  className,
28
+ isSecretToggleable,
29
+ helperText,
30
+ leftElement,
31
+ rightElement,
25
32
  ...props
26
33
  }: FloatingInputProps) {
34
+ const [isFocused, setIsFocused] = useState(false);
27
35
  const onFocus = useCallback(() => {
28
- useFocusSate.getState().setIsFocused(true);
36
+ setIsFocused(true);
29
37
  }, []);
30
38
  const onBlur = useCallback(() => {
31
- useFocusSate.getState().setIsFocused(false);
39
+ setIsFocused(false);
32
40
  }, []);
41
+ const tw = useTw();
42
+ const [showPassword, setShowPassword] = useState(false);
33
43
  return (
34
- <View
35
- className={cn(
36
- "w-full rounded-xl h-16 overflow-hidden border border-muted/15",
37
- containerClassName
38
- )}
39
- >
40
- <Label label={label} value={value} />
41
-
42
- <TextInput
43
- onFocus={onFocus}
44
- onBlur={onBlur}
45
- value={value}
46
- onChangeText={onChangeText}
44
+ <>
45
+ <View
47
46
  className={cn(
48
- "flex-1 p-3 bg-card rounded-xl absolute w-full h-full -z-5 pt-5 text-foreground text-[16px]",
49
- className
47
+ "w-full rounded-xl h-16 overflow-hidden border border-muted/15",
48
+ containerClassName
50
49
  )}
51
- {...props}
52
- />
53
- </View>
50
+ >
51
+ <Label label={label} value={value} isFocused={isFocused} />
52
+
53
+ {leftElement && (
54
+ <View className="absolute left-2 bottom-2">{leftElement}</View>
55
+ )}
56
+ <TextInput
57
+ onFocus={onFocus}
58
+ onBlur={onBlur}
59
+ value={value}
60
+ onChangeText={onChangeText}
61
+ className={cn(
62
+ "flex-1 p-3 bg-card rounded-xl absolute w-full h-full -z-5 pt-5 text-foreground text-[16px]",
63
+ className,
64
+ isSecretToggleable || rightElement ? "pr-12" : "",
65
+ leftElement ? "pl-12" : ""
66
+ )}
67
+ secureTextEntry={!showPassword}
68
+ {...props}
69
+ placeholder=""
70
+ />
71
+ {rightElement && (
72
+ <View className="absolute right-2 bottom-2">{rightElement}</View>
73
+ )}
74
+ {isSecretToggleable && (
75
+ <ShowPassword
76
+ showPassword={showPassword}
77
+ setShowPassword={setShowPassword}
78
+ />
79
+ )}
80
+ </View>
81
+
82
+ {helperText && <Text className="text-muted text-sm">{helperText}</Text>}
83
+
84
+ {error && <Text className="text-danger text-sm">{error}</Text>}
85
+ </>
54
86
  );
55
87
  }
56
88
 
57
- const Label = ({ label, value }: { label?: string; value?: string }) => {
58
- const isFocused = useFocusSate((state) => state.isFocused);
89
+ const Label = ({
90
+ label,
91
+ value,
92
+ isFocused,
93
+ }: {
94
+ label?: string;
95
+ value?: string;
96
+ isFocused?: boolean;
97
+ }) => {
59
98
  const labelOnTop = isFocused || !!value;
60
99
 
61
100
  return (
@@ -1,2 +1,3 @@
1
1
  export * from "./floating-input";
2
2
  export * from "./input";
3
+ export * from "./pin-input";
@@ -6,12 +6,17 @@ import {
6
6
  useTw,
7
7
  View,
8
8
  } from "@nativetail/core";
9
+ import { useState } from "react";
10
+ import ShowPassword from "./show-password";
9
11
 
10
12
  type InputProps = TextInputProps & {
11
13
  containerClassName?: string;
12
14
  label: string;
13
15
  error?: string;
14
16
  helperText?: string;
17
+ isSecretToggleable?: boolean;
18
+ leftElement?: React.ReactNode;
19
+ rightElement?: React.ReactNode;
15
20
  };
16
21
  export function Input({
17
22
  value,
@@ -20,22 +25,44 @@ export function Input({
20
25
  label,
21
26
  error,
22
27
  className,
28
+ isSecretToggleable,
29
+ rightElement,
30
+ helperText,
31
+ leftElement,
23
32
  ...props
24
33
  }: InputProps) {
25
34
  const tw = useTw();
35
+ const [showPassword, setShowPassword] = useState(false);
26
36
  return (
27
37
  <View className={cn("w-full gap-1", containerClassName)}>
28
38
  <Text className={cn("text-muted/75 duration-75 ")}>{label}</Text>
39
+
29
40
  <TextInput
30
41
  value={value}
31
42
  onChangeText={onChangeText}
32
43
  className={cn(
33
44
  "p-3 bg-card rounded-lg w-full border border-muted/15 h-14 text-foreground -z-5 text-[16px]",
34
- className
45
+ className,
46
+ isSecretToggleable || rightElement ? "pr-12" : "",
47
+ leftElement ? "pl-12" : ""
35
48
  )}
36
49
  placeholderTextColor={tw.color("muted")}
50
+ secureTextEntry={!showPassword}
37
51
  {...props}
38
52
  />
53
+ {helperText && <Text className="text-muted text-sm">{helperText}</Text>}
54
+
55
+ {leftElement && (
56
+ <View className="absolute left-2 bottom-2">{leftElement}</View>
57
+ )}
58
+ {error && <Text className="text-danger text-sm">{error}</Text>}
59
+
60
+ {isSecretToggleable && (
61
+ <ShowPassword
62
+ showPassword={showPassword}
63
+ setShowPassword={setShowPassword}
64
+ />
65
+ )}
39
66
  </View>
40
67
  );
41
68
  }
@@ -0,0 +1,75 @@
1
+ import { cn, Pressable, TextInput, View } from "@nativetail/core";
2
+ import { useCallback, useRef, useState } from "react";
3
+ import { TextInput as NativeTextInput } from "react-native";
4
+
5
+ export type PinInputProps = {
6
+ value: string;
7
+ onChangeText: (text: string) => void;
8
+ length: number;
9
+ pinBoxClassName?: string;
10
+ pinBoxFocusedClassName?: string;
11
+ containerClassName?: string;
12
+ error?: string;
13
+ helperText?: string;
14
+ };
15
+ export function PinInput({
16
+ value,
17
+ onChangeText,
18
+ containerClassName,
19
+ error,
20
+ pinBoxClassName,
21
+ pinBoxFocusedClassName,
22
+ helperText,
23
+ length,
24
+ ...props
25
+ }: PinInputProps) {
26
+ const [isFocused, setIsFocused] = useState(false);
27
+ const activeIndex = value.length == length ? length - 1 : value.length;
28
+ const textInputRef = useRef<NativeTextInput>();
29
+ const onFocus = useCallback(() => {
30
+ setIsFocused(true);
31
+ }, [setIsFocused]);
32
+ const onBlur = useCallback(() => {
33
+ setIsFocused(false);
34
+ }, [setIsFocused]);
35
+ const _handleChange = useCallback(
36
+ (text: string) => {
37
+ if (text.length <= length) {
38
+ onChangeText(text);
39
+ }
40
+ },
41
+ [onChangeText]
42
+ );
43
+ const onPinBoxPress = useCallback(() => {
44
+ setIsFocused(true);
45
+ textInputRef.current?.focus();
46
+ }, [textInputRef, setIsFocused]);
47
+ return (
48
+ <View className={cn(" gap-2 flex-row w-full", containerClassName)}>
49
+ {Array.from({ length: length }).map((_, index) => (
50
+ <Pressable
51
+ key={`pininput-${index}`}
52
+ className={cn(
53
+ "p-2 bg-card rounded-lg items-center justify-center w-full font-medium aspect-sqaure flex-1 border border-muted/15 h-16 text-foreground text-[16px] text-center",
54
+ pinBoxClassName,
55
+ isFocused &&
56
+ activeIndex === index &&
57
+ "border-foreground" + " " + pinBoxFocusedClassName
58
+ )}
59
+ onPress={onPinBoxPress}
60
+ >
61
+ {value[index]}
62
+ </Pressable>
63
+ ))}
64
+ <TextInput
65
+ ref={textInputRef}
66
+ value={value}
67
+ onChangeText={_handleChange}
68
+ onFocus={onFocus}
69
+ onBlur={onBlur}
70
+ className="opacity-0 scale-0 absolute"
71
+ {...props}
72
+ />
73
+ </View>
74
+ );
75
+ }
@@ -0,0 +1,32 @@
1
+ import { Pressable, useTw } from "@nativetail/core";
2
+ import { Iconify } from "react-native-iconify";
3
+
4
+ export default function ShowPassword({
5
+ showPassword,
6
+ setShowPassword,
7
+ }: {
8
+ showPassword: boolean;
9
+ setShowPassword: (showPassword: boolean) => void;
10
+ }) {
11
+ const tw = useTw();
12
+ return (
13
+ <Pressable
14
+ onPress={() => setShowPassword(!showPassword)}
15
+ className="absolute right-2 bottom-2"
16
+ >
17
+ {showPassword ? (
18
+ <Iconify
19
+ icon="solar:eye-linear"
20
+ size={20}
21
+ color={tw.color("foreground")}
22
+ />
23
+ ) : (
24
+ <Iconify
25
+ icon="solar:eye-closed-linear"
26
+ size={20}
27
+ color={tw.color("foreground")}
28
+ />
29
+ )}
30
+ </Pressable>
31
+ );
32
+ }
@@ -0,0 +1,48 @@
1
+ import { cn, Pressable, View } from "@nativetail/core";
2
+
3
+ export type TabsProps = {
4
+ tabs: {
5
+ label: string;
6
+ value: string;
7
+ }[];
8
+ activeTab: string;
9
+ setActiveTab: (value: string) => void;
10
+ inactiveTabClassName?: string;
11
+ activeTabClassName?: string;
12
+ containerClassName?: string;
13
+ };
14
+ export function Tabs({
15
+ tabs,
16
+ activeTab,
17
+ setActiveTab,
18
+ containerClassName,
19
+ activeTabClassName,
20
+ inactiveTabClassName,
21
+ }: TabsProps) {
22
+ return (
23
+ <View
24
+ className={cn(
25
+ "rounded-xl self-start flex-row border overflow-hidden border-muted/15 bg-background",
26
+ containerClassName
27
+ )}
28
+ >
29
+ {tabs?.map((tab) => {
30
+ const isActive = tab.value === activeTab;
31
+
32
+ return (
33
+ <Pressable
34
+ className={cn(
35
+ "p-2 text-sm font-medium",
36
+ isActive ? "bg-primary text-white" : "bg-background text-muted",
37
+ isActive ? activeTabClassName : inactiveTabClassName
38
+ )}
39
+ key={tab.value}
40
+ onPress={() => setActiveTab(tab.value)}
41
+ >
42
+ {tab.label}
43
+ </Pressable>
44
+ );
45
+ })}
46
+ </View>
47
+ );
48
+ }