@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 +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/input/floating-input.tsx +71 -32
- package/src/components/input/index.ts +1 -0
- package/src/components/input/input.tsx +28 -1
- package/src/components/input/pin-input.tsx +75 -0
- package/src/components/input/show-password.tsx +32 -0
- package/src/components/tabs/index.tsx +48 -0
package/package.json
CHANGED
package/src/components/index.ts
CHANGED
@@ -1,20 +1,23 @@
|
|
1
|
-
import {
|
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
|
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
|
-
|
36
|
+
setIsFocused(true);
|
29
37
|
}, []);
|
30
38
|
const onBlur = useCallback(() => {
|
31
|
-
|
39
|
+
setIsFocused(false);
|
32
40
|
}, []);
|
41
|
+
const tw = useTw();
|
42
|
+
const [showPassword, setShowPassword] = useState(false);
|
33
43
|
return (
|
34
|
-
|
35
|
-
|
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
|
-
"
|
49
|
-
|
47
|
+
"w-full rounded-xl h-16 overflow-hidden border border-muted/15",
|
48
|
+
containerClassName
|
50
49
|
)}
|
51
|
-
|
52
|
-
|
53
|
-
|
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 = ({
|
58
|
-
|
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 (
|
@@ -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
|
+
}
|