@nativetail/ui 0.0.5 → 0.0.7

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.5",
3
+ "version": "0.0.7",
4
4
  "description": "",
5
5
  "main": "src/index.ts",
6
6
  "scripts": {},
@@ -40,7 +40,8 @@
40
40
  "react-native-reanimated": "~3.10.1",
41
41
  "react-native-safe-area-context": "4.10.1",
42
42
  "tailwind-merge": "^2.3.0",
43
- "zustand": "^4.5.2"
43
+ "zustand": "^4.5.2",
44
+ "@hookform/resolvers": "^3.6.0"
44
45
  },
45
46
  "devDependencies": {
46
47
  "metro-react-native-babel-preset": "^0.77.0",
@@ -23,7 +23,7 @@ export const ActionSheet = forwardRef<DialogMethods, ActionSheetProps>(
23
23
  {
24
24
  containerClassName,
25
25
  contentClassName,
26
- useBlur = true,
26
+ useBlur = false,
27
27
  onCancel,
28
28
  children,
29
29
  options,
@@ -39,7 +39,7 @@ export const AlertDialog = forwardRef<DialogMethods, AlertDialogProps>(
39
39
  <View className="flex-row items-center border-t border-muted/15">
40
40
  <Button
41
41
  variant="link"
42
- className="flex-1 active:opacity-75 text-foreground"
42
+ className="flex-1 active:opacity-75 text-foreground rounded-none"
43
43
  onPress={onCancel}
44
44
  >
45
45
  Cancel
@@ -1,4 +1,3 @@
1
- import { BlurView } from "expo-blur";
2
1
  import { cn, useTw, View } from "@nativetail/core";
3
2
  import { forwardRef } from "react";
4
3
  import ActionSheet, {
@@ -18,7 +17,7 @@ export const BottomSheet = forwardRef<ActionSheetRef, BottomSheetProps>(
18
17
  containerClassName,
19
18
  contentClassName,
20
19
  children,
21
- useBlur,
20
+ useBlur = false,
22
21
  indicatorClassName,
23
22
  ...props
24
23
  },
@@ -28,7 +27,7 @@ export const BottomSheet = forwardRef<ActionSheetRef, BottomSheetProps>(
28
27
  return (
29
28
  <ActionSheet
30
29
  ref={ref}
31
- containerStyle={tw.style("bg-background/75", containerClassName)}
30
+ containerStyle={tw.style("bg-background/95", containerClassName)}
32
31
  gestureEnabled
33
32
  indicatorStyle={tw.style("bg-muted/15", indicatorClassName)}
34
33
  {...props}
@@ -1,8 +1,9 @@
1
1
  import React, { useMemo } from "react";
2
2
 
3
- import { MotiPressableProps } from "moti/interactions";
4
3
  import {
5
4
  ActivityIndicator,
5
+ cn,
6
+ ConfigVariants,
6
7
  cva,
7
8
  mergeClasses,
8
9
  Pressable,
@@ -10,6 +11,8 @@ import {
10
11
  useTw,
11
12
  VariantProps,
12
13
  } from "@nativetail/core";
14
+ import { MotiPressableProps } from "moti/interactions";
15
+ import { useComponentTheme } from "../../utils/component-theme";
13
16
 
14
17
  const buttonVariants = cva(
15
18
  "flex-row gap-2 items-center justify-center rounded text-sm font-medium hover:opacity-90 active:opacity-80 opacity-100 select-none",
@@ -18,7 +21,7 @@ const buttonVariants = cva(
18
21
  variant: {
19
22
  default: "bg-primary text-foreground ",
20
23
  destructive: "bg-red-500 text-foreground ",
21
- outline: "border border-primary text-foreground bg-black/0 ",
24
+ outline: "border border-muted/15 text-foreground bg-black/0 ",
22
25
  secondary: "bg-secondary text-foreground ",
23
26
  ghost: "",
24
27
  link: "text-primary ",
@@ -40,6 +43,30 @@ const buttonVariants = cva(
40
43
  },
41
44
  }
42
45
  );
46
+ type VariantPropType = (
47
+ props?: ConfigVariants<{
48
+ variant: {
49
+ default: string;
50
+ destructive: string;
51
+ outline: string;
52
+ secondary: string;
53
+ ghost: string;
54
+ link: string;
55
+ card: string;
56
+ };
57
+ size: {
58
+ default: string;
59
+ sm: string;
60
+ lg: string;
61
+ icon: string;
62
+ };
63
+ disabled: {
64
+ true: string;
65
+ };
66
+ }> & {
67
+ className?: string;
68
+ }
69
+ ) => string;
43
70
  export type ButtonProps = MotiPressableProps &
44
71
  VariantProps<typeof buttonVariants> & {
45
72
  text?: string;
@@ -51,36 +78,51 @@ export type ButtonProps = MotiPressableProps &
51
78
  className?: string;
52
79
  children?: React.ReactNode;
53
80
  loadingIndicatorClassName?: string;
81
+ variants?: VariantPropType;
54
82
  };
55
83
 
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) => {
84
+ const Button = (passedProps: ButtonProps) => {
85
+ const componentTheme = useComponentTheme();
86
+ const buttonProps = componentTheme?.Button || {};
87
+ const {
88
+ text,
89
+ children,
90
+ isLoading,
91
+ disabled,
92
+ variant,
93
+ leftElement,
94
+ rightElement,
95
+ size,
96
+ className: propClassName,
97
+ loadingIndicatorClassName,
98
+ variants,
99
+ ...props
100
+ } = {
101
+ ...buttonProps,
102
+ ...passedProps,
103
+ };
70
104
  const tw = useTw();
105
+ const className = cn(buttonProps.className, passedProps.className);
71
106
 
72
107
  const loading = isLoading;
73
108
 
74
109
  const isDisabled = disabled || loading;
75
110
  const variantClass = useMemo(
76
111
  () =>
77
- buttonVariants({
78
- variant,
79
- size,
80
- className,
81
- disabled: isDisabled,
82
- }),
83
- [variant, size, className, isDisabled, tw]
112
+ variants
113
+ ? variants({
114
+ variant,
115
+ size,
116
+ className,
117
+ disabled: isDisabled,
118
+ })
119
+ : buttonVariants({
120
+ variant,
121
+ size,
122
+ className,
123
+ disabled: isDisabled,
124
+ }),
125
+ [variant, size, className, isDisabled, tw, variants]
84
126
  );
85
127
 
86
128
  const { textClasses } = separateClasses(variantClass);
@@ -33,7 +33,7 @@ const chipVariants = cva(
33
33
  },
34
34
  }
35
35
  );
36
- type ChipProps = VariantProps<typeof chipVariants> & {
36
+ export type ChipProps = VariantProps<typeof chipVariants> & {
37
37
  text?: string;
38
38
  textClass?: string;
39
39
  leftElement?: React.ReactNode;
@@ -19,13 +19,13 @@ export function Counter({
19
19
  const tw = useTw();
20
20
  const increment = useCallback(() => {
21
21
  setValue((prev) => {
22
- if (max && prev >= max) return;
22
+ if (max && prev >= max) return prev;
23
23
  return prev + 1;
24
24
  });
25
25
  }, [setValue, max]);
26
26
  const decrement = useCallback(() => {
27
27
  setValue((prev) => {
28
- if (min && prev <= min) return;
28
+ if (min && prev <= min) return prev;
29
29
  return prev - 1;
30
30
  });
31
31
  }, [setValue, min]);
@@ -34,6 +34,7 @@ export function Counter({
34
34
  const number = parseInt(text);
35
35
 
36
36
  if (isNaN(number)) return;
37
+ if (!number) return;
37
38
  setValue(number);
38
39
  },
39
40
  [min, max, setValue]
@@ -57,7 +58,7 @@ export function Counter({
57
58
  <TextInput
58
59
  className="flex-1 items-center text-center justify-center bg-card rounded-xl h-full text-foreground font-medium select-none"
59
60
  onChangeText={onChangeText}
60
- value={value.toString()}
61
+ value={value?.toString?.()}
61
62
  />
62
63
  </View>
63
64
  <CounterButton disabled={!!(max && value >= max)} onPress={increment}>
@@ -17,6 +17,8 @@ export type DialogProps = {
17
17
  useBlur?: boolean;
18
18
  children: React.ReactNode;
19
19
  onRequestClose?: () => void;
20
+ closable?: boolean;
21
+ backdropClassName?: string;
20
22
  };
21
23
  let timeout: NodeJS.Timeout;
22
24
  export type DialogMethods = {
@@ -27,9 +29,11 @@ export const Dialog = forwardRef<DialogMethods, DialogProps>(function Dialog(
27
29
  {
28
30
  containerClassName,
29
31
  contentClassName,
30
- useBlur = true,
32
+ useBlur = false,
31
33
  children,
34
+ closable = true,
32
35
  onRequestClose,
36
+ backdropClassName,
33
37
  }: DialogProps,
34
38
  ref
35
39
  ) {
@@ -70,27 +74,35 @@ export const Dialog = forwardRef<DialogMethods, DialogProps>(function Dialog(
70
74
  }
71
75
  }, [isOpen]);
72
76
 
77
+ const handleOnRequestClose =
78
+ onRequestClose ?? closable ? getRef().hide : undefined;
73
79
  return (
74
80
  <Modal
75
81
  visible={modalOpen}
76
82
  transparent
77
- onRequestClose={onRequestClose}
83
+ onRequestClose={handleOnRequestClose}
78
84
  animationType="fade"
79
85
  statusBarTranslucent
80
86
  >
81
- <Pressable
87
+ <View
82
88
  className={cn(
83
- "flex-1 items-center justify-center p-4 w-full h-full bg-black/35",
89
+ "flex-1 items-center justify-center p-4 w-full h-full ",
84
90
  containerClassName
85
91
  )}
86
- onPress={onRequestClose}
87
- disabled={!onRequestClose}
88
92
  >
93
+ <Pressable
94
+ onPress={handleOnRequestClose}
95
+ disabled={!closable && !onRequestClose}
96
+ className={cn(
97
+ "flex-1 bg-black/35 -z-10 w-full h-full absolute top-0 left-0",
98
+ backdropClassName
99
+ )}
100
+ />
89
101
  <AnimatePresence exitBeforeEnter>
90
102
  {isOpen && (
91
103
  <View
92
104
  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",
105
+ "absolute overflow-hidden in:opacity-0 opacity-100 out:opacity-0 in:scale-0 scale-100 out:scale-0 z-15 bg-card/95 rounded-xl max-w-sm w-full border border-muted/10",
94
106
  contentClassName
95
107
  )}
96
108
  onDidAnimate={onDidAnimate}
@@ -105,7 +117,7 @@ export const Dialog = forwardRef<DialogMethods, DialogProps>(function Dialog(
105
117
  </View>
106
118
  )}
107
119
  </AnimatePresence>
108
- </Pressable>
120
+ </View>
109
121
  </Modal>
110
122
  );
111
123
  });
@@ -130,7 +130,7 @@ let timeout: NodeJS.Timeout;
130
130
  const DropdownMenu = ({
131
131
  className,
132
132
  children,
133
- useBlur = true,
133
+ useBlur = false,
134
134
  }: {
135
135
  className?: string;
136
136
  children: ReactNode;
@@ -215,12 +215,14 @@ const DropdownItem = ({
215
215
  children,
216
216
  last,
217
217
  first,
218
+ autoClosable = true,
218
219
  ...props
219
220
  }: {
220
221
  className?: string;
221
222
  last?: boolean;
222
223
  children: ReactNode;
223
224
  first?: boolean;
225
+ autoClosable?: boolean;
224
226
  } & PressableProps) => {
225
227
  const close = useDropdownContext().close;
226
228
  return (
@@ -233,7 +235,7 @@ const DropdownItem = ({
233
235
  )}
234
236
  {...props}
235
237
  onPress={() => {
236
- close();
238
+ if (autoClosable) close();
237
239
  props.onPress && props.onPress();
238
240
  }}
239
241
  >
@@ -1,24 +1,52 @@
1
- import {
2
- cn,
3
- Text,
4
- TextInput,
5
- TextInputProps,
6
- useTw,
7
- View,
8
- } from "@nativetail/core";
9
- import { useCallback, useState } from "react";
1
+ import { cn, Text, TextInput, TextInputProps, View } from "@nativetail/core";
2
+ import { LegacyRef, useCallback, useState } from "react";
10
3
  import ShowPassword from "./show-password";
4
+ import { Control, Controller, Path } from "react-hook-form";
11
5
 
12
- type FloatingInputProps = Omit<TextInputProps, "placeholder"> & {
6
+ export type FloatingInputProps<T = Record<string, any>> = TextInputProps & {
13
7
  containerClassName?: string;
14
- label: string;
8
+ label?: string;
15
9
  error?: string;
16
10
  helperText?: string;
17
11
  isSecretToggleable?: boolean;
18
12
  leftElement?: React.ReactNode;
19
13
  rightElement?: React.ReactNode;
14
+ value?: string;
15
+ control?: Control<T, any>;
16
+ name?: Path<T>;
17
+ inputRef?: LegacyRef<typeof TextInput>;
18
+ labelClassName?: string;
19
+ activeLabelClassName?: string;
20
20
  };
21
- export function FloatingInput({
21
+
22
+ export const FloatingInput = <T extends Record<string, any>>({
23
+ name,
24
+ control,
25
+ ...props
26
+ }: FloatingInputProps<T>) => {
27
+ if (control) {
28
+ return (
29
+ <Controller
30
+ name={name}
31
+ control={control}
32
+ render={({ field, fieldState }) => {
33
+ return (
34
+ <BaseInput
35
+ {...props}
36
+ value={field.value}
37
+ onChangeText={(text) => {
38
+ field.onChange(text);
39
+ }}
40
+ error={fieldState?.error?.message}
41
+ />
42
+ );
43
+ }}
44
+ />
45
+ );
46
+ }
47
+ return <BaseInput {...props} />;
48
+ };
49
+ function BaseInput({
22
50
  value,
23
51
  onChangeText,
24
52
  containerClassName,
@@ -29,6 +57,8 @@ export function FloatingInput({
29
57
  helperText,
30
58
  leftElement,
31
59
  rightElement,
60
+ labelClassName,
61
+ activeLabelClassName,
32
62
  ...props
33
63
  }: FloatingInputProps) {
34
64
  const [isFocused, setIsFocused] = useState(false);
@@ -49,7 +79,13 @@ export function FloatingInput({
49
79
  containerClassName
50
80
  )}
51
81
  >
52
- <Label label={label} value={value} isFocused={isFocused} />
82
+ <Label
83
+ label={label}
84
+ value={value}
85
+ isFocused={isFocused}
86
+ labelClassName={labelClassName}
87
+ activeLabelClassName={activeLabelClassName}
88
+ />
53
89
 
54
90
  {leftElement && (
55
91
  <View className="absolute left-2 bottom-2">{leftElement}</View>
@@ -59,6 +95,7 @@ export function FloatingInput({
59
95
  onBlur={onBlur}
60
96
  value={value}
61
97
  onChangeText={onChangeText}
98
+ enablesReturnKeyAutomatically
62
99
  className={cn(
63
100
  "flex-1 p-3 bg-card rounded-xl absolute w-full h-full -z-5 pt-5 text-foreground text-[16px]",
64
101
  className,
@@ -91,10 +128,14 @@ const Label = ({
91
128
  label,
92
129
  value,
93
130
  isFocused,
131
+ activeLabelClassName,
132
+ labelClassName,
94
133
  }: {
95
134
  label?: string;
96
135
  value?: string;
97
136
  isFocused?: boolean;
137
+ labelClassName?: string;
138
+ activeLabelClassName?: string;
98
139
  }) => {
99
140
  const labelOnTop = isFocused || !!value;
100
141
 
@@ -103,8 +144,10 @@ const Label = ({
103
144
  <Text
104
145
  animated
105
146
  className={cn(
106
- "text-muted duration-75 ",
107
- labelOnTop ? " -translate-y-16 text-xs" : "translate-y-0 text-[16px]"
147
+ "text-muted duration-75 translate-y-0 text-[16px]",
148
+ labelClassName,
149
+ labelOnTop ? " -translate-y-16 text-xs" : "",
150
+ labelOnTop ? activeLabelClassName : ""
108
151
  )}
109
152
  >
110
153
  {label}
@@ -6,19 +6,54 @@ import {
6
6
  useTw,
7
7
  View,
8
8
  } from "@nativetail/core";
9
- import { useState } from "react";
9
+ import React, { LegacyRef, useState } from "react";
10
+ import { Control, Controller, Path } from "react-hook-form";
11
+ import { TextInput as NativeTextInput } from "react-native";
10
12
  import ShowPassword from "./show-password";
11
13
 
12
- type InputProps = TextInputProps & {
14
+ export type InputProps<T = Record<string, any>> = TextInputProps & {
13
15
  containerClassName?: string;
14
- label: string;
16
+ label?: string;
15
17
  error?: string;
16
18
  helperText?: string;
17
19
  isSecretToggleable?: boolean;
18
20
  leftElement?: React.ReactNode;
19
21
  rightElement?: React.ReactNode;
22
+ value?: string;
23
+ control?: Control<T, any>;
24
+ name?: Path<T>;
25
+ inputRef?: LegacyRef<NativeTextInput>;
20
26
  };
21
- export function Input({
27
+
28
+ export const Input = <T extends Record<string, any>>({
29
+ name,
30
+ control,
31
+ ...props
32
+ }: InputProps<T>) => {
33
+ if (control) {
34
+ return (
35
+ <Controller
36
+ name={name}
37
+ control={control}
38
+ render={({ field, fieldState }) => {
39
+ return (
40
+ <BaseInput
41
+ {...props}
42
+ value={field.value}
43
+ onChangeText={(text) => {
44
+ field.onChange(text);
45
+ }}
46
+ error={fieldState.error?.message}
47
+ />
48
+ );
49
+ }}
50
+ />
51
+ );
52
+ }
53
+ return <BaseInput {...props} />;
54
+ };
55
+
56
+ const BaseInput = <T extends Record<string, any>>({
22
57
  value,
23
58
  onChangeText,
24
59
  containerClassName,
@@ -29,8 +64,9 @@ export function Input({
29
64
  rightElement,
30
65
  helperText,
31
66
  leftElement,
67
+ inputRef,
32
68
  ...props
33
- }: InputProps) {
69
+ }: InputProps<T>) => {
34
70
  const tw = useTw();
35
71
 
36
72
  const [showPassword, setShowPassword] = useState(
@@ -38,16 +74,18 @@ export function Input({
38
74
  );
39
75
  return (
40
76
  <View className={cn("w-full gap-1", containerClassName)}>
41
- <Text className={cn("text-muted/75 duration-75 ")}>{label}</Text>
77
+ <Text className={cn("text-muted/75 duration-75 text-sm")}>{label}</Text>
42
78
 
43
79
  <TextInput
44
80
  value={value}
45
81
  onChangeText={onChangeText}
82
+ ref={inputRef}
46
83
  className={cn(
47
- "p-3 bg-card rounded-lg w-full border border-muted/15 h-14 text-foreground -z-5 text-[16px]",
84
+ "p-3 bg-card rounded-lg w-full border border-muted/15 h-12 text-foreground -z-5 text-[16px]",
48
85
  className,
49
86
  isSecretToggleable || rightElement ? "pr-12" : "",
50
- leftElement ? "pl-12" : ""
87
+ leftElement ? "pl-12" : "",
88
+ error && "border-danger"
51
89
  )}
52
90
  placeholderTextColor={tw.color("muted")}
53
91
  secureTextEntry={!showPassword}
@@ -72,4 +110,4 @@ export function Input({
72
110
  )}
73
111
  </View>
74
112
  );
75
- }
113
+ };