@medway-ui/native 1.0.2
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 +153 -0
- package/lib/commonjs/components/Alert.js +206 -0
- package/lib/commonjs/components/Alert.js.map +1 -0
- package/lib/commonjs/components/AlertDialog.js +273 -0
- package/lib/commonjs/components/AlertDialog.js.map +1 -0
- package/lib/commonjs/components/Badge.js +94 -0
- package/lib/commonjs/components/Badge.js.map +1 -0
- package/lib/commonjs/components/Button.js +181 -0
- package/lib/commonjs/components/Button.js.map +1 -0
- package/lib/commonjs/components/Checkbox.js +91 -0
- package/lib/commonjs/components/Checkbox.js.map +1 -0
- package/lib/commonjs/components/Dialog.js +164 -0
- package/lib/commonjs/components/Dialog.js.map +1 -0
- package/lib/commonjs/components/Input.js +364 -0
- package/lib/commonjs/components/Input.js.map +1 -0
- package/lib/commonjs/components/Label.js +90 -0
- package/lib/commonjs/components/Label.js.map +1 -0
- package/lib/commonjs/components/Loading.js +49 -0
- package/lib/commonjs/components/Loading.js.map +1 -0
- package/lib/commonjs/components/Popover.js +54 -0
- package/lib/commonjs/components/Popover.js.map +1 -0
- package/lib/commonjs/components/RadioGroup.js +72 -0
- package/lib/commonjs/components/RadioGroup.js.map +1 -0
- package/lib/commonjs/components/Select.js +234 -0
- package/lib/commonjs/components/Select.js.map +1 -0
- package/lib/commonjs/components/Separator.js +22 -0
- package/lib/commonjs/components/Separator.js.map +1 -0
- package/lib/commonjs/components/Sheet.js +148 -0
- package/lib/commonjs/components/Sheet.js.map +1 -0
- package/lib/commonjs/components/Switch.js +99 -0
- package/lib/commonjs/components/Switch.js.map +1 -0
- package/lib/commonjs/components/Tag.js +132 -0
- package/lib/commonjs/components/Tag.js.map +1 -0
- package/lib/commonjs/components/Toast.js +244 -0
- package/lib/commonjs/components/Toast.js.map +1 -0
- package/lib/commonjs/components/Tooltip.js +54 -0
- package/lib/commonjs/components/Tooltip.js.map +1 -0
- package/lib/commonjs/hooks/use-toast.js +136 -0
- package/lib/commonjs/hooks/use-toast.js.map +1 -0
- package/lib/commonjs/index.js +667 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/lib/utils.js +22 -0
- package/lib/commonjs/lib/utils.js.map +1 -0
- package/lib/commonjs/nativewind-env.d.js +3 -0
- package/lib/commonjs/nativewind-env.d.js.map +1 -0
- package/lib/module/components/Alert.js +195 -0
- package/lib/module/components/Alert.js.map +1 -0
- package/lib/module/components/AlertDialog.js +257 -0
- package/lib/module/components/AlertDialog.js.map +1 -0
- package/lib/module/components/Badge.js +90 -0
- package/lib/module/components/Badge.js.map +1 -0
- package/lib/module/components/Button.js +177 -0
- package/lib/module/components/Button.js.map +1 -0
- package/lib/module/components/Checkbox.js +86 -0
- package/lib/module/components/Checkbox.js.map +1 -0
- package/lib/module/components/Dialog.js +150 -0
- package/lib/module/components/Dialog.js.map +1 -0
- package/lib/module/components/Input.js +349 -0
- package/lib/module/components/Input.js.map +1 -0
- package/lib/module/components/Label.js +86 -0
- package/lib/module/components/Label.js.map +1 -0
- package/lib/module/components/Loading.js +45 -0
- package/lib/module/components/Loading.js.map +1 -0
- package/lib/module/components/Popover.js +47 -0
- package/lib/module/components/Popover.js.map +1 -0
- package/lib/module/components/RadioGroup.js +66 -0
- package/lib/module/components/RadioGroup.js.map +1 -0
- package/lib/module/components/Select.js +217 -0
- package/lib/module/components/Select.js.map +1 -0
- package/lib/module/components/Separator.js +19 -0
- package/lib/module/components/Separator.js.map +1 -0
- package/lib/module/components/Sheet.js +136 -0
- package/lib/module/components/Sheet.js.map +1 -0
- package/lib/module/components/Switch.js +94 -0
- package/lib/module/components/Switch.js.map +1 -0
- package/lib/module/components/Tag.js +128 -0
- package/lib/module/components/Tag.js.map +1 -0
- package/lib/module/components/Toast.js +233 -0
- package/lib/module/components/Toast.js.map +1 -0
- package/lib/module/components/Tooltip.js +47 -0
- package/lib/module/components/Tooltip.js.map +1 -0
- package/lib/module/hooks/use-toast.js +130 -0
- package/lib/module/hooks/use-toast.js.map +1 -0
- package/lib/module/index.js +41 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib/utils.js +18 -0
- package/lib/module/lib/utils.js.map +1 -0
- package/lib/module/nativewind-env.d.js +3 -0
- package/lib/module/nativewind-env.d.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/components/Alert.d.ts +36 -0
- package/lib/typescript/components/Alert.d.ts.map +1 -0
- package/lib/typescript/components/AlertDialog.d.ts +44 -0
- package/lib/typescript/components/AlertDialog.d.ts.map +1 -0
- package/lib/typescript/components/Badge.d.ts +17 -0
- package/lib/typescript/components/Badge.d.ts.map +1 -0
- package/lib/typescript/components/Button.d.ts +24 -0
- package/lib/typescript/components/Button.d.ts.map +1 -0
- package/lib/typescript/components/Checkbox.d.ts +20 -0
- package/lib/typescript/components/Checkbox.d.ts.map +1 -0
- package/lib/typescript/components/Dialog.d.ts +35 -0
- package/lib/typescript/components/Dialog.d.ts.map +1 -0
- package/lib/typescript/components/Input.d.ts +57 -0
- package/lib/typescript/components/Input.d.ts.map +1 -0
- package/lib/typescript/components/Label.d.ts +21 -0
- package/lib/typescript/components/Label.d.ts.map +1 -0
- package/lib/typescript/components/Loading.d.ts +9 -0
- package/lib/typescript/components/Loading.d.ts.map +1 -0
- package/lib/typescript/components/Popover.d.ts +11 -0
- package/lib/typescript/components/Popover.d.ts.map +1 -0
- package/lib/typescript/components/RadioGroup.d.ts +13 -0
- package/lib/typescript/components/RadioGroup.d.ts.map +1 -0
- package/lib/typescript/components/Select.d.ts +37 -0
- package/lib/typescript/components/Select.d.ts.map +1 -0
- package/lib/typescript/components/Separator.d.ts +9 -0
- package/lib/typescript/components/Separator.d.ts.map +1 -0
- package/lib/typescript/components/Sheet.d.ts +31 -0
- package/lib/typescript/components/Sheet.d.ts.map +1 -0
- package/lib/typescript/components/Switch.d.ts +18 -0
- package/lib/typescript/components/Switch.d.ts.map +1 -0
- package/lib/typescript/components/Tag.d.ts +17 -0
- package/lib/typescript/components/Tag.d.ts.map +1 -0
- package/lib/typescript/components/Toast.d.ts +42 -0
- package/lib/typescript/components/Toast.d.ts.map +1 -0
- package/lib/typescript/components/Tooltip.d.ts +14 -0
- package/lib/typescript/components/Tooltip.d.ts.map +1 -0
- package/lib/typescript/hooks/use-toast.d.ts +46 -0
- package/lib/typescript/hooks/use-toast.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +38 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/lib/utils.d.ts +3 -0
- package/lib/typescript/lib/utils.d.ts.map +1 -0
- package/package.json +111 -0
- package/src/components/Alert.tsx +185 -0
- package/src/components/AlertDialog.tsx +277 -0
- package/src/components/Badge.tsx +113 -0
- package/src/components/Button.tsx +224 -0
- package/src/components/Checkbox.tsx +106 -0
- package/src/components/Dialog.tsx +175 -0
- package/src/components/Input.tsx +439 -0
- package/src/components/Label.tsx +95 -0
- package/src/components/Loading.tsx +54 -0
- package/src/components/Popover.tsx +46 -0
- package/src/components/RadioGroup.tsx +62 -0
- package/src/components/Select.tsx +246 -0
- package/src/components/Separator.tsx +24 -0
- package/src/components/Sheet.tsx +165 -0
- package/src/components/Switch.tsx +138 -0
- package/src/components/Tag.tsx +116 -0
- package/src/components/Toast.tsx +265 -0
- package/src/components/Tooltip.tsx +49 -0
- package/src/hooks/use-toast.ts +144 -0
- package/src/index.ts +193 -0
- package/src/lib/utils.ts +63 -0
- package/src/nativewind-env.d.ts +1 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useState,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
cloneElement,
|
|
6
|
+
type ReactElement,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
View,
|
|
12
|
+
TextInput,
|
|
13
|
+
Text,
|
|
14
|
+
Pressable,
|
|
15
|
+
type TextInputProps,
|
|
16
|
+
type ViewProps,
|
|
17
|
+
type TextProps,
|
|
18
|
+
} from "react-native";
|
|
19
|
+
import Animated, {
|
|
20
|
+
useSharedValue,
|
|
21
|
+
useAnimatedStyle,
|
|
22
|
+
withTiming,
|
|
23
|
+
interpolate,
|
|
24
|
+
Easing,
|
|
25
|
+
} from "react-native-reanimated";
|
|
26
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
27
|
+
import {
|
|
28
|
+
ChevronDown,
|
|
29
|
+
Hide,
|
|
30
|
+
Lock,
|
|
31
|
+
Mail,
|
|
32
|
+
Show,
|
|
33
|
+
TriangleWarning,
|
|
34
|
+
} from "@medway-ui/icons/native";
|
|
35
|
+
import { cn } from "@/lib/utils";
|
|
36
|
+
|
|
37
|
+
export type InputType = "text" | "email" | "numeric" | "password";
|
|
38
|
+
|
|
39
|
+
type IconElement = ReactElement<{ size?: number; strokeWidth?: number }>;
|
|
40
|
+
|
|
41
|
+
export interface InputProps
|
|
42
|
+
extends Omit<TextInputProps, "size">,
|
|
43
|
+
Omit<VariantProps<typeof inputVariants>, "focused" | "disabled"> {
|
|
44
|
+
type?: InputType;
|
|
45
|
+
invalid?: boolean;
|
|
46
|
+
iconLeft?: IconElement;
|
|
47
|
+
iconRight?: IconElement;
|
|
48
|
+
label?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface InputPasswordProps
|
|
52
|
+
extends Omit<TextInputProps, "size">,
|
|
53
|
+
Omit<VariantProps<typeof inputVariants>, "focused" | "disabled"> {
|
|
54
|
+
invalid?: boolean;
|
|
55
|
+
label?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface InputNumericProps
|
|
59
|
+
extends Omit<TextInputProps, "size" | "onChangeText">,
|
|
60
|
+
Omit<VariantProps<typeof inputVariants>, "focused" | "disabled"> {
|
|
61
|
+
invalid?: boolean;
|
|
62
|
+
iconLeft?: IconElement;
|
|
63
|
+
onChangeText?: (value: string) => void;
|
|
64
|
+
min?: number;
|
|
65
|
+
allowNegative?: boolean;
|
|
66
|
+
label?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface InputLabelProps {
|
|
70
|
+
children?: ReactNode;
|
|
71
|
+
floated?: boolean;
|
|
72
|
+
disabled?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface InputTextProps extends TextProps {
|
|
76
|
+
invalid?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const INPUT_ICON_SIZE_LG = 24;
|
|
80
|
+
const INPUT_ICON_SIZE_MD = 20;
|
|
81
|
+
const INPUT_ICON_STROKE_WIDTH = 1.6;
|
|
82
|
+
const CHEVRON_SIZE_LG = 10;
|
|
83
|
+
const CHEVRON_SIZE_MD = 8;
|
|
84
|
+
|
|
85
|
+
const inputVariants = cva(
|
|
86
|
+
"w-full rounded-md border-2 border-gray-stroke-muted bg-surface-card px-4 text-gray-foreground",
|
|
87
|
+
{
|
|
88
|
+
variants: {
|
|
89
|
+
size: {
|
|
90
|
+
lg: "h-12 text-md",
|
|
91
|
+
md: "h-10 text-sm",
|
|
92
|
+
},
|
|
93
|
+
invalid: {
|
|
94
|
+
true: "border-feedback-error-stroke",
|
|
95
|
+
false: "",
|
|
96
|
+
},
|
|
97
|
+
focused: {
|
|
98
|
+
true: "border-gray-stroke-emphasis",
|
|
99
|
+
false: "",
|
|
100
|
+
},
|
|
101
|
+
disabled: {
|
|
102
|
+
true: "bg-gray-background-soft-muted text-gray-foreground-muted",
|
|
103
|
+
false: "",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
defaultVariants: {
|
|
107
|
+
size: "lg",
|
|
108
|
+
invalid: false,
|
|
109
|
+
focused: false,
|
|
110
|
+
disabled: false,
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
function InputWrapper({ className, ...props }: ViewProps) {
|
|
116
|
+
return <View className={cn("flex-col gap-2", className)} {...props} />;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function InputField({ className, ...props }: ViewProps) {
|
|
120
|
+
return <View className={cn("relative", className)} {...props} />;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function FloatingLabel({ children, floated = false, disabled = false }: InputLabelProps) {
|
|
124
|
+
const progress = useSharedValue(floated ? 1 : 0);
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
progress.value = withTiming(floated ? 1 : 0, { duration: 200, easing: Easing.out(Easing.ease) });
|
|
128
|
+
}, [floated]);
|
|
129
|
+
|
|
130
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
131
|
+
transform: [{ translateY: interpolate(progress.value, [0, 1], [-6, -22]) }],
|
|
132
|
+
fontSize: interpolate(progress.value, [0, 1], [15, 12]),
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Animated.Text
|
|
137
|
+
style={[
|
|
138
|
+
animatedStyle,
|
|
139
|
+
{
|
|
140
|
+
position: "absolute",
|
|
141
|
+
left: 16,
|
|
142
|
+
top: "50%",
|
|
143
|
+
zIndex: 10,
|
|
144
|
+
},
|
|
145
|
+
]}
|
|
146
|
+
className={cn(
|
|
147
|
+
"font-medium",
|
|
148
|
+
disabled ? "text-gray-foreground-muted" : "text-gray-foreground"
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
{children}
|
|
152
|
+
</Animated.Text>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function InputLabel({ children, floated = false, disabled = false }: InputLabelProps) {
|
|
157
|
+
return <FloatingLabel floated={floated} disabled={disabled}>{children}</FloatingLabel>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function InputText({ invalid = false, className, ...props }: InputTextProps) {
|
|
161
|
+
return (
|
|
162
|
+
<Text
|
|
163
|
+
className={cn(
|
|
164
|
+
"text-xs -mt-1",
|
|
165
|
+
invalid ? "text-feedback-error-foreground" : "text-gray-foreground",
|
|
166
|
+
className
|
|
167
|
+
)}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function InputAdornment({ className, ...props }: ViewProps) {
|
|
174
|
+
return <View className={cn("relative flex-row w-full", className)} {...props} />;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function InputStartIcon({ className, ...props }: ViewProps) {
|
|
178
|
+
return (
|
|
179
|
+
<View
|
|
180
|
+
className={cn("absolute left-4 top-1/2 z-10 flex-row items-center -translate-y-1/2", className)}
|
|
181
|
+
{...props}
|
|
182
|
+
/>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function InputEndIcon({ className, ...props }: ViewProps) {
|
|
187
|
+
return (
|
|
188
|
+
<View
|
|
189
|
+
className={cn("absolute right-4 top-1/2 z-10 flex-row items-center -translate-y-1/2", className)}
|
|
190
|
+
{...props}
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function applyInputIconSize(
|
|
196
|
+
icon: ReactElement<{ size?: number; strokeWidth?: number }>,
|
|
197
|
+
size: VariantProps<typeof inputVariants>["size"] = "lg"
|
|
198
|
+
): ReactElement {
|
|
199
|
+
const iconSize = size === "md" ? INPUT_ICON_SIZE_MD : INPUT_ICON_SIZE_LG;
|
|
200
|
+
return cloneElement(icon, { size: iconSize, strokeWidth: INPUT_ICON_STROKE_WIDTH });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function InputPassword({ className, size = "lg", invalid = false, label, ...props }: InputPasswordProps) {
|
|
204
|
+
const [visible, setVisible] = useState(false);
|
|
205
|
+
const [focused, setFocused] = useState(false);
|
|
206
|
+
const hasValue = Boolean(props.value || props.defaultValue);
|
|
207
|
+
const isFloated = focused || hasValue;
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<InputAdornment>
|
|
211
|
+
<InputStartIcon>
|
|
212
|
+
{applyInputIconSize(<Lock />, size)}
|
|
213
|
+
</InputStartIcon>
|
|
214
|
+
<View className="relative flex-1">
|
|
215
|
+
{label && <InputLabel floated={isFloated} disabled={props.editable === false}>{label}</InputLabel>}
|
|
216
|
+
<TextInput
|
|
217
|
+
secureTextEntry={!visible}
|
|
218
|
+
className={cn(
|
|
219
|
+
inputVariants({ size, invalid, focused, disabled: props.editable === false }),
|
|
220
|
+
"pl-12 pr-12",
|
|
221
|
+
className
|
|
222
|
+
)}
|
|
223
|
+
onFocus={() => setFocused(true)}
|
|
224
|
+
onBlur={() => setFocused(false)}
|
|
225
|
+
accessibilityLabel={label}
|
|
226
|
+
{...props}
|
|
227
|
+
/>
|
|
228
|
+
</View>
|
|
229
|
+
<InputEndIcon>
|
|
230
|
+
<Pressable
|
|
231
|
+
onPress={() => setVisible((v) => !v)}
|
|
232
|
+
disabled={props.editable === false}
|
|
233
|
+
accessibilityLabel={visible ? "Hide password" : "Show password"}
|
|
234
|
+
>
|
|
235
|
+
{applyInputIconSize(visible ? <Hide /> : <Show />, size)}
|
|
236
|
+
</Pressable>
|
|
237
|
+
</InputEndIcon>
|
|
238
|
+
</InputAdornment>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function InputNumeric({
|
|
243
|
+
className,
|
|
244
|
+
size = "lg",
|
|
245
|
+
invalid = false,
|
|
246
|
+
iconLeft,
|
|
247
|
+
min,
|
|
248
|
+
value,
|
|
249
|
+
defaultValue,
|
|
250
|
+
onChangeText,
|
|
251
|
+
editable = true,
|
|
252
|
+
allowNegative = false,
|
|
253
|
+
label,
|
|
254
|
+
...props
|
|
255
|
+
}: InputNumericProps) {
|
|
256
|
+
const [internalValue, setInternalValue] = useState(String(defaultValue ?? ""));
|
|
257
|
+
const isControlled = value !== undefined;
|
|
258
|
+
const displayValue = isControlled ? String(value ?? "") : internalValue;
|
|
259
|
+
const [focused, setFocused] = useState(false);
|
|
260
|
+
const isFloated = focused || displayValue !== "";
|
|
261
|
+
|
|
262
|
+
const numericValue = parseFloat(displayValue.replace(",", ".")) || 0;
|
|
263
|
+
|
|
264
|
+
const handleStep = useCallback(
|
|
265
|
+
(delta: number) => {
|
|
266
|
+
let next = numericValue + delta;
|
|
267
|
+
if (min !== undefined && next < min) next = min;
|
|
268
|
+
const nextStr = String(next);
|
|
269
|
+
if (!isControlled) setInternalValue(nextStr);
|
|
270
|
+
onChangeText?.(nextStr);
|
|
271
|
+
},
|
|
272
|
+
[numericValue, min, isControlled, onChangeText]
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const chevronSize = size === "md" ? CHEVRON_SIZE_MD : CHEVRON_SIZE_LG;
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<InputAdornment>
|
|
279
|
+
{iconLeft && (
|
|
280
|
+
<InputStartIcon>
|
|
281
|
+
{applyInputIconSize(iconLeft, size)}
|
|
282
|
+
</InputStartIcon>
|
|
283
|
+
)}
|
|
284
|
+
<View className="relative flex-1">
|
|
285
|
+
{label && <InputLabel floated={isFloated} disabled={!editable}>{label}</InputLabel>}
|
|
286
|
+
<TextInput
|
|
287
|
+
keyboardType={allowNegative ? "numbers-and-punctuation" : "numeric"}
|
|
288
|
+
value={displayValue}
|
|
289
|
+
onChangeText={(text) => {
|
|
290
|
+
const cleaned = allowNegative
|
|
291
|
+
? text.replace(/[^0-9\-]/g, "").replace(/(?!^)-/g, "")
|
|
292
|
+
: text.replace(/[^0-9]/g, "");
|
|
293
|
+
if (!isControlled) setInternalValue(cleaned);
|
|
294
|
+
onChangeText?.(cleaned);
|
|
295
|
+
}}
|
|
296
|
+
editable={editable}
|
|
297
|
+
onFocus={() => setFocused(true)}
|
|
298
|
+
onBlur={() => setFocused(false)}
|
|
299
|
+
className={cn(
|
|
300
|
+
inputVariants({ size, invalid, focused, disabled: !editable }),
|
|
301
|
+
iconLeft ? "pl-12" : "",
|
|
302
|
+
"pr-12",
|
|
303
|
+
className
|
|
304
|
+
)}
|
|
305
|
+
accessibilityLabel={label}
|
|
306
|
+
{...props}
|
|
307
|
+
/>
|
|
308
|
+
</View>
|
|
309
|
+
<InputEndIcon>
|
|
310
|
+
<View className="flex-col">
|
|
311
|
+
<Pressable
|
|
312
|
+
disabled={!editable}
|
|
313
|
+
onPress={() => handleStep(1)}
|
|
314
|
+
className="items-center justify-center p-1"
|
|
315
|
+
accessibilityLabel="Increase value"
|
|
316
|
+
>
|
|
317
|
+
<ChevronDown
|
|
318
|
+
style={{ transform: [{ rotate: "180deg" }] }}
|
|
319
|
+
size={chevronSize}
|
|
320
|
+
strokeWidth={3}
|
|
321
|
+
/>
|
|
322
|
+
</Pressable>
|
|
323
|
+
<Pressable
|
|
324
|
+
disabled={!editable}
|
|
325
|
+
onPress={() => handleStep(-1)}
|
|
326
|
+
className="items-center justify-center p-1"
|
|
327
|
+
accessibilityLabel="Decrease value"
|
|
328
|
+
>
|
|
329
|
+
<ChevronDown size={chevronSize} strokeWidth={3} />
|
|
330
|
+
</Pressable>
|
|
331
|
+
</View>
|
|
332
|
+
</InputEndIcon>
|
|
333
|
+
</InputAdornment>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function Input({
|
|
338
|
+
className,
|
|
339
|
+
type = "text",
|
|
340
|
+
size = "lg",
|
|
341
|
+
invalid = false,
|
|
342
|
+
iconLeft,
|
|
343
|
+
iconRight,
|
|
344
|
+
label,
|
|
345
|
+
value,
|
|
346
|
+
defaultValue,
|
|
347
|
+
editable = true,
|
|
348
|
+
...props
|
|
349
|
+
}: InputProps) {
|
|
350
|
+
const [focused, setFocused] = useState(false);
|
|
351
|
+
const hasValue = Boolean(value || defaultValue);
|
|
352
|
+
const isFloated = focused || hasValue;
|
|
353
|
+
|
|
354
|
+
if (type === "numeric") {
|
|
355
|
+
return (
|
|
356
|
+
<InputNumeric
|
|
357
|
+
className={className}
|
|
358
|
+
size={size}
|
|
359
|
+
invalid={invalid}
|
|
360
|
+
iconLeft={iconLeft}
|
|
361
|
+
value={value as string}
|
|
362
|
+
defaultValue={defaultValue as string}
|
|
363
|
+
editable={editable}
|
|
364
|
+
label={label}
|
|
365
|
+
{...props}
|
|
366
|
+
/>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (type === "password") {
|
|
371
|
+
return (
|
|
372
|
+
<InputPassword
|
|
373
|
+
className={className}
|
|
374
|
+
size={size}
|
|
375
|
+
invalid={invalid}
|
|
376
|
+
value={value as string}
|
|
377
|
+
defaultValue={defaultValue as string}
|
|
378
|
+
editable={editable}
|
|
379
|
+
label={label}
|
|
380
|
+
{...props}
|
|
381
|
+
/>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const resolvedIconLeft = iconLeft ?? (type === "email" ? <Mail /> : undefined);
|
|
386
|
+
const showInvalidRightIcon = invalid && (type === "text" || type === "email");
|
|
387
|
+
const resolvedIconRight = showInvalidRightIcon
|
|
388
|
+
? <TriangleWarning />
|
|
389
|
+
: iconRight;
|
|
390
|
+
|
|
391
|
+
return (
|
|
392
|
+
<InputAdornment>
|
|
393
|
+
{resolvedIconLeft && (
|
|
394
|
+
<InputStartIcon>
|
|
395
|
+
{applyInputIconSize(resolvedIconLeft, size)}
|
|
396
|
+
</InputStartIcon>
|
|
397
|
+
)}
|
|
398
|
+
<View className="relative flex-1">
|
|
399
|
+
{label && <InputLabel floated={isFloated} disabled={!editable}>{label}</InputLabel>}
|
|
400
|
+
<TextInput
|
|
401
|
+
keyboardType={type === "email" ? "email-address" : "default"}
|
|
402
|
+
value={value as string}
|
|
403
|
+
defaultValue={defaultValue as string}
|
|
404
|
+
editable={editable}
|
|
405
|
+
onFocus={() => setFocused(true)}
|
|
406
|
+
onBlur={() => setFocused(false)}
|
|
407
|
+
className={cn(
|
|
408
|
+
inputVariants({ size, invalid, focused, disabled: !editable }),
|
|
409
|
+
resolvedIconLeft ? "pl-12" : "",
|
|
410
|
+
resolvedIconRight ? "pr-12" : "",
|
|
411
|
+
className
|
|
412
|
+
)}
|
|
413
|
+
accessibilityLabel={label}
|
|
414
|
+
{...props}
|
|
415
|
+
/>
|
|
416
|
+
</View>
|
|
417
|
+
{resolvedIconRight && (
|
|
418
|
+
<InputEndIcon>
|
|
419
|
+
{applyInputIconSize(resolvedIconRight, size)}
|
|
420
|
+
</InputEndIcon>
|
|
421
|
+
)}
|
|
422
|
+
</InputAdornment>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export {
|
|
427
|
+
Input,
|
|
428
|
+
InputAdornment,
|
|
429
|
+
applyInputIconSize,
|
|
430
|
+
InputEndIcon,
|
|
431
|
+
InputField,
|
|
432
|
+
InputLabel,
|
|
433
|
+
InputNumeric,
|
|
434
|
+
InputPassword,
|
|
435
|
+
InputStartIcon,
|
|
436
|
+
InputText,
|
|
437
|
+
InputWrapper,
|
|
438
|
+
inputVariants,
|
|
439
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { Pressable, Text, type PressableProps } from "react-native";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
interface LabelProps extends PressableProps, VariantProps<typeof labelVariants> {
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
block?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const labelVariants = cva(
|
|
13
|
+
"flex-row items-center gap-3 shrink-0",
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
variant: {
|
|
17
|
+
regular: "text-gray-foreground",
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
sm: "text-xs",
|
|
21
|
+
md: "text-sm",
|
|
22
|
+
lg: "text-md",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: "regular",
|
|
27
|
+
size: "md",
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const labelTextVariants = cva("font-medium leading-none select-none", {
|
|
33
|
+
variants: {
|
|
34
|
+
variant: {
|
|
35
|
+
regular: "text-gray-foreground",
|
|
36
|
+
},
|
|
37
|
+
size: {
|
|
38
|
+
sm: "text-xs",
|
|
39
|
+
md: "text-sm",
|
|
40
|
+
lg: "text-md",
|
|
41
|
+
},
|
|
42
|
+
disabled: {
|
|
43
|
+
true: "text-gray-foreground-muted",
|
|
44
|
+
false: "",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
defaultVariants: {
|
|
48
|
+
variant: "regular",
|
|
49
|
+
size: "md",
|
|
50
|
+
disabled: false,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function Label({ className, variant, size, block, disabled = false, children, ...props }: LabelProps) {
|
|
55
|
+
if (block) {
|
|
56
|
+
return (
|
|
57
|
+
<Pressable
|
|
58
|
+
disabled={disabled}
|
|
59
|
+
className={cn(
|
|
60
|
+
"flex-col items-start justify-center gap-4 rounded-lg border border-gray-stroke-muted p-4",
|
|
61
|
+
disabled ? "opacity-50" : "",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
>
|
|
66
|
+
{typeof children === "string" ? (
|
|
67
|
+
<Text className={cn(labelTextVariants({ variant, size, disabled }))}>
|
|
68
|
+
{children}
|
|
69
|
+
</Text>
|
|
70
|
+
) : (
|
|
71
|
+
children
|
|
72
|
+
)}
|
|
73
|
+
</Pressable>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Pressable
|
|
79
|
+
disabled={disabled}
|
|
80
|
+
className={cn(labelVariants({ variant, size }), className)}
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
{typeof children === "string" ? (
|
|
84
|
+
<Text className={cn(labelTextVariants({ variant, size, disabled }))}>
|
|
85
|
+
{children}
|
|
86
|
+
</Text>
|
|
87
|
+
) : (
|
|
88
|
+
children
|
|
89
|
+
)}
|
|
90
|
+
</Pressable>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { Label, labelVariants, labelTextVariants };
|
|
95
|
+
export type { LabelProps };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import Svg, { Path } from "react-native-svg";
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withRepeat,
|
|
7
|
+
withTiming,
|
|
8
|
+
cancelAnimation,
|
|
9
|
+
Easing,
|
|
10
|
+
} from "react-native-reanimated";
|
|
11
|
+
import { cn } from "@/lib/utils";
|
|
12
|
+
|
|
13
|
+
interface LoadingProps {
|
|
14
|
+
size?: number;
|
|
15
|
+
className?: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Loading({ size = 24, color = "currentColor", className }: LoadingProps) {
|
|
20
|
+
const rotation = useSharedValue(0);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
rotation.value = withRepeat(
|
|
24
|
+
withTiming(360, { duration: 1000, easing: Easing.linear }),
|
|
25
|
+
-1,
|
|
26
|
+
false
|
|
27
|
+
);
|
|
28
|
+
return () => cancelAnimation(rotation);
|
|
29
|
+
}, [rotation]);
|
|
30
|
+
|
|
31
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
32
|
+
transform: [{ rotate: `${rotation.value}deg` }],
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Animated.View style={animatedStyle} className={cn(className)}>
|
|
37
|
+
<Svg
|
|
38
|
+
width={size}
|
|
39
|
+
height={size}
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
fill="none"
|
|
42
|
+
stroke={color}
|
|
43
|
+
strokeWidth={2}
|
|
44
|
+
strokeLinecap="round"
|
|
45
|
+
strokeLinejoin="round"
|
|
46
|
+
>
|
|
47
|
+
<Path d="M21 12a9 9 0 1 1-6.219-8.56" />
|
|
48
|
+
</Svg>
|
|
49
|
+
</Animated.View>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { Loading };
|
|
54
|
+
export type { LoadingProps };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as PopoverPrimitive from "@rn-primitives/popover";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
interface PopoverContentProps extends PopoverPrimitive.ContentProps {
|
|
5
|
+
sideOffset?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function Popover({ ...props }: PopoverPrimitive.RootProps) {
|
|
9
|
+
return <PopoverPrimitive.Root {...props} />;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function PopoverTrigger({ ...props }: PopoverPrimitive.TriggerProps) {
|
|
13
|
+
return <PopoverPrimitive.Trigger {...props} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function PopoverClose({ ...props }: PopoverPrimitive.CloseProps) {
|
|
17
|
+
return <PopoverPrimitive.Close {...props} />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function PopoverContent({
|
|
21
|
+
className,
|
|
22
|
+
align = "center",
|
|
23
|
+
sideOffset = 4,
|
|
24
|
+
children,
|
|
25
|
+
...props
|
|
26
|
+
}: PopoverContentProps) {
|
|
27
|
+
return (
|
|
28
|
+
<PopoverPrimitive.Portal>
|
|
29
|
+
<PopoverPrimitive.Overlay className="absolute inset-0" />
|
|
30
|
+
<PopoverPrimitive.Content
|
|
31
|
+
align={align}
|
|
32
|
+
sideOffset={sideOffset}
|
|
33
|
+
className={cn(
|
|
34
|
+
"z-50 w-72 rounded-md border border-gray-background-fill bg-surface-card p-4 shadow-md",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</PopoverPrimitive.Content>
|
|
41
|
+
</PopoverPrimitive.Portal>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { Popover, PopoverClose, PopoverContent, PopoverTrigger };
|
|
46
|
+
export type { PopoverContentProps };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { View } from "react-native";
|
|
2
|
+
import * as RadioGroupPrimitive from "@rn-primitives/radio-group";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
interface RadioGroupItemProps
|
|
7
|
+
extends RadioGroupPrimitive.ItemProps,
|
|
8
|
+
VariantProps<typeof radioGroupVariants> {}
|
|
9
|
+
|
|
10
|
+
const INNER_DOT_SIZES: Record<"sm" | "md" | "lg", number> = {
|
|
11
|
+
sm: 8,
|
|
12
|
+
md: 10,
|
|
13
|
+
lg: 14,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const radioGroupVariants = cva(
|
|
17
|
+
"items-center justify-center rounded-full border-2 shrink-0 bg-surface-card border-gray-stroke-muted",
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
size: {
|
|
21
|
+
sm: "h-4 w-4",
|
|
22
|
+
md: "h-5 w-5",
|
|
23
|
+
lg: "h-6 w-6",
|
|
24
|
+
},
|
|
25
|
+
variant: {
|
|
26
|
+
regular: "",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
size: "md",
|
|
31
|
+
variant: "regular",
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
function RadioGroup({ className, ...props }: RadioGroupPrimitive.RootProps) {
|
|
37
|
+
return (
|
|
38
|
+
<RadioGroupPrimitive.Root className={cn("gap-3", className)} {...props} />
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function RadioGroupItem({ className, size = "md", variant, ...props }: RadioGroupItemProps) {
|
|
43
|
+
const resolvedSize = size ?? "md";
|
|
44
|
+
const dotSize = INNER_DOT_SIZES[resolvedSize];
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<RadioGroupPrimitive.Item
|
|
48
|
+
className={cn(radioGroupVariants({ size, variant }), className)}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
<RadioGroupPrimitive.Indicator className="items-center justify-center">
|
|
52
|
+
<View
|
|
53
|
+
style={{ width: dotSize, height: dotSize, borderRadius: dotSize / 2 }}
|
|
54
|
+
className="bg-primary-background-fill"
|
|
55
|
+
/>
|
|
56
|
+
</RadioGroupPrimitive.Indicator>
|
|
57
|
+
</RadioGroupPrimitive.Item>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { RadioGroup, RadioGroupItem, radioGroupVariants };
|
|
62
|
+
export type { RadioGroupItemProps };
|