@medway-ui/native 1.0.4 → 1.1.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 (154) hide show
  1. package/README.md +283 -77
  2. package/lib/commonjs/components/Alert.js +6 -3
  3. package/lib/commonjs/components/Alert.js.map +1 -1
  4. package/lib/commonjs/components/AlertDialog.js +38 -15
  5. package/lib/commonjs/components/AlertDialog.js.map +1 -1
  6. package/lib/commonjs/components/Avatar.js +101 -0
  7. package/lib/commonjs/components/Avatar.js.map +1 -0
  8. package/lib/commonjs/components/CardNews.js +319 -0
  9. package/lib/commonjs/components/CardNews.js.map +1 -0
  10. package/lib/commonjs/components/Checkbox.js +36 -13
  11. package/lib/commonjs/components/Checkbox.js.map +1 -1
  12. package/lib/commonjs/components/Dialog.js +15 -10
  13. package/lib/commonjs/components/Dialog.js.map +1 -1
  14. package/lib/commonjs/components/EmptyState.js +42 -0
  15. package/lib/commonjs/components/EmptyState.js.map +1 -0
  16. package/lib/commonjs/components/FallbackImage.js +55 -0
  17. package/lib/commonjs/components/FallbackImage.js.map +1 -0
  18. package/lib/commonjs/components/Input.js +55 -16
  19. package/lib/commonjs/components/Input.js.map +1 -1
  20. package/lib/commonjs/components/Label.js +43 -14
  21. package/lib/commonjs/components/Label.js.map +1 -1
  22. package/lib/commonjs/components/Loading.js +7 -3
  23. package/lib/commonjs/components/Loading.js.map +1 -1
  24. package/lib/commonjs/components/PageHeader.js +39 -0
  25. package/lib/commonjs/components/PageHeader.js.map +1 -0
  26. package/lib/commonjs/components/Select.js +32 -13
  27. package/lib/commonjs/components/Select.js.map +1 -1
  28. package/lib/commonjs/components/Sheet.js +14 -9
  29. package/lib/commonjs/components/Sheet.js.map +1 -1
  30. package/lib/commonjs/components/Skeleton.js +34 -0
  31. package/lib/commonjs/components/Skeleton.js.map +1 -0
  32. package/lib/commonjs/components/Switch.js +43 -14
  33. package/lib/commonjs/components/Switch.js.map +1 -1
  34. package/lib/commonjs/components/ThemedIcon.js +29 -0
  35. package/lib/commonjs/components/ThemedIcon.js.map +1 -0
  36. package/lib/commonjs/components/Toast.js +24 -12
  37. package/lib/commonjs/components/Toast.js.map +1 -1
  38. package/lib/commonjs/components/Tooltip.js +5 -3
  39. package/lib/commonjs/components/Tooltip.js.map +1 -1
  40. package/lib/commonjs/hooks/useThemeColor.js +71 -0
  41. package/lib/commonjs/hooks/useThemeColor.js.map +1 -0
  42. package/lib/commonjs/index.js +152 -0
  43. package/lib/commonjs/index.js.map +1 -1
  44. package/lib/module/components/Alert.js +6 -3
  45. package/lib/module/components/Alert.js.map +1 -1
  46. package/lib/module/components/AlertDialog.js +38 -15
  47. package/lib/module/components/AlertDialog.js.map +1 -1
  48. package/lib/module/components/Avatar.js +94 -0
  49. package/lib/module/components/Avatar.js.map +1 -0
  50. package/lib/module/components/CardNews.js +303 -0
  51. package/lib/module/components/CardNews.js.map +1 -0
  52. package/lib/module/components/Checkbox.js +37 -14
  53. package/lib/module/components/Checkbox.js.map +1 -1
  54. package/lib/module/components/Dialog.js +15 -10
  55. package/lib/module/components/Dialog.js.map +1 -1
  56. package/lib/module/components/EmptyState.js +39 -0
  57. package/lib/module/components/EmptyState.js.map +1 -0
  58. package/lib/module/components/FallbackImage.js +51 -0
  59. package/lib/module/components/FallbackImage.js.map +1 -0
  60. package/lib/module/components/Input.js +55 -16
  61. package/lib/module/components/Input.js.map +1 -1
  62. package/lib/module/components/Label.js +44 -15
  63. package/lib/module/components/Label.js.map +1 -1
  64. package/lib/module/components/Loading.js +7 -3
  65. package/lib/module/components/Loading.js.map +1 -1
  66. package/lib/module/components/PageHeader.js +36 -0
  67. package/lib/module/components/PageHeader.js.map +1 -0
  68. package/lib/module/components/Select.js +32 -13
  69. package/lib/module/components/Select.js.map +1 -1
  70. package/lib/module/components/Sheet.js +14 -9
  71. package/lib/module/components/Sheet.js.map +1 -1
  72. package/lib/module/components/Skeleton.js +30 -0
  73. package/lib/module/components/Skeleton.js.map +1 -0
  74. package/lib/module/components/Switch.js +44 -15
  75. package/lib/module/components/Switch.js.map +1 -1
  76. package/lib/module/components/ThemedIcon.js +26 -0
  77. package/lib/module/components/ThemedIcon.js.map +1 -0
  78. package/lib/module/components/Toast.js +24 -12
  79. package/lib/module/components/Toast.js.map +1 -1
  80. package/lib/module/components/Tooltip.js +6 -4
  81. package/lib/module/components/Tooltip.js.map +1 -1
  82. package/lib/module/hooks/useThemeColor.js +68 -0
  83. package/lib/module/hooks/useThemeColor.js.map +1 -0
  84. package/lib/module/index.js +15 -0
  85. package/lib/module/index.js.map +1 -1
  86. package/lib/typescript/components/Alert.d.ts.map +1 -1
  87. package/lib/typescript/components/AlertDialog.d.ts +2 -2
  88. package/lib/typescript/components/AlertDialog.d.ts.map +1 -1
  89. package/lib/typescript/components/Avatar.d.ts +28 -0
  90. package/lib/typescript/components/Avatar.d.ts.map +1 -0
  91. package/lib/typescript/components/CardNews.d.ts +54 -0
  92. package/lib/typescript/components/CardNews.d.ts.map +1 -0
  93. package/lib/typescript/components/Checkbox.d.ts +5 -5
  94. package/lib/typescript/components/Checkbox.d.ts.map +1 -1
  95. package/lib/typescript/components/Dialog.d.ts.map +1 -1
  96. package/lib/typescript/components/EmptyState.d.ts +13 -0
  97. package/lib/typescript/components/EmptyState.d.ts.map +1 -0
  98. package/lib/typescript/components/FallbackImage.d.ts +9 -0
  99. package/lib/typescript/components/FallbackImage.d.ts.map +1 -0
  100. package/lib/typescript/components/Input.d.ts +4 -5
  101. package/lib/typescript/components/Input.d.ts.map +1 -1
  102. package/lib/typescript/components/Label.d.ts.map +1 -1
  103. package/lib/typescript/components/Loading.d.ts +5 -1
  104. package/lib/typescript/components/Loading.d.ts.map +1 -1
  105. package/lib/typescript/components/PageHeader.d.ts +12 -0
  106. package/lib/typescript/components/PageHeader.d.ts.map +1 -0
  107. package/lib/typescript/components/Select.d.ts +3 -1
  108. package/lib/typescript/components/Select.d.ts.map +1 -1
  109. package/lib/typescript/components/Sheet.d.ts.map +1 -1
  110. package/lib/typescript/components/Skeleton.d.ts +8 -0
  111. package/lib/typescript/components/Skeleton.d.ts.map +1 -0
  112. package/lib/typescript/components/Switch.d.ts +2 -2
  113. package/lib/typescript/components/Switch.d.ts.map +1 -1
  114. package/lib/typescript/components/ThemedIcon.d.ts +24 -0
  115. package/lib/typescript/components/ThemedIcon.d.ts.map +1 -0
  116. package/lib/typescript/components/Toast.d.ts +1 -1
  117. package/lib/typescript/components/Toast.d.ts.map +1 -1
  118. package/lib/typescript/components/Tooltip.d.ts.map +1 -1
  119. package/lib/typescript/hooks/useThemeColor.d.ts +11 -0
  120. package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -0
  121. package/lib/typescript/index.d.ts +16 -0
  122. package/lib/typescript/index.d.ts.map +1 -1
  123. package/package.json +28 -4
  124. package/setup/MedwayUiProvider.tsx +64 -0
  125. package/setup/babel-brownfield.js +120 -0
  126. package/setup/createMedwayUiNavigator.tsx +94 -0
  127. package/setup/global.css +5 -0
  128. package/setup/index.ts +12 -0
  129. package/setup/index.tsx +42 -0
  130. package/setup/lazyScreen.tsx +14 -0
  131. package/setup/metro.js +266 -0
  132. package/setup/tailwind.js +84 -0
  133. package/setup/tokens.native.css +873 -0
  134. package/src/components/Alert.tsx +3 -1
  135. package/src/components/AlertDialog.tsx +63 -19
  136. package/src/components/Avatar.tsx +130 -0
  137. package/src/components/CardNews.tsx +412 -0
  138. package/src/components/Checkbox.tsx +51 -23
  139. package/src/components/Dialog.tsx +13 -7
  140. package/src/components/EmptyState.tsx +57 -0
  141. package/src/components/FallbackImage.tsx +58 -0
  142. package/src/components/Input.tsx +65 -19
  143. package/src/components/Label.tsx +49 -14
  144. package/src/components/Loading.tsx +13 -2
  145. package/src/components/PageHeader.tsx +50 -0
  146. package/src/components/Select.tsx +22 -10
  147. package/src/components/Sheet.tsx +30 -24
  148. package/src/components/Skeleton.tsx +45 -0
  149. package/src/components/Switch.tsx +64 -32
  150. package/src/components/ThemedIcon.tsx +46 -0
  151. package/src/components/Toast.tsx +26 -13
  152. package/src/components/Tooltip.tsx +1 -0
  153. package/src/hooks/useThemeColor.ts +79 -0
  154. package/src/index.ts +60 -0
@@ -1,16 +1,21 @@
1
- import { useState } from "react";
1
+ import { useState, type ReactNode } from "react";
2
+ import { type ViewStyle } from "react-native";
2
3
  import * as CheckboxPrimitive from "@rn-primitives/checkbox";
3
- import { Check, RemoveMinus } from "@medway-ui/icons/native";
4
- import { cva } from "class-variance-authority";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { cn } from "@/lib/utils";
6
+ import { Check, RemoveMinus } from "@medway-ui/icons/native";
7
+ import { useThemeColor } from "@/hooks/useThemeColor";
6
8
 
7
- interface CheckboxProps extends Omit<CheckboxPrimitive.RootProps, "checked" | "disabled" | "onCheckedChange"> {
9
+ interface CheckboxProps
10
+ extends Omit<
11
+ CheckboxPrimitive.RootProps,
12
+ "checked" | "onCheckedChange" | "children"
13
+ >,
14
+ Omit<VariantProps<typeof checkboxVariants>, "checked" | "disabled"> {
15
+ children?: ReactNode;
8
16
  checked?: boolean;
9
17
  defaultChecked?: boolean;
10
18
  onCheckedChange?: (checked: boolean) => void;
11
- disabled?: boolean;
12
- size?: "sm" | "md" | "lg";
13
- variant?: "regular";
14
19
  uncheckIcon?: boolean;
15
20
  }
16
21
 
@@ -20,6 +25,12 @@ const ICON_SIZES: Record<"sm" | "md" | "lg", number> = {
20
25
  lg: 20,
21
26
  };
22
27
 
28
+ const CHECKBOX_DIMENSIONS: Record<"sm" | "md" | "lg", number> = {
29
+ sm: 16,
30
+ md: 20,
31
+ lg: 24,
32
+ };
33
+
23
34
  const checkboxVariants = cva(
24
35
  "items-center justify-center rounded-sm border-2 shrink-0",
25
36
  {
@@ -59,17 +70,32 @@ function Checkbox({
59
70
  defaultChecked = false,
60
71
  onCheckedChange,
61
72
  disabled,
73
+ style,
74
+ children,
62
75
  ...props
63
76
  }: CheckboxProps) {
64
77
  const resolvedSize = size ?? "md";
65
78
  const iconSize = ICON_SIZES[resolvedSize];
79
+ const dimension = CHECKBOX_DIMENSIONS[resolvedSize];
66
80
  const [internalChecked, setInternalChecked] = useState(defaultChecked);
67
81
  const isControlled = checked !== undefined;
68
82
  const isChecked = isControlled ? checked : internalChecked;
83
+ const iconColor = useThemeColor("primary-foreground-onfill");
84
+
85
+ const handleCheckedChange = (nextChecked: boolean) => {
86
+ if (!isControlled) setInternalChecked(nextChecked);
87
+ onCheckedChange?.(nextChecked);
88
+ };
69
89
 
70
- const handleCheckedChange = (val: boolean) => {
71
- if (!isControlled) setInternalChecked(val);
72
- onCheckedChange?.(val);
90
+ const rootStyle: ViewStyle = {
91
+ width: dimension,
92
+ height: dimension,
93
+ minWidth: dimension,
94
+ minHeight: dimension,
95
+ borderWidth: 2,
96
+ borderRadius: 4,
97
+ alignItems: "center",
98
+ justifyContent: "center",
73
99
  };
74
100
 
75
101
  return (
@@ -77,27 +103,29 @@ function Checkbox({
77
103
  checked={isChecked}
78
104
  onCheckedChange={handleCheckedChange}
79
105
  disabled={disabled}
106
+ style={(state) => [
107
+ rootStyle,
108
+ typeof style === "function" ? style(state) : style,
109
+ ]}
80
110
  className={cn(
81
- checkboxVariants({ size, variant, checked: isChecked, disabled: disabled ?? false }),
111
+ checkboxVariants({
112
+ size,
113
+ variant,
114
+ checked: isChecked,
115
+ disabled: disabled ?? false,
116
+ }),
82
117
  className
83
118
  )}
84
119
  {...props}
85
120
  >
86
121
  <CheckboxPrimitive.Indicator className="items-center justify-center">
87
- {uncheckIcon ? (
88
- <RemoveMinus
89
- size={iconSize}
90
- strokeWidth={2.6}
91
- className="text-primary-foreground-onfill"
92
- />
122
+ {iconColor && (uncheckIcon ? (
123
+ <RemoveMinus width={iconSize} height={iconSize} strokeWidth={2.6} color={iconColor} />
93
124
  ) : (
94
- <Check
95
- size={iconSize}
96
- strokeWidth={2.6}
97
- className="text-primary-foreground-onfill"
98
- />
99
- )}
125
+ <Check width={iconSize} height={iconSize} strokeWidth={2.6} color={iconColor} />
126
+ ))}
100
127
  </CheckboxPrimitive.Indicator>
128
+ {children}
101
129
  </CheckboxPrimitive.Root>
102
130
  );
103
131
  }
@@ -2,6 +2,7 @@ import { type ReactNode, isValidElement, cloneElement, type ReactElement } from
2
2
  import { View, Text, ScrollView, type ViewProps } from "react-native";
3
3
  import * as DialogPrimitive from "@rn-primitives/dialog";
4
4
  import { CloseMd } from "@medway-ui/icons/native";
5
+ import { useThemeColor } from "@/hooks/useThemeColor";
5
6
  import { cn } from "@/lib/utils";
6
7
  import { Separator } from "@/components/Separator";
7
8
 
@@ -55,26 +56,31 @@ function DialogContainer({
55
56
  showCloseButton = true,
56
57
  ...props
57
58
  }: DialogContainerProps) {
59
+ const closeIconColor = useThemeColor("gray-foreground");
60
+
58
61
  return (
59
62
  <DialogPrimitive.Portal>
60
63
  <DialogPrimitive.Overlay
61
- className={cn("absolute inset-0 items-center justify-center bg-black/50", overlayClassName)}
64
+ className={cn("absolute inset-0 bg-black/50", overlayClassName)}
65
+ />
66
+ <DialogPrimitive.Content
67
+ className="absolute inset-0 items-center justify-center px-4"
68
+ {...props}
62
69
  >
63
- <DialogPrimitive.Content
70
+ <View
64
71
  className={cn(
65
- "relative flex-col max-h-[90%] w-full max-w-[calc(100%-2rem)] rounded-xl bg-background shadow-lg overflow-hidden",
72
+ "relative flex-col max-h-[90%] w-full rounded-xl bg-background shadow-lg overflow-hidden",
66
73
  className
67
74
  )}
68
- {...props}
69
75
  >
70
76
  {children}
71
77
  {showCloseButton && (
72
78
  <DialogPrimitive.Close className="absolute right-3 top-3 rounded-pill p-1.5 active:bg-gray-background-soft">
73
- <CloseMd size={16} className="text-gray-foreground" />
79
+ {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
74
80
  </DialogPrimitive.Close>
75
81
  )}
76
- </DialogPrimitive.Content>
77
- </DialogPrimitive.Overlay>
82
+ </View>
83
+ </DialogPrimitive.Content>
78
84
  </DialogPrimitive.Portal>
79
85
  );
80
86
  }
@@ -0,0 +1,57 @@
1
+ import { type ReactNode } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+
4
+ import {
5
+ Alert,
6
+ AlertActions,
7
+ AlertContainer,
8
+ AlertContent,
9
+ AlertDescription,
10
+ AlertIcon,
11
+ AlertTitle,
12
+ } from "@/components/Alert";
13
+ import { cn } from "@/lib/utils";
14
+
15
+ interface EmptyStateProps extends ViewProps {
16
+ icon?: ReactNode;
17
+ title: string;
18
+ description?: string;
19
+ action?: ReactNode;
20
+ variant?: "regular" | "emphasis" | "success" | "warning" | "error";
21
+ }
22
+
23
+ function EmptyState({
24
+ className,
25
+ icon,
26
+ title,
27
+ description,
28
+ action,
29
+ variant = "regular",
30
+ ...props
31
+ }: EmptyStateProps) {
32
+ return (
33
+ <View
34
+ className={cn(
35
+ "items-center gap-6 rounded-lg border border-gray-stroke-muted bg-surface-card p-8",
36
+ className
37
+ )}
38
+ {...props}
39
+ >
40
+ <Alert variant={variant} className="border-0 bg-transparent p-0">
41
+ <AlertContainer>
42
+ {icon ? <AlertIcon icon={icon} /> : null}
43
+ <AlertContent>
44
+ <AlertTitle>{title}</AlertTitle>
45
+ {description ? (
46
+ <AlertDescription>{description}</AlertDescription>
47
+ ) : null}
48
+ </AlertContent>
49
+ </AlertContainer>
50
+ {action ? <AlertActions>{action}</AlertActions> : null}
51
+ </Alert>
52
+ </View>
53
+ );
54
+ }
55
+
56
+ export { EmptyState };
57
+ export type { EmptyStateProps };
@@ -0,0 +1,58 @@
1
+ import Svg, {
2
+ Defs,
3
+ RadialGradient,
4
+ Stop,
5
+ Rect,
6
+ Path,
7
+ G,
8
+ type SvgProps,
9
+ } from "react-native-svg";
10
+
11
+ import { cn } from "@/lib/utils";
12
+
13
+ const GRADIENT_ID = "medway-fallback-gradient";
14
+
15
+ const LOGO_PATH =
16
+ "M104.229 3.90299H107.408C107.836 3.90299 108.122 4.34302 107.948 4.73464L100.413 21.6491C100.318 21.8625 100.106 22 99.8724 22H96.6935C96.2653 22 95.9791 21.56 96.153 21.1684L97.89 17.2687L92.3798 4.90074C92.1706 4.43102 92.5141 3.90189 93.0281 3.90189H95.8779C96.1585 3.90189 96.4128 4.0669 96.5262 4.32322L100.09 12.325L103.686 4.25391C103.781 4.0405 103.993 3.90299 104.227 3.90299H104.229ZM91.9043 11.0005V17.3875C91.9043 17.7791 91.5862 18.097 91.1943 18.097H88.9455C88.5537 18.097 88.2356 17.7791 88.2356 17.3875V17.2841C87.1547 17.8957 85.8999 18.2367 84.5647 18.2147C80.7496 18.152 77.6159 15.0928 77.4706 11.2822C77.3132 7.16906 80.6043 3.78419 84.6847 3.78419C88.765 3.78419 91.9043 7.01505 91.9043 10.9994V11.0005ZM88.0573 11.0005C88.0573 9.13926 86.5471 7.62888 84.6836 7.62888C82.8201 7.62888 81.3099 9.13816 81.3099 11.0005C81.3099 12.8629 82.8201 14.3722 84.6836 14.3722C86.5471 14.3722 88.0573 12.8629 88.0573 11.0005ZM75.4519 12.1578V4.61363C75.4519 4.22201 75.1338 3.9041 74.7419 3.9041H72.3159C71.9241 3.9041 71.606 4.22201 71.606 4.61363V12.2282C71.606 13.4273 70.6153 14.3997 69.4089 14.3711C68.2378 14.3436 67.3154 13.358 67.3154 12.1875V4.61363C67.3154 4.22201 66.9973 3.9041 66.6054 3.9041H64.1794C63.7876 3.9041 63.4695 4.22201 63.4695 4.61363V12.2282C63.4695 13.4273 62.4788 14.3997 61.2724 14.3711C60.1013 14.3436 59.1789 13.358 59.1789 12.1875V4.61363C59.1789 4.22201 58.8608 3.9041 58.4689 3.9041H56.0429C55.6511 3.9041 55.333 4.22201 55.333 4.61363V12.1578C55.333 15.4778 58.0507 18.2422 61.3726 18.2158C62.9235 18.2037 64.3346 17.5998 65.3935 16.6196C66.4524 17.5998 67.8635 18.2037 69.4144 18.2158C72.7364 18.2422 75.4541 15.4767 75.4541 12.1578H75.4519ZM49.7094 0H51.9582C52.35 0 52.6682 0.317916 52.6682 0.709535V17.3875C52.6682 17.7791 52.35 18.097 51.9582 18.097H49.7094C49.3176 18.097 48.9995 17.7791 48.9995 17.3875V17.2841C47.9505 17.877 46.7397 18.2158 45.4486 18.2158C41.3671 18.2158 38.0771 14.832 38.2345 10.7178C38.3798 6.90834 41.5135 3.84799 45.3286 3.78529C46.6648 3.76329 47.9186 4.1043 48.9995 4.71594V0.709535C48.9995 0.317916 49.3176 0 49.7094 0ZM48.8223 11.0005C48.8223 9.13926 47.3121 7.62888 45.4486 7.62888C43.585 7.62888 42.0749 9.13816 42.0749 11.0005C42.0749 12.8629 43.585 14.3722 45.4486 14.3722C47.3121 14.3722 48.8223 12.8629 48.8223 11.0005ZM26.4139 12.2425C26.1508 12.2425 25.9769 12.5186 26.0947 12.7529C26.6979 13.9608 27.9031 14.7847 29.2945 14.7847C30.2576 14.7847 31.1326 14.3887 31.7799 13.7452C31.9109 13.6143 32.0903 13.5428 32.2752 13.5428H34.8806C35.4265 13.5428 35.7689 14.1324 35.497 14.6054C34.2587 16.7626 31.945 18.2147 29.2945 18.2147C25.3396 18.2147 22.1343 14.9838 22.1343 10.9994C22.1343 7.01505 25.3407 3.78419 29.2945 3.78419C33.2482 3.78419 36.4546 7.01505 36.4546 10.9994C36.4546 11.1997 36.4469 11.3966 36.4304 11.5924C36.4007 11.9598 36.0914 12.2414 35.7226 12.2414H26.4139V12.2425ZM26.2356 8.98855C26.0947 9.22286 26.2686 9.52208 26.5427 9.52208H32.0451C32.3192 9.52208 32.4931 9.22396 32.3522 8.98855C31.7138 7.9237 30.5823 7.21526 29.2945 7.21526C28.0066 7.21526 26.8751 7.9237 26.2367 8.98855H26.2356ZM14.0815 3.78529C12.5306 3.79739 11.1194 4.40132 10.0605 5.38147C9.00165 4.40132 7.59054 3.79739 6.03963 3.78529C2.71767 3.75889 0 6.52443 0 9.84329V17.3875C0 17.7791 0.318107 18.097 0.709962 18.097H3.13594C3.52779 18.097 3.8459 17.7791 3.8459 17.3875V9.81359C3.8459 8.64313 4.7683 7.65748 5.93946 7.62998C7.14585 7.60138 8.13649 8.57383 8.13649 9.77289V17.3875C8.13649 17.7791 8.4546 18.097 8.84645 18.097H11.2724C11.6643 18.097 11.9824 17.7791 11.9824 17.3875V9.81359C11.9824 8.64313 12.9048 7.65748 14.076 7.62998C15.2823 7.60138 16.273 8.57383 16.273 9.77289V17.3875C16.273 17.7791 16.5911 18.097 16.9829 18.097H19.4089C19.8008 18.097 20.1189 17.7791 20.1189 17.3875V9.84329C20.1189 6.52333 17.4012 3.75889 14.0793 3.78529H14.0815Z";
17
+
18
+ interface FallbackImageProps extends SvgProps {
19
+ label?: string;
20
+ className?: string;
21
+ }
22
+
23
+ function FallbackImage({
24
+ className,
25
+ label = "Medway",
26
+ ...props
27
+ }: FallbackImageProps) {
28
+ return (
29
+ <Svg
30
+ accessibilityRole="image"
31
+ accessibilityLabel={label}
32
+ viewBox="0 0 320 167"
33
+ preserveAspectRatio="xMidYMid slice"
34
+ className={cn("size-full", className)}
35
+ {...props}
36
+ >
37
+ <Defs>
38
+ <RadialGradient
39
+ id={GRADIENT_ID}
40
+ cx="50%"
41
+ cy="42%"
42
+ r="78%"
43
+ >
44
+ <Stop offset="0%" stopColor="#15397A" />
45
+ <Stop offset="55%" stopColor="#06245E" />
46
+ <Stop offset="100%" stopColor="#021536" />
47
+ </RadialGradient>
48
+ </Defs>
49
+ <Rect width="320" height="167" fill={`url(#${GRADIENT_ID})`} />
50
+ <G transform="translate(85.5 62.7) scale(1.38)" fill="#FFFFFF">
51
+ <Path d={LOGO_PATH} />
52
+ </G>
53
+ </Svg>
54
+ );
55
+ }
56
+
57
+ export { FallbackImage };
58
+ export type { FallbackImageProps };
@@ -32,11 +32,17 @@ import {
32
32
  Show,
33
33
  TriangleWarning,
34
34
  } from "@medway-ui/icons/native";
35
+ import { useThemeColor } from "@/hooks/useThemeColor";
35
36
  import { cn } from "@/lib/utils";
36
37
 
37
38
  export type InputType = "text" | "email" | "numeric" | "password";
38
39
 
39
- type IconElement = ReactElement<{ size?: number; strokeWidth?: number }>;
40
+ type IconElement = ReactElement<{
41
+ width?: number;
42
+ height?: number;
43
+ color?: string;
44
+ strokeWidth?: number;
45
+ }>;
40
46
 
41
47
  export interface InputProps
42
48
  extends Omit<TextInputProps, "size">,
@@ -193,11 +199,17 @@ function InputEndIcon({ className, ...props }: ViewProps) {
193
199
  }
194
200
 
195
201
  function applyInputIconSize(
196
- icon: ReactElement<{ size?: number; strokeWidth?: number }>,
197
- size: VariantProps<typeof inputVariants>["size"] = "lg"
198
- ): ReactElement {
202
+ icon: IconElement,
203
+ size: VariantProps<typeof inputVariants>["size"] = "lg",
204
+ color?: string,
205
+ ): IconElement {
199
206
  const iconSize = size === "md" ? INPUT_ICON_SIZE_MD : INPUT_ICON_SIZE_LG;
200
- return cloneElement(icon, { size: iconSize, strokeWidth: INPUT_ICON_STROKE_WIDTH });
207
+ return cloneElement(icon, {
208
+ width: iconSize,
209
+ height: iconSize,
210
+ color,
211
+ strokeWidth: INPUT_ICON_STROKE_WIDTH,
212
+ });
201
213
  }
202
214
 
203
215
  function InputPassword({ className, size = "lg", invalid = false, label, ...props }: InputPasswordProps) {
@@ -205,11 +217,20 @@ function InputPassword({ className, size = "lg", invalid = false, label, ...prop
205
217
  const [focused, setFocused] = useState(false);
206
218
  const hasValue = Boolean(props.value || props.defaultValue);
207
219
  const isFloated = focused || hasValue;
220
+ const iconColor = useThemeColor("gray-foreground");
221
+ const iconSize = size === "md" ? INPUT_ICON_SIZE_MD : INPUT_ICON_SIZE_LG;
208
222
 
209
223
  return (
210
224
  <InputAdornment>
211
225
  <InputStartIcon>
212
- {applyInputIconSize(<Lock />, size)}
226
+ {iconColor && (
227
+ <Lock
228
+ width={iconSize}
229
+ height={iconSize}
230
+ strokeWidth={INPUT_ICON_STROKE_WIDTH}
231
+ color={iconColor}
232
+ />
233
+ )}
213
234
  </InputStartIcon>
214
235
  <View className="relative flex-1">
215
236
  {label && <InputLabel floated={isFloated} disabled={props.editable === false}>{label}</InputLabel>}
@@ -232,7 +253,11 @@ function InputPassword({ className, size = "lg", invalid = false, label, ...prop
232
253
  disabled={props.editable === false}
233
254
  accessibilityLabel={visible ? "Hide password" : "Show password"}
234
255
  >
235
- {applyInputIconSize(visible ? <Hide /> : <Show />, size)}
256
+ {iconColor && (visible ? (
257
+ <Hide width={iconSize} height={iconSize} strokeWidth={INPUT_ICON_STROKE_WIDTH} color={iconColor} />
258
+ ) : (
259
+ <Show width={iconSize} height={iconSize} strokeWidth={INPUT_ICON_STROKE_WIDTH} color={iconColor} />
260
+ ))}
236
261
  </Pressable>
237
262
  </InputEndIcon>
238
263
  </InputAdornment>
@@ -258,6 +283,7 @@ function InputNumeric({
258
283
  const displayValue = isControlled ? String(value ?? "") : internalValue;
259
284
  const [focused, setFocused] = useState(false);
260
285
  const isFloated = focused || displayValue !== "";
286
+ const iconColor = useThemeColor("gray-foreground");
261
287
 
262
288
  const numericValue = parseFloat(displayValue.replace(",", ".")) || 0;
263
289
 
@@ -278,7 +304,7 @@ function InputNumeric({
278
304
  <InputAdornment>
279
305
  {iconLeft && (
280
306
  <InputStartIcon>
281
- {applyInputIconSize(iconLeft, size)}
307
+ {applyInputIconSize(iconLeft, size, iconColor)}
282
308
  </InputStartIcon>
283
309
  )}
284
310
  <View className="relative flex-1">
@@ -314,11 +340,15 @@ function InputNumeric({
314
340
  className="items-center justify-center p-1"
315
341
  accessibilityLabel="Increase value"
316
342
  >
317
- <ChevronDown
318
- style={{ transform: [{ rotate: "180deg" }] }}
319
- size={chevronSize}
320
- strokeWidth={3}
321
- />
343
+ {iconColor && (
344
+ <ChevronDown
345
+ width={chevronSize}
346
+ height={chevronSize}
347
+ strokeWidth={3}
348
+ color={iconColor}
349
+ style={{ transform: [{ rotate: "180deg" }] }}
350
+ />
351
+ )}
322
352
  </Pressable>
323
353
  <Pressable
324
354
  disabled={!editable}
@@ -326,7 +356,9 @@ function InputNumeric({
326
356
  className="items-center justify-center p-1"
327
357
  accessibilityLabel="Decrease value"
328
358
  >
329
- <ChevronDown size={chevronSize} strokeWidth={3} />
359
+ {iconColor && (
360
+ <ChevronDown width={chevronSize} height={chevronSize} strokeWidth={3} color={iconColor} />
361
+ )}
330
362
  </Pressable>
331
363
  </View>
332
364
  </InputEndIcon>
@@ -350,6 +382,8 @@ function Input({
350
382
  const [focused, setFocused] = useState(false);
351
383
  const hasValue = Boolean(value || defaultValue);
352
384
  const isFloated = focused || hasValue;
385
+ const iconColor = useThemeColor("gray-foreground");
386
+ const errorIconColor = useThemeColor("feedback-error-foreground");
353
387
 
354
388
  if (type === "numeric") {
355
389
  return (
@@ -382,17 +416,29 @@ function Input({
382
416
  );
383
417
  }
384
418
 
385
- const resolvedIconLeft = iconLeft ?? (type === "email" ? <Mail /> : undefined);
419
+ const iconSize = size === "md" ? INPUT_ICON_SIZE_MD : INPUT_ICON_SIZE_LG;
420
+ const defaultIconLeft =
421
+ type === "email" && iconColor ? (
422
+ <Mail width={iconSize} height={iconSize} strokeWidth={INPUT_ICON_STROKE_WIDTH} color={iconColor} />
423
+ ) : undefined;
424
+ const resolvedIconLeft = iconLeft
425
+ ? applyInputIconSize(iconLeft, size, iconColor)
426
+ : defaultIconLeft;
427
+
386
428
  const showInvalidRightIcon = invalid && (type === "text" || type === "email");
387
429
  const resolvedIconRight = showInvalidRightIcon
388
- ? <TriangleWarning />
389
- : iconRight;
430
+ ? errorIconColor && (
431
+ <TriangleWarning width={iconSize} height={iconSize} strokeWidth={INPUT_ICON_STROKE_WIDTH} color={errorIconColor} />
432
+ )
433
+ : iconRight
434
+ ? applyInputIconSize(iconRight, size, iconColor)
435
+ : undefined;
390
436
 
391
437
  return (
392
438
  <InputAdornment>
393
439
  {resolvedIconLeft && (
394
440
  <InputStartIcon>
395
- {applyInputIconSize(resolvedIconLeft, size)}
441
+ {resolvedIconLeft}
396
442
  </InputStartIcon>
397
443
  )}
398
444
  <View className="relative flex-1">
@@ -416,7 +462,7 @@ function Input({
416
462
  </View>
417
463
  {resolvedIconRight && (
418
464
  <InputEndIcon>
419
- {applyInputIconSize(resolvedIconRight, size)}
465
+ {resolvedIconRight}
420
466
  </InputEndIcon>
421
467
  )}
422
468
  </InputAdornment>
@@ -9,6 +9,49 @@ interface LabelProps extends PressableProps, VariantProps<typeof labelVariants>
9
9
  disabled?: boolean;
10
10
  }
11
11
 
12
+ function getLabelText(children: ReactNode): string | null {
13
+ if (typeof children === "string" || typeof children === "number") {
14
+ return String(children);
15
+ }
16
+
17
+ if (Array.isArray(children)) {
18
+ const parts = children.filter(
19
+ (child): child is string | number =>
20
+ typeof child === "string" || typeof child === "number",
21
+ );
22
+
23
+ if (parts.length === children.length && parts.length > 0) {
24
+ return parts.join("");
25
+ }
26
+ }
27
+
28
+ return null;
29
+ }
30
+
31
+ function LabelText({
32
+ children,
33
+ variant,
34
+ size,
35
+ disabled,
36
+ }: {
37
+ children: ReactNode;
38
+ variant?: LabelProps["variant"];
39
+ size?: LabelProps["size"];
40
+ disabled?: boolean;
41
+ }) {
42
+ const text = getLabelText(children);
43
+
44
+ if (text !== null) {
45
+ return (
46
+ <Text className={cn(labelTextVariants({ variant, size, disabled }))}>
47
+ {text}
48
+ </Text>
49
+ );
50
+ }
51
+
52
+ return <>{children}</>;
53
+ }
54
+
12
55
  const labelVariants = cva(
13
56
  "flex-row items-center gap-3 shrink-0",
14
57
  {
@@ -63,13 +106,9 @@ function Label({ className, variant, size, block, disabled = false, children, ..
63
106
  )}
64
107
  {...props}
65
108
  >
66
- {typeof children === "string" ? (
67
- <Text className={cn(labelTextVariants({ variant, size, disabled }))}>
68
- {children}
69
- </Text>
70
- ) : (
71
- children
72
- )}
109
+ <LabelText variant={variant} size={size} disabled={disabled}>
110
+ {children}
111
+ </LabelText>
73
112
  </Pressable>
74
113
  );
75
114
  }
@@ -80,13 +119,9 @@ function Label({ className, variant, size, block, disabled = false, children, ..
80
119
  className={cn(labelVariants({ variant, size }), className)}
81
120
  {...props}
82
121
  >
83
- {typeof children === "string" ? (
84
- <Text className={cn(labelTextVariants({ variant, size, disabled }))}>
85
- {children}
86
- </Text>
87
- ) : (
88
- children
89
- )}
122
+ <LabelText variant={variant} size={size} disabled={disabled}>
123
+ {children}
124
+ </LabelText>
90
125
  </Pressable>
91
126
  );
92
127
  }
@@ -9,14 +9,25 @@ import Animated, {
9
9
  Easing,
10
10
  } from "react-native-reanimated";
11
11
  import { cn } from "@/lib/utils";
12
+ import { useThemeColor, type ThemeColorToken } from "@/hooks/useThemeColor";
12
13
 
13
14
  interface LoadingProps {
14
15
  size?: number;
15
16
  className?: string;
17
+ /** Cor explícita (opcional). Preferir `colorToken` para cores do theme. */
16
18
  color?: string;
19
+ /** Token semântico do design system (default: primary-background-fill). */
20
+ colorToken?: ThemeColorToken;
17
21
  }
18
22
 
19
- function Loading({ size = 24, color = "currentColor", className }: LoadingProps) {
23
+ function Loading({
24
+ size = 24,
25
+ colorToken = "primary-background-fill",
26
+ className,
27
+ color,
28
+ }: LoadingProps) {
29
+ const themeColor = useThemeColor(colorToken);
30
+ const stroke = color ?? themeColor;
20
31
  const rotation = useSharedValue(0);
21
32
 
22
33
  useEffect(() => {
@@ -39,7 +50,7 @@ function Loading({ size = 24, color = "currentColor", className }: LoadingProps)
39
50
  height={size}
40
51
  viewBox="0 0 24 24"
41
52
  fill="none"
42
- stroke={color}
53
+ stroke={stroke}
43
54
  strokeWidth={2}
44
55
  strokeLinecap="round"
45
56
  strokeLinejoin="round"
@@ -0,0 +1,50 @@
1
+ import { type ReactNode } from "react";
2
+ import { Text, View, type ViewProps } from "react-native";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ interface PageHeaderProps extends ViewProps {
7
+ icon?: ReactNode;
8
+ title: string;
9
+ description?: string;
10
+ badge?: ReactNode;
11
+ }
12
+
13
+ function PageHeader({
14
+ className,
15
+ icon,
16
+ title,
17
+ description,
18
+ badge,
19
+ ...props
20
+ }: PageHeaderProps) {
21
+ return (
22
+ <View
23
+ className={cn(
24
+ "overflow-hidden rounded-t-xl rounded-b-2xl bg-secondary-background-soft-muted pb-4",
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ <View className="gap-4 px-4 pt-8">
30
+ <View className="flex-row items-start gap-3">
31
+ {icon}
32
+ <View className="flex-1 gap-1">
33
+ <Text className="text-title-xs font-semibold text-primary-foreground">
34
+ {title}
35
+ </Text>
36
+ {description ? (
37
+ <Text className="text-sm leading-sm text-gray-foreground-muted">
38
+ {description}
39
+ </Text>
40
+ ) : null}
41
+ </View>
42
+ </View>
43
+ {badge}
44
+ </View>
45
+ </View>
46
+ );
47
+ }
48
+
49
+ export { PageHeader };
50
+ export type { PageHeaderProps };