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