@medway-ui/native 1.6.1 → 1.7.0

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 (223) hide show
  1. package/README.md +27 -2
  2. package/lib/commonjs/components/Alert.js +3 -1
  3. package/lib/commonjs/components/Alert.js.map +1 -1
  4. package/lib/commonjs/components/AlertDialog.js +5 -2
  5. package/lib/commonjs/components/AlertDialog.js.map +1 -1
  6. package/lib/commonjs/components/Badge.js +37 -2
  7. package/lib/commonjs/components/Badge.js.map +1 -1
  8. package/lib/commonjs/components/Button.js +32 -1
  9. package/lib/commonjs/components/Button.js.map +1 -1
  10. package/lib/commonjs/components/Carousel.js +12 -2
  11. package/lib/commonjs/components/Carousel.js.map +1 -1
  12. package/lib/commonjs/components/Checkbox.js +2 -0
  13. package/lib/commonjs/components/Checkbox.js.map +1 -1
  14. package/lib/commonjs/components/Chip.js +208 -0
  15. package/lib/commonjs/components/Chip.js.map +1 -0
  16. package/lib/commonjs/components/Choice.js +116 -0
  17. package/lib/commonjs/components/Choice.js.map +1 -0
  18. package/lib/commonjs/components/Dialog.js +27 -16
  19. package/lib/commonjs/components/Dialog.js.map +1 -1
  20. package/lib/commonjs/components/DropdownMenu.js +73 -57
  21. package/lib/commonjs/components/DropdownMenu.js.map +1 -1
  22. package/lib/commonjs/components/EmptyState.js +9 -2
  23. package/lib/commonjs/components/EmptyState.js.map +1 -1
  24. package/lib/commonjs/components/Feed.js +7 -3
  25. package/lib/commonjs/components/Feed.js.map +1 -1
  26. package/lib/commonjs/components/Feedback.js +77 -0
  27. package/lib/commonjs/components/Feedback.js.map +1 -0
  28. package/lib/commonjs/components/Filter.js +93 -0
  29. package/lib/commonjs/components/Filter.js.map +1 -0
  30. package/lib/commonjs/components/IndexLevel.js +4 -3
  31. package/lib/commonjs/components/IndexLevel.js.map +1 -1
  32. package/lib/commonjs/components/Input.js +149 -45
  33. package/lib/commonjs/components/Input.js.map +1 -1
  34. package/lib/commonjs/components/InvitePopover.js +4 -1
  35. package/lib/commonjs/components/InvitePopover.js.map +1 -1
  36. package/lib/commonjs/components/MemberRow.js +3 -4
  37. package/lib/commonjs/components/MemberRow.js.map +1 -1
  38. package/lib/commonjs/components/PostForm.js +18 -8
  39. package/lib/commonjs/components/PostForm.js.map +1 -1
  40. package/lib/commonjs/components/PrizeCollection.js +1 -1
  41. package/lib/commonjs/components/RadioGroup.js +7 -0
  42. package/lib/commonjs/components/RadioGroup.js.map +1 -1
  43. package/lib/commonjs/components/Select.js +1 -1
  44. package/lib/commonjs/components/Sheet.js +4 -1
  45. package/lib/commonjs/components/Sheet.js.map +1 -1
  46. package/lib/commonjs/components/Skeleton.js +55 -4
  47. package/lib/commonjs/components/Skeleton.js.map +1 -1
  48. package/lib/commonjs/components/Switch.js +2 -0
  49. package/lib/commonjs/components/Switch.js.map +1 -1
  50. package/lib/commonjs/components/Tabs.js +2 -0
  51. package/lib/commonjs/components/Tabs.js.map +1 -1
  52. package/lib/commonjs/components/Toast.js +355 -79
  53. package/lib/commonjs/components/Toast.js.map +1 -1
  54. package/lib/commonjs/editor/NoteEditor.js +36 -11
  55. package/lib/commonjs/editor/NoteEditor.js.map +1 -1
  56. package/lib/commonjs/editor/editorHtml.generated.js +1 -1
  57. package/lib/commonjs/editor/editorHtml.generated.js.map +1 -1
  58. package/lib/commonjs/hooks/useThemeColor.js +693 -65
  59. package/lib/commonjs/hooks/useThemeColor.js.map +1 -1
  60. package/lib/commonjs/hooks/useTrackedText.js +21 -0
  61. package/lib/commonjs/hooks/useTrackedText.js.map +1 -0
  62. package/lib/commonjs/index.js +46 -0
  63. package/lib/commonjs/index.js.map +1 -1
  64. package/lib/commonjs/lib/optional-deps.js +20 -0
  65. package/lib/commonjs/lib/optional-deps.js.map +1 -0
  66. package/lib/commonjs/lib/touch-target.js +19 -0
  67. package/lib/commonjs/lib/touch-target.js.map +1 -0
  68. package/lib/module/components/Alert.js +3 -1
  69. package/lib/module/components/Alert.js.map +1 -1
  70. package/lib/module/components/AlertDialog.js +5 -2
  71. package/lib/module/components/AlertDialog.js.map +1 -1
  72. package/lib/module/components/Badge.js +36 -2
  73. package/lib/module/components/Badge.js.map +1 -1
  74. package/lib/module/components/Button.js +32 -1
  75. package/lib/module/components/Button.js.map +1 -1
  76. package/lib/module/components/Carousel.js +12 -2
  77. package/lib/module/components/Carousel.js.map +1 -1
  78. package/lib/module/components/Checkbox.js +2 -0
  79. package/lib/module/components/Checkbox.js.map +1 -1
  80. package/lib/module/components/Chip.js +204 -0
  81. package/lib/module/components/Chip.js.map +1 -0
  82. package/lib/module/components/Choice.js +112 -0
  83. package/lib/module/components/Choice.js.map +1 -0
  84. package/lib/module/components/Dialog.js +28 -17
  85. package/lib/module/components/Dialog.js.map +1 -1
  86. package/lib/module/components/DropdownMenu.js +74 -58
  87. package/lib/module/components/DropdownMenu.js.map +1 -1
  88. package/lib/module/components/EmptyState.js +9 -2
  89. package/lib/module/components/EmptyState.js.map +1 -1
  90. package/lib/module/components/Feed.js +7 -3
  91. package/lib/module/components/Feed.js.map +1 -1
  92. package/lib/module/components/Feedback.js +74 -0
  93. package/lib/module/components/Feedback.js.map +1 -0
  94. package/lib/module/components/Filter.js +89 -0
  95. package/lib/module/components/Filter.js.map +1 -0
  96. package/lib/module/components/IndexLevel.js +4 -3
  97. package/lib/module/components/IndexLevel.js.map +1 -1
  98. package/lib/module/components/Input.js +149 -45
  99. package/lib/module/components/Input.js.map +1 -1
  100. package/lib/module/components/InvitePopover.js +4 -1
  101. package/lib/module/components/InvitePopover.js.map +1 -1
  102. package/lib/module/components/MemberRow.js +3 -4
  103. package/lib/module/components/MemberRow.js.map +1 -1
  104. package/lib/module/components/PostForm.js +18 -8
  105. package/lib/module/components/PostForm.js.map +1 -1
  106. package/lib/module/components/PrizeCollection.js +1 -1
  107. package/lib/module/components/RadioGroup.js +7 -0
  108. package/lib/module/components/RadioGroup.js.map +1 -1
  109. package/lib/module/components/Select.js +1 -1
  110. package/lib/module/components/Sheet.js +4 -1
  111. package/lib/module/components/Sheet.js.map +1 -1
  112. package/lib/module/components/Skeleton.js +56 -5
  113. package/lib/module/components/Skeleton.js.map +1 -1
  114. package/lib/module/components/Switch.js +2 -0
  115. package/lib/module/components/Switch.js.map +1 -1
  116. package/lib/module/components/Tabs.js +2 -0
  117. package/lib/module/components/Tabs.js.map +1 -1
  118. package/lib/module/components/Toast.js +360 -84
  119. package/lib/module/components/Toast.js.map +1 -1
  120. package/lib/module/editor/NoteEditor.js +36 -11
  121. package/lib/module/editor/NoteEditor.js.map +1 -1
  122. package/lib/module/editor/editorHtml.generated.js +1 -1
  123. package/lib/module/editor/editorHtml.generated.js.map +1 -1
  124. package/lib/module/hooks/useThemeColor.js +693 -65
  125. package/lib/module/hooks/useThemeColor.js.map +1 -1
  126. package/lib/module/hooks/useTrackedText.js +18 -0
  127. package/lib/module/hooks/useTrackedText.js.map +1 -0
  128. package/lib/module/index.js +8 -0
  129. package/lib/module/index.js.map +1 -1
  130. package/lib/module/lib/optional-deps.js +16 -0
  131. package/lib/module/lib/optional-deps.js.map +1 -0
  132. package/lib/module/lib/touch-target.js +15 -0
  133. package/lib/module/lib/touch-target.js.map +1 -0
  134. package/lib/typescript/components/Alert.d.ts.map +1 -1
  135. package/lib/typescript/components/AlertDialog.d.ts.map +1 -1
  136. package/lib/typescript/components/Badge.d.ts +1 -1
  137. package/lib/typescript/components/Badge.d.ts.map +1 -1
  138. package/lib/typescript/components/Button.d.ts.map +1 -1
  139. package/lib/typescript/components/Carousel.d.ts +1 -1
  140. package/lib/typescript/components/Carousel.d.ts.map +1 -1
  141. package/lib/typescript/components/Checkbox.d.ts.map +1 -1
  142. package/lib/typescript/components/Chip.d.ts +30 -0
  143. package/lib/typescript/components/Chip.d.ts.map +1 -0
  144. package/lib/typescript/components/Choice.d.ts +17 -0
  145. package/lib/typescript/components/Choice.d.ts.map +1 -0
  146. package/lib/typescript/components/Dialog.d.ts.map +1 -1
  147. package/lib/typescript/components/DropdownMenu.d.ts +0 -7
  148. package/lib/typescript/components/DropdownMenu.d.ts.map +1 -1
  149. package/lib/typescript/components/EmptyState.d.ts +3 -2
  150. package/lib/typescript/components/EmptyState.d.ts.map +1 -1
  151. package/lib/typescript/components/Feed.d.ts.map +1 -1
  152. package/lib/typescript/components/Feedback.d.ts +17 -0
  153. package/lib/typescript/components/Feedback.d.ts.map +1 -0
  154. package/lib/typescript/components/Filter.d.ts +17 -0
  155. package/lib/typescript/components/Filter.d.ts.map +1 -0
  156. package/lib/typescript/components/IndexLevel.d.ts.map +1 -1
  157. package/lib/typescript/components/Input.d.ts +3 -3
  158. package/lib/typescript/components/Input.d.ts.map +1 -1
  159. package/lib/typescript/components/InvitePopover.d.ts.map +1 -1
  160. package/lib/typescript/components/MemberRow.d.ts.map +1 -1
  161. package/lib/typescript/components/PostForm.d.ts.map +1 -1
  162. package/lib/typescript/components/RadioGroup.d.ts.map +1 -1
  163. package/lib/typescript/components/Sheet.d.ts.map +1 -1
  164. package/lib/typescript/components/Skeleton.d.ts +1 -1
  165. package/lib/typescript/components/Skeleton.d.ts.map +1 -1
  166. package/lib/typescript/components/Switch.d.ts.map +1 -1
  167. package/lib/typescript/components/Tabs.d.ts.map +1 -1
  168. package/lib/typescript/components/Tag.d.ts +2 -2
  169. package/lib/typescript/components/ThemedIcon.d.ts +1 -1
  170. package/lib/typescript/components/ThemedIcon.d.ts.map +1 -1
  171. package/lib/typescript/components/Toast.d.ts +24 -13
  172. package/lib/typescript/components/Toast.d.ts.map +1 -1
  173. package/lib/typescript/editor/NoteEditor.d.ts.map +1 -1
  174. package/lib/typescript/editor/editorHtml.generated.d.ts.map +1 -1
  175. package/lib/typescript/hooks/useThemeColor.d.ts +0 -10
  176. package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -1
  177. package/lib/typescript/hooks/useTrackedText.d.ts +6 -0
  178. package/lib/typescript/hooks/useTrackedText.d.ts.map +1 -0
  179. package/lib/typescript/index.d.ts +9 -1
  180. package/lib/typescript/index.d.ts.map +1 -1
  181. package/lib/typescript/lib/optional-deps.d.ts +23 -0
  182. package/lib/typescript/lib/optional-deps.d.ts.map +1 -0
  183. package/lib/typescript/lib/touch-target.d.ts +5 -0
  184. package/lib/typescript/lib/touch-target.d.ts.map +1 -0
  185. package/package.json +6 -1
  186. package/setup/createMedwayUiNavigator.tsx +4 -7
  187. package/setup/metro.js +12 -3
  188. package/src/components/Alert.tsx +3 -1
  189. package/src/components/AlertDialog.tsx +8 -3
  190. package/src/components/Badge.tsx +51 -3
  191. package/src/components/Button.tsx +39 -1
  192. package/src/components/Carousel.tsx +17 -3
  193. package/src/components/Checkbox.tsx +2 -0
  194. package/src/components/Chip.tsx +227 -0
  195. package/src/components/Choice.tsx +139 -0
  196. package/src/components/Dialog.tsx +39 -23
  197. package/src/components/DropdownMenu.tsx +99 -87
  198. package/src/components/EmptyState.tsx +18 -3
  199. package/src/components/Feed.tsx +4 -3
  200. package/src/components/Feedback.tsx +111 -0
  201. package/src/components/Filter.tsx +106 -0
  202. package/src/components/IndexLevel.tsx +8 -10
  203. package/src/components/Input.tsx +142 -54
  204. package/src/components/InvitePopover.tsx +5 -1
  205. package/src/components/MemberRow.tsx +3 -5
  206. package/src/components/PostForm.tsx +16 -9
  207. package/src/components/PrizeCollection.tsx +1 -1
  208. package/src/components/RadioGroup.tsx +8 -0
  209. package/src/components/Select.tsx +1 -1
  210. package/src/components/Sheet.tsx +4 -1
  211. package/src/components/Skeleton.tsx +47 -6
  212. package/src/components/Switch.tsx +2 -0
  213. package/src/components/Tabs.tsx +2 -0
  214. package/src/components/ThemedIcon.tsx +1 -1
  215. package/src/components/Toast.tsx +489 -87
  216. package/src/editor/NoteEditor.tsx +47 -10
  217. package/src/editor/editorHtml.generated.ts +1 -1
  218. package/src/hooks/useThemeColor.ts +757 -64
  219. package/src/hooks/useTrackedText.ts +20 -0
  220. package/src/index.ts +36 -1
  221. package/src/lib/optional-deps.ts +27 -0
  222. package/src/lib/touch-target.ts +12 -0
  223. package/setup/index.tsx +0 -42
@@ -32,6 +32,7 @@ import {
32
32
  } from "@medway-ui/icons/native";
33
33
  import { Text, type TextComponentProps } from "@/components/Text";
34
34
  import { useThemeColor } from "@/hooks/useThemeColor";
35
+ import { useTrackedText } from "@/hooks/useTrackedText";
35
36
  import { fontFamilyMedium } from "@/lib/typography";
36
37
  import { cn } from "@/lib/utils";
37
38
 
@@ -72,6 +73,9 @@ export interface InputNumericProps
72
73
  label?: string;
73
74
  }
74
75
 
76
+ type FocusHandler = TextInputProps["onFocus"];
77
+ type BlurHandler = TextInputProps["onBlur"];
78
+
75
79
  export interface InputLabelProps {
76
80
  children?: ReactNode;
77
81
  floated?: boolean;
@@ -213,11 +217,38 @@ function applyInputIconSize(
213
217
  });
214
218
  }
215
219
 
216
- function InputPassword({ className, size = "lg", invalid = false, label, ...props }: InputPasswordProps) {
217
- const [visible, setVisible] = useState(false);
220
+ function useInputFocus(onFocus?: FocusHandler, onBlur?: BlurHandler) {
218
221
  const [focused, setFocused] = useState(false);
219
- const hasValue = Boolean(props.value || props.defaultValue);
220
- const isFloated = focused || hasValue;
222
+
223
+ const handleFocus: NonNullable<FocusHandler> = (event) => {
224
+ setFocused(true);
225
+ onFocus?.(event);
226
+ };
227
+
228
+ const handleBlur: NonNullable<BlurHandler> = (event) => {
229
+ setFocused(false);
230
+ onBlur?.(event);
231
+ };
232
+
233
+ return { focused, handleFocus, handleBlur };
234
+ }
235
+
236
+ function InputPassword({
237
+ className,
238
+ size = "lg",
239
+ invalid = false,
240
+ label,
241
+ value,
242
+ defaultValue,
243
+ onFocus,
244
+ onBlur,
245
+ onChangeText,
246
+ ...props
247
+ }: InputPasswordProps) {
248
+ const [visible, setVisible] = useState(false);
249
+ const { focused, handleFocus, handleBlur } = useInputFocus(onFocus, onBlur);
250
+ const { text, handleChangeText } = useTrackedText(value, defaultValue, onChangeText);
251
+ const isFloated = focused || text !== "";
221
252
  const iconColor = useThemeColor("gray-foreground");
222
253
  const iconSize = size === "md" ? INPUT_ICON_SIZE_MD : INPUT_ICON_SIZE_LG;
223
254
 
@@ -242,10 +273,13 @@ function InputPassword({ className, size = "lg", invalid = false, label, ...prop
242
273
  "pl-12 pr-12 font-medium",
243
274
  className
244
275
  )}
245
- onFocus={() => setFocused(true)}
246
- onBlur={() => setFocused(false)}
276
+ value={value}
277
+ defaultValue={defaultValue}
247
278
  accessibilityLabel={label}
248
279
  {...props}
280
+ onFocus={handleFocus}
281
+ onBlur={handleBlur}
282
+ onChangeText={handleChangeText}
249
283
  style={[{ fontFamily: fontFamilyMedium }, props.style]}
250
284
  />
251
285
  </View>
@@ -253,7 +287,9 @@ function InputPassword({ className, size = "lg", invalid = false, label, ...prop
253
287
  <Pressable
254
288
  onPress={() => setVisible((v) => !v)}
255
289
  disabled={props.editable === false}
256
- accessibilityLabel={visible ? "Hide password" : "Show password"}
290
+ accessibilityRole="button"
291
+ accessibilityLabel={visible ? "Ocultar senha" : "Mostrar senha"}
292
+ hitSlop={10}
257
293
  >
258
294
  {iconColor && (visible ? (
259
295
  <Hide width={iconSize} height={iconSize} strokeWidth={INPUT_ICON_STROKE_WIDTH} color={iconColor} />
@@ -278,26 +314,28 @@ function InputNumeric({
278
314
  editable = true,
279
315
  allowNegative = false,
280
316
  label,
317
+ onFocus,
318
+ onBlur,
281
319
  ...props
282
320
  }: InputNumericProps) {
283
321
  const [internalValue, setInternalValue] = useState(String(defaultValue ?? ""));
284
322
  const isControlled = value !== undefined;
285
323
  const displayValue = isControlled ? String(value ?? "") : internalValue;
286
- const [focused, setFocused] = useState(false);
324
+ const { focused, handleFocus, handleBlur } = useInputFocus(onFocus, onBlur);
287
325
  const isFloated = focused || displayValue !== "";
288
326
  const iconColor = useThemeColor("gray-foreground");
289
327
 
290
328
  const numericValue = parseFloat(displayValue.replace(",", ".")) || 0;
329
+ const lowerBound = Math.max(min ?? -Infinity, allowNegative ? -Infinity : 0);
291
330
 
292
331
  const handleStep = useCallback(
293
332
  (delta: number) => {
294
- let next = numericValue + delta;
295
- if (min !== undefined && next < min) next = min;
333
+ const next = Math.max(numericValue + delta, lowerBound);
296
334
  const nextStr = String(next);
297
335
  if (!isControlled) setInternalValue(nextStr);
298
336
  onChangeText?.(nextStr);
299
337
  },
300
- [numericValue, min, isControlled, onChangeText]
338
+ [numericValue, lowerBound, isControlled, onChangeText]
301
339
  );
302
340
 
303
341
  const chevronSize = size === "md" ? CHEVRON_SIZE_MD : CHEVRON_SIZE_LG;
@@ -322,8 +360,6 @@ function InputNumeric({
322
360
  onChangeText?.(cleaned);
323
361
  }}
324
362
  editable={editable}
325
- onFocus={() => setFocused(true)}
326
- onBlur={() => setFocused(false)}
327
363
  className={cn(
328
364
  inputVariants({ size, invalid, focused, disabled: !editable }),
329
365
  iconLeft ? "pl-12" : "",
@@ -332,6 +368,8 @@ function InputNumeric({
332
368
  )}
333
369
  accessibilityLabel={label}
334
370
  {...props}
371
+ onFocus={handleFocus}
372
+ onBlur={handleBlur}
335
373
  style={[{ fontFamily: fontFamilyMedium }, props.style]}
336
374
  />
337
375
  </View>
@@ -341,7 +379,9 @@ function InputNumeric({
341
379
  disabled={!editable}
342
380
  onPress={() => handleStep(1)}
343
381
  className="items-center justify-center p-1"
344
- accessibilityLabel="Increase value"
382
+ accessibilityRole="button"
383
+ accessibilityLabel="Aumentar valor"
384
+ hitSlop={{ top: 12, bottom: 4, left: 16, right: 16 }}
345
385
  >
346
386
  {iconColor && (
347
387
  <ChevronDown
@@ -357,7 +397,9 @@ function InputNumeric({
357
397
  disabled={!editable}
358
398
  onPress={() => handleStep(-1)}
359
399
  className="items-center justify-center p-1"
360
- accessibilityLabel="Decrease value"
400
+ accessibilityRole="button"
401
+ accessibilityLabel="Diminuir valor"
402
+ hitSlop={{ top: 4, bottom: 12, left: 16, right: 16 }}
361
403
  >
362
404
  {iconColor && (
363
405
  <ChevronDown width={chevronSize} height={chevronSize} strokeWidth={3} color={iconColor} />
@@ -369,7 +411,7 @@ function InputNumeric({
369
411
  );
370
412
  }
371
413
 
372
- function Input({
414
+ function InputTextual({
373
415
  className,
374
416
  type = "text",
375
417
  size = "lg",
@@ -380,45 +422,17 @@ function Input({
380
422
  value,
381
423
  defaultValue,
382
424
  editable = true,
425
+ onFocus,
426
+ onBlur,
427
+ onChangeText,
383
428
  ...props
384
429
  }: InputProps) {
385
- const [focused, setFocused] = useState(false);
386
- const hasValue = Boolean(value || defaultValue);
387
- const isFloated = focused || hasValue;
430
+ const { focused, handleFocus, handleBlur } = useInputFocus(onFocus, onBlur);
431
+ const { text, handleChangeText } = useTrackedText(value, defaultValue, onChangeText);
432
+ const isFloated = focused || text !== "";
388
433
  const iconColor = useThemeColor("gray-foreground");
389
434
  const errorIconColor = useThemeColor("feedback-error-foreground");
390
435
 
391
- if (type === "numeric") {
392
- return (
393
- <InputNumeric
394
- className={className}
395
- size={size}
396
- invalid={invalid}
397
- iconLeft={iconLeft}
398
- value={value as string}
399
- defaultValue={defaultValue as string}
400
- editable={editable}
401
- label={label}
402
- {...props}
403
- />
404
- );
405
- }
406
-
407
- if (type === "password") {
408
- return (
409
- <InputPassword
410
- className={className}
411
- size={size}
412
- invalid={invalid}
413
- value={value as string}
414
- defaultValue={defaultValue as string}
415
- editable={editable}
416
- label={label}
417
- {...props}
418
- />
419
- );
420
- }
421
-
422
436
  const iconSize = size === "md" ? INPUT_ICON_SIZE_MD : INPUT_ICON_SIZE_LG;
423
437
  const defaultIconLeft =
424
438
  type === "email" && iconColor ? (
@@ -448,11 +462,9 @@ function Input({
448
462
  {label && <InputLabel floated={isFloated} disabled={!editable}>{label}</InputLabel>}
449
463
  <TextInput
450
464
  keyboardType={type === "email" ? "email-address" : "default"}
451
- value={value as string}
452
- defaultValue={defaultValue as string}
465
+ value={value}
466
+ defaultValue={defaultValue}
453
467
  editable={editable}
454
- onFocus={() => setFocused(true)}
455
- onBlur={() => setFocused(false)}
456
468
  className={cn(
457
469
  inputVariants({ size, invalid, focused, disabled: !editable }),
458
470
  resolvedIconLeft ? "pl-12" : "",
@@ -462,6 +474,9 @@ function Input({
462
474
  )}
463
475
  accessibilityLabel={label}
464
476
  {...props}
477
+ onFocus={handleFocus}
478
+ onBlur={handleBlur}
479
+ onChangeText={handleChangeText}
465
480
  style={[{ fontFamily: fontFamilyMedium }, props.style]}
466
481
  />
467
482
  </View>
@@ -474,6 +489,79 @@ function Input({
474
489
  );
475
490
  }
476
491
 
492
+ function Input({
493
+ className,
494
+ type = "text",
495
+ size = "lg",
496
+ invalid = false,
497
+ iconLeft,
498
+ iconRight,
499
+ label,
500
+ value,
501
+ defaultValue,
502
+ editable = true,
503
+ onFocus,
504
+ onBlur,
505
+ onChangeText,
506
+ ...props
507
+ }: InputProps) {
508
+ if (type === "numeric") {
509
+ return (
510
+ <InputNumeric
511
+ className={className}
512
+ size={size}
513
+ invalid={invalid}
514
+ iconLeft={iconLeft}
515
+ value={value}
516
+ defaultValue={defaultValue}
517
+ editable={editable}
518
+ label={label}
519
+ onFocus={onFocus}
520
+ onBlur={onBlur}
521
+ onChangeText={onChangeText}
522
+ {...props}
523
+ />
524
+ );
525
+ }
526
+
527
+ if (type === "password") {
528
+ return (
529
+ <InputPassword
530
+ className={className}
531
+ size={size}
532
+ invalid={invalid}
533
+ value={value}
534
+ defaultValue={defaultValue}
535
+ editable={editable}
536
+ label={label}
537
+ onFocus={onFocus}
538
+ onBlur={onBlur}
539
+ onChangeText={onChangeText}
540
+ {...props}
541
+ />
542
+ );
543
+ }
544
+
545
+ return (
546
+ <InputTextual
547
+ className={className}
548
+ type={type}
549
+ size={size}
550
+ invalid={invalid}
551
+ iconLeft={iconLeft}
552
+ iconRight={iconRight}
553
+ label={label}
554
+ value={value}
555
+ defaultValue={defaultValue}
556
+ editable={editable}
557
+ onFocus={onFocus}
558
+ onBlur={onBlur}
559
+ onChangeText={onChangeText}
560
+ {...props}
561
+ />
562
+ );
563
+ }
564
+
477
565
  export {
478
566
  Input,
479
567
  InputAdornment,
@@ -8,6 +8,7 @@ import { CloseMd } from "@medway-ui/icons/native";
8
8
  import { Button } from "@/components/Button";
9
9
  import { Input } from "@/components/Input";
10
10
  import { Separator } from "@/components/Separator";
11
+ import { useThemeColor } from "@/hooks/useThemeColor";
11
12
  import { cn } from "@/lib/utils";
12
13
 
13
14
  interface InvitePopoverProps extends PopoverPrimitive.RootProps {
@@ -87,17 +88,20 @@ function InvitePopoverContent({
87
88
  }
88
89
 
89
90
  function InvitePopoverClose({ className, ...props }: PopoverPrimitive.CloseProps) {
91
+ const closeIconColor = useThemeColor("primary-foreground-onfill");
92
+
90
93
  return (
91
94
  <PopoverPrimitive.Close
92
95
  className={cn(
93
96
  "mb-4 size-12 items-center justify-center self-end rounded-pill bg-primary-background-fill active:bg-primary-background-fill-emphasis",
94
97
  className
95
98
  )}
99
+ accessibilityRole="button"
96
100
  accessibilityLabel="Fechar"
97
101
  {...props}
98
102
  >
99
103
  <View className="size-6">
100
- <CloseMd width={24} height={24} color="#FFFFFF" strokeWidth={2.2} />
104
+ <CloseMd width={24} height={24} color={closeIconColor} strokeWidth={2.2} />
101
105
  </View>
102
106
  </PopoverPrimitive.Close>
103
107
  );
@@ -13,7 +13,7 @@ const rankIndicatorVariants = cva("size-[22px] rounded-full", {
13
13
  rank: {
14
14
  1: "bg-yellow-background-fill",
15
15
  2: "bg-gray-background-fill",
16
- 3: "bg-warning-background-fill",
16
+ 3: "bg-bronze-background-fill",
17
17
  },
18
18
  },
19
19
  defaultVariants: {
@@ -95,16 +95,14 @@ function MemberRow({
95
95
  }: MemberRowProps) {
96
96
  const initials = avatarFallback ?? getInitials(name);
97
97
 
98
- if (variant === "selectable") {
99
- const selectionValue = (props as MemberRowSelectableProps).value;
100
-
98
+ if (variant === "selectable" && value !== undefined) {
101
99
  return (
102
100
  <View
103
101
  className={cn(memberRowVariants({ variant, highlighted }), className)}
104
102
  {...props}
105
103
  >
106
104
  <View className="min-w-0 flex-1 flex-row items-center gap-4">
107
- <RadioGroupItem value={selectionValue} size="md" />
105
+ <RadioGroupItem value={value} size="md" />
108
106
  <View className="min-w-0 flex-1 flex-row items-center gap-2">
109
107
  <Avatar size="sm" alt={avatarAlt ?? name}>
110
108
  {avatarSrc ? <AvatarImage src={avatarSrc} /> : null}
@@ -1,4 +1,4 @@
1
- import { type ReactElement, type ReactNode } from "react";
1
+ import { useState, type ReactElement, type ReactNode } from "react";
2
2
  import { Pressable, TextInput, View, type PressableProps, type TextInputProps, type ViewProps } from "react-native";
3
3
 
4
4
  import { Text } from "@/components/Text";
@@ -6,6 +6,7 @@ import { Text } from "@/components/Text";
6
6
  import { Button } from "@/components/Button";
7
7
  import { Checkbox } from "@/components/Checkbox";
8
8
  import { Input, InputText, inputVariants } from "@/components/Input";
9
+ import { useTrackedText } from "@/hooks/useTrackedText";
9
10
  import { cn } from "@/lib/utils";
10
11
 
11
12
  interface PostFormProps extends ViewProps {
@@ -124,7 +125,7 @@ function PostFormDescription({
124
125
  editable = true,
125
126
  ...props
126
127
  }: PostFormDescriptionProps) {
127
- const currentLength = (value ?? defaultValue ?? "").length;
128
+ const { text, handleChangeText } = useTrackedText(value, defaultValue, onChangeText);
128
129
 
129
130
  return (
130
131
  <View className={cn("gap-1", className)}>
@@ -134,7 +135,7 @@ function PostFormDescription({
134
135
  maxLength={maxLength}
135
136
  value={value}
136
137
  defaultValue={defaultValue}
137
- onChangeText={onChangeText}
138
+ onChangeText={handleChangeText}
138
139
  placeholder={placeholder}
139
140
  editable={editable}
140
141
  className={cn(
@@ -151,7 +152,7 @@ function PostFormDescription({
151
152
  style={[{ fontFamily: "MontserratMedium" }, props.style]}
152
153
  />
153
154
  <InputText className="text-right text-xs font-medium text-gray-foreground">
154
- {currentLength} / {maxLength}
155
+ {text.length} / {maxLength}
155
156
  </InputText>
156
157
  </View>
157
158
  );
@@ -196,11 +197,18 @@ function PostFormCheckboxOption({
196
197
  disabled,
197
198
  ...props
198
199
  }: PostFormCheckboxOptionProps) {
199
- const isChecked = checked ?? defaultChecked ?? false;
200
+ const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);
201
+ const isControlled = checked !== undefined;
202
+ const isChecked = isControlled ? checked : internalChecked;
203
+
204
+ const handleCheckedChange = (nextChecked: boolean) => {
205
+ if (!isControlled) setInternalChecked(nextChecked);
206
+ onCheckedChange?.(nextChecked);
207
+ };
200
208
 
201
209
  const toggle = () => {
202
210
  if (disabled) return;
203
- onCheckedChange?.(!isChecked);
211
+ handleCheckedChange(!isChecked);
204
212
  };
205
213
 
206
214
  return (
@@ -210,9 +218,8 @@ function PostFormCheckboxOption({
210
218
  >
211
219
  <Checkbox
212
220
  size="lg"
213
- checked={checked}
214
- defaultChecked={defaultChecked}
215
- onCheckedChange={onCheckedChange}
221
+ checked={isChecked}
222
+ onCheckedChange={handleCheckedChange}
216
223
  disabled={disabled}
217
224
  />
218
225
  <Pressable
@@ -37,7 +37,7 @@ interface PrizeItemProps extends ViewProps {
37
37
  const rankColors: Record<1 | 2 | 3, string> = {
38
38
  1: "bg-yellow-background-fill",
39
39
  2: "bg-gray-background-fill",
40
- 3: "bg-warning-background-fill",
40
+ 3: "bg-bronze-background-fill",
41
41
  };
42
42
 
43
43
  function PrizeCollection({
@@ -3,6 +3,7 @@ import * as RadioGroupPrimitive from "@rn-primitives/radio-group";
3
3
  import { cva, type VariantProps } from "class-variance-authority";
4
4
  import { type ReactNode } from "react";
5
5
  import { cn } from "@/lib/utils";
6
+ import { getMinTouchHitSlop } from "@/lib/touch-target";
6
7
 
7
8
  interface RadioGroupItemProps
8
9
  extends RadioGroupPrimitive.ItemProps,
@@ -14,6 +15,12 @@ const INNER_DOT_SIZES: Record<"sm" | "md" | "lg", number> = {
14
15
  lg: 14,
15
16
  };
16
17
 
18
+ const RADIO_DIMENSIONS: Record<"sm" | "md" | "lg", number> = {
19
+ sm: 16,
20
+ md: 20,
21
+ lg: 24,
22
+ };
23
+
17
24
  const radioGroupVariants = cva(
18
25
  "items-center justify-center rounded-full border-2 shrink-0 bg-surface-card border-gray-stroke-muted",
19
26
  {
@@ -46,6 +53,7 @@ function RadioGroupItem({ className, size = "md", variant, ...props }: RadioGrou
46
53
 
47
54
  return (
48
55
  <RadioGroupPrimitive.Item
56
+ hitSlop={getMinTouchHitSlop(RADIO_DIMENSIONS[resolvedSize])}
49
57
  className={cn(radioGroupVariants({ size, variant }), className)}
50
58
  {...props}
51
59
  >
@@ -114,7 +114,7 @@ function SelectValue({ className, placeholder, ...props }: SelectPrimitive.Value
114
114
  function SelectContent({ className, children, ...props }: SelectPrimitive.ContentProps) {
115
115
  return (
116
116
  <SelectPrimitive.Portal>
117
- <SelectPrimitive.Overlay className="absolute inset-0 bg-black/20">
117
+ <SelectPrimitive.Overlay className="absolute inset-0 bg-black-24">
118
118
  <SelectPrimitive.Content
119
119
  className={cn(
120
120
  "z-50 min-w-48 overflow-hidden rounded-md border border-gray-stroke-muted bg-surface-elevated shadow-lg",
@@ -68,7 +68,7 @@ function SheetContainer({
68
68
  return (
69
69
  <DialogPrimitive.Portal>
70
70
  <DialogPrimitive.Overlay
71
- className={cn("absolute inset-0 bg-black/50", overlayClassName)}
71
+ className={cn("absolute inset-0 bg-black-48", overlayClassName)}
72
72
  />
73
73
  <DialogPrimitive.Content
74
74
  className={cn(overlayPositionMap[side])}
@@ -84,6 +84,9 @@ function SheetContainer({
84
84
  {children}
85
85
  {showCloseButton && (
86
86
  <DialogPrimitive.Close
87
+ accessibilityRole="button"
88
+ accessibilityLabel="Fechar"
89
+ hitSlop={8}
87
90
  className={cn(
88
91
  closeButtonPositionMap[side],
89
92
  "rounded-pill p-1.5 active:bg-gray-background-soft"
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from "react";
2
- import { type ViewProps } from "react-native";
2
+ import { StyleSheet, View, type ViewProps } from "react-native";
3
3
  import Animated, {
4
4
  cancelAnimation,
5
5
  Easing,
@@ -16,8 +16,41 @@ interface SkeletonProps extends ViewProps {
16
16
  className?: string;
17
17
  }
18
18
 
19
- function Skeleton({ className, style, ...props }: SkeletonProps) {
19
+ // `bg-*` after any variant (`dark:`, `data-[state=on]:`, `[&>*]:`), with or
20
+ // without the important modifier (`!bg-*`).
21
+ const BACKGROUND_CLASS = /(?:^|:)!?bg-/;
22
+
23
+ /** Background classes paint the pulsing layer; the rest shape the box. */
24
+ function splitClassName(className: string | undefined) {
25
+ const classes = className?.split(/\s+/).filter(Boolean) ?? [];
26
+
27
+ return {
28
+ box: classes.filter((name) => !BACKGROUND_CLASS.test(name)).join(" "),
29
+ background: classes.filter((name) => BACKGROUND_CLASS.test(name)).join(" "),
30
+ };
31
+ }
32
+
33
+ /** Same split for `style`: `backgroundColor` paints the pulsing layer. */
34
+ function splitStyle(style: ViewProps["style"]) {
35
+ const flat = StyleSheet.flatten(style);
36
+ if (flat?.backgroundColor === undefined) {
37
+ return { box: style, background: undefined };
38
+ }
39
+
40
+ const { backgroundColor, ...box } = flat;
41
+ return { box, background: { backgroundColor } };
42
+ }
43
+
44
+ // NativeWind only maps `className` on registered components, and a Reanimated
45
+ // view isn't one: the classes were dropped and the skeleton had no size or
46
+ // color. Registering it doesn't help either, since NativeWind then merges the
47
+ // classes and the animated style into one object that Reanimated reads as the
48
+ // animated style alone. So the classes stay on plain views and only the pulse
49
+ // runs on the Reanimated one.
50
+ function Skeleton({ className, style, children, ...props }: SkeletonProps) {
20
51
  const shimmer = useSharedValue(0);
52
+ const classes = splitClassName(className);
53
+ const styles = splitStyle(style);
21
54
 
22
55
  useEffect(() => {
23
56
  shimmer.value = withRepeat(
@@ -33,11 +66,19 @@ function Skeleton({ className, style, ...props }: SkeletonProps) {
33
66
  }));
34
67
 
35
68
  return (
36
- <Animated.View
37
- style={[animatedStyle, style]}
38
- className={cn("rounded-md bg-gray-150", className)}
69
+ <View
70
+ style={styles.box}
71
+ className={cn("overflow-hidden rounded-md", classes.box)}
39
72
  {...props}
40
- />
73
+ >
74
+ <Animated.View style={[StyleSheet.absoluteFill, animatedStyle]}>
75
+ <View
76
+ style={styles.background}
77
+ className={cn("flex-1 bg-gray-150", classes.background)}
78
+ />
79
+ </Animated.View>
80
+ {children}
81
+ </View>
41
82
  );
42
83
  }
43
84
 
@@ -9,6 +9,7 @@ import Animated, {
9
9
  } from "react-native-reanimated";
10
10
  import { cva, type VariantProps } from "class-variance-authority";
11
11
  import { cn } from "@/lib/utils";
12
+ import { getMinTouchHitSlop } from "@/lib/touch-target";
12
13
  import { useThemeColor } from "@/hooks/useThemeColor";
13
14
 
14
15
  interface SwitchProps
@@ -141,6 +142,7 @@ function Switch({
141
142
  checked={isChecked}
142
143
  onCheckedChange={handleCheckedChange}
143
144
  disabled={disabled}
145
+ hitSlop={getMinTouchHitSlop(dimensions.width, dimensions.height)}
144
146
  style={(state) => [
145
147
  rootStyle,
146
148
  typeof style === "function" ? style(state) : style,
@@ -118,10 +118,12 @@ function TabsTrigger({
118
118
  if (variant === "filter") {
119
119
  const hasLabel = typeof children === "string" && children.length > 0;
120
120
  const showLabel = isActive && hasLabel;
121
+ const accessibilityLabel = props.accessibilityLabel ?? (hasLabel ? children : undefined);
121
122
 
122
123
  return (
123
124
  <TabsPrimitive.Trigger asChild value={value} {...props}>
124
125
  <Pressable
126
+ accessibilityLabel={accessibilityLabel}
125
127
  className={cn(
126
128
  "relative min-h-12 flex-1 flex-row items-center justify-center gap-3 py-3",
127
129
  className
@@ -5,7 +5,7 @@ import { useThemeColor, type ThemeColorToken } from "@/hooks/useThemeColor";
5
5
  type NativeIconProps = {
6
6
  width?: number;
7
7
  height?: number;
8
- color?: string;
8
+ color: string;
9
9
  strokeWidth?: number;
10
10
  className?: string;
11
11
  };