@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.
Files changed (155) hide show
  1. package/README.md +153 -0
  2. package/lib/commonjs/components/Alert.js +206 -0
  3. package/lib/commonjs/components/Alert.js.map +1 -0
  4. package/lib/commonjs/components/AlertDialog.js +273 -0
  5. package/lib/commonjs/components/AlertDialog.js.map +1 -0
  6. package/lib/commonjs/components/Badge.js +94 -0
  7. package/lib/commonjs/components/Badge.js.map +1 -0
  8. package/lib/commonjs/components/Button.js +181 -0
  9. package/lib/commonjs/components/Button.js.map +1 -0
  10. package/lib/commonjs/components/Checkbox.js +91 -0
  11. package/lib/commonjs/components/Checkbox.js.map +1 -0
  12. package/lib/commonjs/components/Dialog.js +164 -0
  13. package/lib/commonjs/components/Dialog.js.map +1 -0
  14. package/lib/commonjs/components/Input.js +364 -0
  15. package/lib/commonjs/components/Input.js.map +1 -0
  16. package/lib/commonjs/components/Label.js +90 -0
  17. package/lib/commonjs/components/Label.js.map +1 -0
  18. package/lib/commonjs/components/Loading.js +49 -0
  19. package/lib/commonjs/components/Loading.js.map +1 -0
  20. package/lib/commonjs/components/Popover.js +54 -0
  21. package/lib/commonjs/components/Popover.js.map +1 -0
  22. package/lib/commonjs/components/RadioGroup.js +72 -0
  23. package/lib/commonjs/components/RadioGroup.js.map +1 -0
  24. package/lib/commonjs/components/Select.js +234 -0
  25. package/lib/commonjs/components/Select.js.map +1 -0
  26. package/lib/commonjs/components/Separator.js +22 -0
  27. package/lib/commonjs/components/Separator.js.map +1 -0
  28. package/lib/commonjs/components/Sheet.js +148 -0
  29. package/lib/commonjs/components/Sheet.js.map +1 -0
  30. package/lib/commonjs/components/Switch.js +99 -0
  31. package/lib/commonjs/components/Switch.js.map +1 -0
  32. package/lib/commonjs/components/Tag.js +132 -0
  33. package/lib/commonjs/components/Tag.js.map +1 -0
  34. package/lib/commonjs/components/Toast.js +244 -0
  35. package/lib/commonjs/components/Toast.js.map +1 -0
  36. package/lib/commonjs/components/Tooltip.js +54 -0
  37. package/lib/commonjs/components/Tooltip.js.map +1 -0
  38. package/lib/commonjs/hooks/use-toast.js +136 -0
  39. package/lib/commonjs/hooks/use-toast.js.map +1 -0
  40. package/lib/commonjs/index.js +667 -0
  41. package/lib/commonjs/index.js.map +1 -0
  42. package/lib/commonjs/lib/utils.js +22 -0
  43. package/lib/commonjs/lib/utils.js.map +1 -0
  44. package/lib/commonjs/nativewind-env.d.js +3 -0
  45. package/lib/commonjs/nativewind-env.d.js.map +1 -0
  46. package/lib/module/components/Alert.js +195 -0
  47. package/lib/module/components/Alert.js.map +1 -0
  48. package/lib/module/components/AlertDialog.js +257 -0
  49. package/lib/module/components/AlertDialog.js.map +1 -0
  50. package/lib/module/components/Badge.js +90 -0
  51. package/lib/module/components/Badge.js.map +1 -0
  52. package/lib/module/components/Button.js +177 -0
  53. package/lib/module/components/Button.js.map +1 -0
  54. package/lib/module/components/Checkbox.js +86 -0
  55. package/lib/module/components/Checkbox.js.map +1 -0
  56. package/lib/module/components/Dialog.js +150 -0
  57. package/lib/module/components/Dialog.js.map +1 -0
  58. package/lib/module/components/Input.js +349 -0
  59. package/lib/module/components/Input.js.map +1 -0
  60. package/lib/module/components/Label.js +86 -0
  61. package/lib/module/components/Label.js.map +1 -0
  62. package/lib/module/components/Loading.js +45 -0
  63. package/lib/module/components/Loading.js.map +1 -0
  64. package/lib/module/components/Popover.js +47 -0
  65. package/lib/module/components/Popover.js.map +1 -0
  66. package/lib/module/components/RadioGroup.js +66 -0
  67. package/lib/module/components/RadioGroup.js.map +1 -0
  68. package/lib/module/components/Select.js +217 -0
  69. package/lib/module/components/Select.js.map +1 -0
  70. package/lib/module/components/Separator.js +19 -0
  71. package/lib/module/components/Separator.js.map +1 -0
  72. package/lib/module/components/Sheet.js +136 -0
  73. package/lib/module/components/Sheet.js.map +1 -0
  74. package/lib/module/components/Switch.js +94 -0
  75. package/lib/module/components/Switch.js.map +1 -0
  76. package/lib/module/components/Tag.js +128 -0
  77. package/lib/module/components/Tag.js.map +1 -0
  78. package/lib/module/components/Toast.js +233 -0
  79. package/lib/module/components/Toast.js.map +1 -0
  80. package/lib/module/components/Tooltip.js +47 -0
  81. package/lib/module/components/Tooltip.js.map +1 -0
  82. package/lib/module/hooks/use-toast.js +130 -0
  83. package/lib/module/hooks/use-toast.js.map +1 -0
  84. package/lib/module/index.js +41 -0
  85. package/lib/module/index.js.map +1 -0
  86. package/lib/module/lib/utils.js +18 -0
  87. package/lib/module/lib/utils.js.map +1 -0
  88. package/lib/module/nativewind-env.d.js +3 -0
  89. package/lib/module/nativewind-env.d.js.map +1 -0
  90. package/lib/module/package.json +1 -0
  91. package/lib/typescript/components/Alert.d.ts +36 -0
  92. package/lib/typescript/components/Alert.d.ts.map +1 -0
  93. package/lib/typescript/components/AlertDialog.d.ts +44 -0
  94. package/lib/typescript/components/AlertDialog.d.ts.map +1 -0
  95. package/lib/typescript/components/Badge.d.ts +17 -0
  96. package/lib/typescript/components/Badge.d.ts.map +1 -0
  97. package/lib/typescript/components/Button.d.ts +24 -0
  98. package/lib/typescript/components/Button.d.ts.map +1 -0
  99. package/lib/typescript/components/Checkbox.d.ts +20 -0
  100. package/lib/typescript/components/Checkbox.d.ts.map +1 -0
  101. package/lib/typescript/components/Dialog.d.ts +35 -0
  102. package/lib/typescript/components/Dialog.d.ts.map +1 -0
  103. package/lib/typescript/components/Input.d.ts +57 -0
  104. package/lib/typescript/components/Input.d.ts.map +1 -0
  105. package/lib/typescript/components/Label.d.ts +21 -0
  106. package/lib/typescript/components/Label.d.ts.map +1 -0
  107. package/lib/typescript/components/Loading.d.ts +9 -0
  108. package/lib/typescript/components/Loading.d.ts.map +1 -0
  109. package/lib/typescript/components/Popover.d.ts +11 -0
  110. package/lib/typescript/components/Popover.d.ts.map +1 -0
  111. package/lib/typescript/components/RadioGroup.d.ts +13 -0
  112. package/lib/typescript/components/RadioGroup.d.ts.map +1 -0
  113. package/lib/typescript/components/Select.d.ts +37 -0
  114. package/lib/typescript/components/Select.d.ts.map +1 -0
  115. package/lib/typescript/components/Separator.d.ts +9 -0
  116. package/lib/typescript/components/Separator.d.ts.map +1 -0
  117. package/lib/typescript/components/Sheet.d.ts +31 -0
  118. package/lib/typescript/components/Sheet.d.ts.map +1 -0
  119. package/lib/typescript/components/Switch.d.ts +18 -0
  120. package/lib/typescript/components/Switch.d.ts.map +1 -0
  121. package/lib/typescript/components/Tag.d.ts +17 -0
  122. package/lib/typescript/components/Tag.d.ts.map +1 -0
  123. package/lib/typescript/components/Toast.d.ts +42 -0
  124. package/lib/typescript/components/Toast.d.ts.map +1 -0
  125. package/lib/typescript/components/Tooltip.d.ts +14 -0
  126. package/lib/typescript/components/Tooltip.d.ts.map +1 -0
  127. package/lib/typescript/hooks/use-toast.d.ts +46 -0
  128. package/lib/typescript/hooks/use-toast.d.ts.map +1 -0
  129. package/lib/typescript/index.d.ts +38 -0
  130. package/lib/typescript/index.d.ts.map +1 -0
  131. package/lib/typescript/lib/utils.d.ts +3 -0
  132. package/lib/typescript/lib/utils.d.ts.map +1 -0
  133. package/package.json +111 -0
  134. package/src/components/Alert.tsx +185 -0
  135. package/src/components/AlertDialog.tsx +277 -0
  136. package/src/components/Badge.tsx +113 -0
  137. package/src/components/Button.tsx +224 -0
  138. package/src/components/Checkbox.tsx +106 -0
  139. package/src/components/Dialog.tsx +175 -0
  140. package/src/components/Input.tsx +439 -0
  141. package/src/components/Label.tsx +95 -0
  142. package/src/components/Loading.tsx +54 -0
  143. package/src/components/Popover.tsx +46 -0
  144. package/src/components/RadioGroup.tsx +62 -0
  145. package/src/components/Select.tsx +246 -0
  146. package/src/components/Separator.tsx +24 -0
  147. package/src/components/Sheet.tsx +165 -0
  148. package/src/components/Switch.tsx +138 -0
  149. package/src/components/Tag.tsx +116 -0
  150. package/src/components/Toast.tsx +265 -0
  151. package/src/components/Tooltip.tsx +49 -0
  152. package/src/hooks/use-toast.ts +144 -0
  153. package/src/index.ts +193 -0
  154. package/src/lib/utils.ts +63 -0
  155. 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 };