@medway-ui/native 1.0.3 → 1.1.1

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 +76 -3
  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 -5
  124. package/setup/MedwayUiProvider.tsx +65 -0
  125. package/setup/babel-brownfield.js +50 -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 +195 -0
  132. package/setup/tailwind.js +64 -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
@@ -9,9 +9,10 @@ import React, {
9
9
  } from "react";
10
10
  import { View, Text, type ViewProps, type TextProps } from "react-native";
11
11
  import * as SelectPrimitive from "@rn-primitives/select";
12
- import { Check, ChevronDown, ChevronUp } from "@medway-ui/icons/native";
13
12
  import { cva } from "class-variance-authority";
14
13
  import { cn } from "@/lib/utils";
14
+ import { Check, ChevronDown, ChevronUp } from "@medway-ui/icons/native";
15
+ import { useThemeColor } from "@/hooks/useThemeColor";
15
16
 
16
17
  type SelectSize = "md" | "lg";
17
18
 
@@ -53,6 +54,11 @@ const selectVariants = cva(
53
54
  }
54
55
  );
55
56
 
57
+ const SELECT_HEIGHT: Record<SelectSize, number> = {
58
+ lg: 48,
59
+ md: 40,
60
+ };
61
+
56
62
  const SelectContext = createContext<SelectContextValue>({
57
63
  invalid: false,
58
64
  size: "lg",
@@ -71,20 +77,21 @@ function SelectWrapper({ className, ...props }: ViewProps) {
71
77
  return <View className={cn("relative flex-col gap-2", className)} {...props} />;
72
78
  }
73
79
 
74
- function SelectTrigger({ className, children, ...props }: SelectTriggerProps) {
80
+ function SelectTrigger({ className, children, style, ...props }: SelectTriggerProps & { style?: ViewProps['style'] }) {
75
81
  const { invalid, size } = useContext(SelectContext);
82
+ const chevronSize = size === "lg" ? 24 : 20;
83
+ const chevronColor = useThemeColor("gray-foreground");
76
84
 
77
85
  return (
78
86
  <SelectPrimitive.Trigger
87
+ style={[{ height: SELECT_HEIGHT[size], minHeight: SELECT_HEIGHT[size] }, style]}
79
88
  className={cn(selectVariants({ size, invalid }), className)}
80
89
  {...props}
81
90
  >
82
91
  {children}
83
- <ChevronDown
84
- size={size === "lg" ? 24 : 20}
85
- strokeWidth={1.6}
86
- className="text-gray-foreground shrink-0"
87
- />
92
+ {chevronColor && (
93
+ <ChevronDown width={chevronSize} height={chevronSize} strokeWidth={1.6} color={chevronColor} />
94
+ )}
88
95
  </SelectPrimitive.Trigger>
89
96
  );
90
97
  }
@@ -142,6 +149,7 @@ function SelectHeader({ className, ...props }: SelectPrimitive.LabelProps) {
142
149
  function SelectItem({ className, children, icon, label, ...props }: SelectItemProps) {
143
150
  const { size } = useContext(SelectContext);
144
151
  const resolvedLabel = label ?? (typeof children === "string" ? children : "");
152
+ const checkColor = useThemeColor("secondary-foreground");
145
153
 
146
154
  return (
147
155
  <SelectPrimitive.Item
@@ -163,7 +171,7 @@ function SelectItem({ className, children, icon, label, ...props }: SelectItemPr
163
171
  )}
164
172
  <SelectPrimitive.ItemText className="flex-1 text-sm font-medium text-gray-foreground" />
165
173
  <SelectPrimitive.ItemIndicator className="absolute right-2 h-3.5 w-3.5 items-center justify-center">
166
- <Check size={14} strokeWidth={1.6} className="text-secondary-foreground" />
174
+ {checkColor && <Check width={14} height={14} strokeWidth={1.6} color={checkColor} />}
167
175
  </SelectPrimitive.ItemIndicator>
168
176
  </SelectPrimitive.Item>
169
177
  );
@@ -179,23 +187,27 @@ function SelectSeparator({ className, ...props }: SelectPrimitive.SeparatorProps
179
187
  }
180
188
 
181
189
  function SelectScrollUpButton({ className, ...props }: SelectPrimitive.ScrollUpButtonProps) {
190
+ const chevronColor = useThemeColor("gray-foreground");
191
+
182
192
  return (
183
193
  <SelectPrimitive.ScrollUpButton
184
194
  className={cn("items-center justify-center py-1", className)}
185
195
  {...props}
186
196
  >
187
- <ChevronUp size={16} strokeWidth={1.6} className="text-gray-foreground" />
197
+ {chevronColor && <ChevronUp width={16} height={16} strokeWidth={1.6} color={chevronColor} />}
188
198
  </SelectPrimitive.ScrollUpButton>
189
199
  );
190
200
  }
191
201
 
192
202
  function SelectScrollDownButton({ className, ...props }: SelectPrimitive.ScrollDownButtonProps) {
203
+ const chevronColor = useThemeColor("gray-foreground");
204
+
193
205
  return (
194
206
  <SelectPrimitive.ScrollDownButton
195
207
  className={cn("items-center justify-center py-1", className)}
196
208
  {...props}
197
209
  >
198
- <ChevronDown size={16} strokeWidth={1.6} className="text-gray-foreground" />
210
+ {chevronColor && <ChevronDown width={16} height={16} strokeWidth={1.6} color={chevronColor} />}
199
211
  </SelectPrimitive.ScrollDownButton>
200
212
  );
201
213
  }
@@ -2,6 +2,7 @@ import { type ReactNode } from "react";
2
2
  import { View, 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
 
@@ -62,33 +63,38 @@ function SheetContainer({
62
63
  showCloseButton = true,
63
64
  ...props
64
65
  }: SheetContainerProps) {
66
+ const closeIconColor = useThemeColor("gray-foreground");
67
+
65
68
  return (
66
69
  <DialogPrimitive.Portal>
67
- <DialogPrimitive.Overlay
68
- className={cn("bg-black/50", overlayPositionMap[side], overlayClassName)}
70
+ <DialogPrimitive.Overlay
71
+ className={cn("absolute inset-0 bg-black/50", overlayClassName)}
72
+ />
73
+ <DialogPrimitive.Content
74
+ className={cn(overlayPositionMap[side])}
75
+ {...props}
76
+ >
77
+ <View
78
+ className={cn(
79
+ "relative flex-col bg-background shadow-lg overflow-hidden",
80
+ contentSizeMap[side],
81
+ className
82
+ )}
69
83
  >
70
- <DialogPrimitive.Content
71
- className={cn(
72
- "relative flex-col bg-background shadow-lg overflow-hidden",
73
- contentSizeMap[side],
74
- className
75
- )}
76
- {...props}
77
- >
78
- {children}
79
- {showCloseButton && (
80
- <DialogPrimitive.Close
81
- className={cn(
82
- closeButtonPositionMap[side],
83
- "rounded-pill p-1.5 active:bg-gray-background-soft"
84
- )}
85
- >
86
- <CloseMd size={16} className="text-gray-foreground" />
87
- </DialogPrimitive.Close>
88
- )}
89
- </DialogPrimitive.Content>
90
- </DialogPrimitive.Overlay>
91
- </DialogPrimitive.Portal>
84
+ {children}
85
+ {showCloseButton && (
86
+ <DialogPrimitive.Close
87
+ className={cn(
88
+ closeButtonPositionMap[side],
89
+ "rounded-pill p-1.5 active:bg-gray-background-soft"
90
+ )}
91
+ >
92
+ {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
93
+ </DialogPrimitive.Close>
94
+ )}
95
+ </View>
96
+ </DialogPrimitive.Content>
97
+ </DialogPrimitive.Portal>
92
98
  );
93
99
  }
94
100
 
@@ -0,0 +1,45 @@
1
+ import { useEffect } from "react";
2
+ import { type ViewProps } from "react-native";
3
+ import Animated, {
4
+ cancelAnimation,
5
+ Easing,
6
+ interpolate,
7
+ useAnimatedStyle,
8
+ useSharedValue,
9
+ withRepeat,
10
+ withTiming,
11
+ } from "react-native-reanimated";
12
+
13
+ import { cn } from "@/lib/utils";
14
+
15
+ interface SkeletonProps extends ViewProps {
16
+ className?: string;
17
+ }
18
+
19
+ function Skeleton({ className, style, ...props }: SkeletonProps) {
20
+ const shimmer = useSharedValue(0);
21
+
22
+ useEffect(() => {
23
+ shimmer.value = withRepeat(
24
+ withTiming(1, { duration: 1200, easing: Easing.inOut(Easing.ease) }),
25
+ -1,
26
+ true
27
+ );
28
+ return () => cancelAnimation(shimmer);
29
+ }, [shimmer]);
30
+
31
+ const animatedStyle = useAnimatedStyle(() => ({
32
+ opacity: interpolate(shimmer.value, [0, 1], [0.35, 0.75]),
33
+ }));
34
+
35
+ return (
36
+ <Animated.View
37
+ style={[animatedStyle, style]}
38
+ className={cn("rounded-md bg-gray-150", className)}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ export { Skeleton };
45
+ export type { SkeletonProps };
@@ -1,5 +1,5 @@
1
1
  import { useState, useEffect, type ReactNode } from "react";
2
- import { View } from "react-native";
2
+ import { View, type ViewStyle } from "react-native";
3
3
  import * as SwitchPrimitive from "@rn-primitives/switch";
4
4
  import Animated, {
5
5
  useSharedValue,
@@ -10,7 +10,12 @@ import Animated, {
10
10
  import { cva, type VariantProps } from "class-variance-authority";
11
11
  import { cn } from "@/lib/utils";
12
12
 
13
- interface SwitchProps extends Omit<SwitchPrimitive.RootProps, "checked" | "onCheckedChange">, VariantProps<typeof switchVariants> {
13
+ interface SwitchProps
14
+ extends Omit<
15
+ SwitchPrimitive.RootProps,
16
+ "checked" | "onCheckedChange" | "children"
17
+ >,
18
+ VariantProps<typeof switchVariants> {
14
19
  checked?: boolean;
15
20
  defaultChecked?: boolean;
16
21
  onCheckedChange?: (checked: boolean) => void;
@@ -30,6 +35,12 @@ const THUMB_TRANSLATE: Record<"sm" | "md" | "lg", number> = {
30
35
  lg: 19,
31
36
  };
32
37
 
38
+ const SWITCH_TRACK: Record<"sm" | "md" | "lg", { width: number; height: number }> = {
39
+ sm: { width: 24, height: 16 },
40
+ md: { width: 32, height: 20 },
41
+ lg: { width: 40, height: 24 },
42
+ };
43
+
33
44
  const switchVariants = cva(
34
45
  "rounded-full border-2 items-center shrink-0",
35
46
  {
@@ -60,9 +71,11 @@ function Switch({
60
71
  defaultChecked,
61
72
  onCheckedChange,
62
73
  disabled,
74
+ style,
63
75
  ...props
64
76
  }: SwitchProps) {
65
77
  const resolvedSize = size ?? "md";
78
+ const trackSize = SWITCH_TRACK[resolvedSize];
66
79
  const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);
67
80
  const isControlled = checked !== undefined;
68
81
  const isChecked = isControlled ? (checked ?? false) : internalChecked;
@@ -83,9 +96,19 @@ function Switch({
83
96
  const hasIcons = checkedIcon || uncheckedIcon;
84
97
  const thumbSize = THUMB_SIZES[resolvedSize];
85
98
 
86
- const handleCheckedChange = (val: boolean) => {
87
- if (!isControlled) setInternalChecked(val);
88
- onCheckedChange?.(val);
99
+ const handleCheckedChange = (nextChecked: boolean) => {
100
+ if (!isControlled) setInternalChecked(nextChecked);
101
+ onCheckedChange?.(nextChecked);
102
+ };
103
+
104
+ const trackStyle: ViewStyle = {
105
+ width: trackSize.width,
106
+ height: trackSize.height,
107
+ minWidth: trackSize.width,
108
+ minHeight: trackSize.height,
109
+ borderRadius: trackSize.height / 2,
110
+ borderWidth: 2,
111
+ justifyContent: "center",
89
112
  };
90
113
 
91
114
  return (
@@ -93,43 +116,52 @@ function Switch({
93
116
  checked={isChecked}
94
117
  onCheckedChange={handleCheckedChange}
95
118
  disabled={disabled}
119
+ style={(state) => [
120
+ trackStyle,
121
+ typeof style === "function" ? style(state) : style,
122
+ ]}
96
123
  className={cn(
97
124
  switchVariants({ size, variant }),
98
125
  disabled
99
126
  ? "bg-gray-background-soft-muted border-gray-stroke-muted"
100
127
  : isChecked
101
- ? "bg-primary-background-fill border-transparent"
102
- : "bg-surface-card border-gray-stroke-muted",
128
+ ? "bg-primary-background-fill border-primary-background-fill"
129
+ : "bg-surface-card border-gray-stroke-muted",
103
130
  className
104
131
  )}
105
132
  {...props}
106
133
  >
107
- <Animated.View
108
- style={[
109
- thumbStyle,
110
- {
111
- width: thumbSize,
112
- height: thumbSize,
113
- borderRadius: thumbSize / 2,
114
- },
115
- ]}
116
- className={cn(
117
- disabled
118
- ? "bg-gray-background-fill-muted"
119
- : hasIcons
120
- ? "bg-transparent"
121
- : isChecked
122
- ? "bg-primary-foreground-onfill"
123
- : "bg-gray-foreground-muted"
124
- )}
125
- >
126
- {hasIcons && (
127
- <View className="flex-1 items-center justify-center">
128
- {isChecked && checkedIcon}
129
- {!isChecked && uncheckedIcon}
134
+ <SwitchPrimitive.Thumb asChild>
135
+ <Animated.View
136
+ style={[
137
+ thumbStyle,
138
+ {
139
+ width: thumbSize,
140
+ height: thumbSize,
141
+ },
142
+ ]}
143
+ >
144
+ <View
145
+ className={cn(
146
+ "flex-1 items-center justify-center rounded-full",
147
+ disabled
148
+ ? "bg-gray-background-fill-muted"
149
+ : hasIcons
150
+ ? "bg-transparent"
151
+ : isChecked
152
+ ? "bg-primary-foreground-onfill"
153
+ : "bg-gray-foreground-muted"
154
+ )}
155
+ >
156
+ {hasIcons ? (
157
+ <>
158
+ {isChecked && checkedIcon}
159
+ {!isChecked && uncheckedIcon}
160
+ </>
161
+ ) : null}
130
162
  </View>
131
- )}
132
- </Animated.View>
163
+ </Animated.View>
164
+ </SwitchPrimitive.Thumb>
133
165
  </SwitchPrimitive.Root>
134
166
  );
135
167
  }
@@ -0,0 +1,46 @@
1
+ import type { ComponentType } from "react";
2
+
3
+ import { useThemeColor, type ThemeColorToken } from "@/hooks/useThemeColor";
4
+
5
+ type NativeIconProps = {
6
+ width?: number;
7
+ height?: number;
8
+ color?: string;
9
+ strokeWidth?: number;
10
+ className?: string;
11
+ };
12
+
13
+ interface ThemedIconProps {
14
+ icon: ComponentType<NativeIconProps>;
15
+ size?: number;
16
+ colorToken: ThemeColorToken;
17
+ strokeWidth?: number;
18
+ className?: string;
19
+ }
20
+
21
+ /**
22
+ * Ícone @medway-ui/icons com cor vinda do theme (CSS variables / NativeWind).
23
+ * Substitui `size` + `className` nos SVGs, que não resolvem cor sozinhos.
24
+ */
25
+ function ThemedIcon({
26
+ icon: Icon,
27
+ size = 24,
28
+ colorToken,
29
+ strokeWidth = 2,
30
+ className,
31
+ }: ThemedIconProps) {
32
+ const color = useThemeColor(colorToken);
33
+
34
+ return (
35
+ <Icon
36
+ width={size}
37
+ height={size}
38
+ color={color}
39
+ strokeWidth={strokeWidth}
40
+ className={className}
41
+ />
42
+ );
43
+ }
44
+
45
+ export { ThemedIcon };
46
+ export type { ThemedIconProps, NativeIconProps };
@@ -1,12 +1,5 @@
1
1
  import { useState, useEffect, type ReactElement, type ReactNode } from "react";
2
2
  import { View, Text, Pressable, type ViewProps } from "react-native";
3
- import {
4
- Info,
5
- CircleCheck,
6
- TriangleWarning,
7
- OctagonWarning,
8
- CloseMd,
9
- } from "@medway-ui/icons/native";
10
3
  import { cva } from "class-variance-authority";
11
4
  import Animated, {
12
5
  useSharedValue,
@@ -16,8 +9,16 @@ import Animated, {
16
9
  runOnJS,
17
10
  } from "react-native-reanimated";
18
11
  import { cn } from "@/lib/utils";
12
+ import {
13
+ CircleCheck,
14
+ CloseMd,
15
+ Info,
16
+ OctagonWarning,
17
+ TriangleWarning,
18
+ } from "@medway-ui/icons/native";
19
19
  import { Button, type ButtonProps } from "@/components/Button";
20
20
  import { useToast } from "@/hooks/use-toast";
21
+ import { useThemeColor } from "@/hooks/useThemeColor";
21
22
 
22
23
  type ToastVariant = "primary" | "secondary" | "success" | "warning" | "error";
23
24
  type ToastActionVariant = ToastVariant;
@@ -81,13 +82,21 @@ const toastActionButtonVariantMap: Record<ToastActionVariant, ButtonProps["varia
81
82
  error: "error-soft",
82
83
  };
83
84
 
84
- const variantIcons: Record<ToastVariant, typeof Info> = {
85
+ const toastIconColorTokens: Record<ToastVariant, string> = {
86
+ primary: "primary-foreground",
87
+ secondary: "secondary-foreground",
88
+ success: "feedback-success-foreground",
89
+ warning: "feedback-warning-foreground",
90
+ error: "feedback-error-foreground",
91
+ };
92
+
93
+ const variantIcons = {
85
94
  primary: Info,
86
95
  secondary: Info,
87
96
  success: CircleCheck,
88
97
  warning: TriangleWarning,
89
98
  error: OctagonWarning,
90
- };
99
+ } as const;
91
100
 
92
101
  function ToastItem({
93
102
  className,
@@ -106,6 +115,8 @@ function ToastItem({
106
115
  transform: [{ translateY: translateY.value }],
107
116
  opacity: opacity.value,
108
117
  }));
118
+ const iconColor = useThemeColor(toastIconColorTokens[variant]);
119
+ const closeIconColor = useThemeColor("gray-foreground");
109
120
 
110
121
  useEffect(() => {
111
122
  if (open) {
@@ -127,8 +138,8 @@ function ToastItem({
127
138
  return (
128
139
  <Animated.View style={animatedStyle}>
129
140
  <View className={cn(toastVariants({ variant }), className)} {...props}>
130
- {showIcon && (
131
- <IconComponent size={20} className={cn(toastIconVariants({ variant }))} />
141
+ {showIcon && iconColor && (
142
+ <IconComponent width={20} height={20} color={iconColor} />
132
143
  )}
133
144
  <View className="flex-1 flex-row items-center justify-between gap-3">
134
145
  {children}
@@ -137,7 +148,7 @@ function ToastItem({
137
148
  onPress={() => onOpenChange?.(false)}
138
149
  className="rounded-pill p-1 active:bg-gray-background-soft"
139
150
  >
140
- <CloseMd size={16} className="text-gray-foreground" />
151
+ {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
141
152
  </Pressable>
142
153
  </View>
143
154
  </Animated.View>
@@ -216,6 +227,8 @@ function ToastViewport({ className, children, ...props }: ViewProps & { children
216
227
  }
217
228
 
218
229
  function ToastClose({ className, onPress, label = "Close", ...props }: ToastCloseProps) {
230
+ const closeIconColor = useThemeColor("gray-foreground");
231
+
219
232
  return (
220
233
  <Pressable
221
234
  onPress={onPress}
@@ -223,7 +236,7 @@ function ToastClose({ className, onPress, label = "Close", ...props }: ToastClos
223
236
  className={cn("rounded-pill p-1 active:bg-gray-background-soft", className)}
224
237
  {...props}
225
238
  >
226
- <CloseMd size={16} className="text-gray-foreground" />
239
+ {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
227
240
  </Pressable>
228
241
  );
229
242
  }
@@ -27,6 +27,7 @@ function TooltipContent({
27
27
  }: TooltipContentProps) {
28
28
  return (
29
29
  <TooltipPrimitive.Portal>
30
+ <TooltipPrimitive.Overlay className="absolute inset-0" />
30
31
  <TooltipPrimitive.Content
31
32
  sideOffset={sideOffset}
32
33
  className={cn(
@@ -0,0 +1,79 @@
1
+ import { useColorScheme, useUnstableNativeVariable } from "nativewind";
2
+
3
+ /** Token sem prefixo `--color-` (ex.: `gray-foreground`, `primary-background-fill`). */
4
+ type ThemeColorToken = string;
5
+
6
+ /** Fallback quando NativeWind não resolve cadeias `var()` — light mode. */
7
+ const LIGHT_COLOR_ALIAS_MAP: Record<string, string> = {
8
+ "content-title": "primary",
9
+ "content-paragraph": "gray-600",
10
+ "gray-foreground": "gray-600",
11
+ "primary-foreground": "primary",
12
+ "secondary-foreground": "secondary-darken-3",
13
+ "surface-card": "white",
14
+ "gray-foreground-muted": "gray-450",
15
+ "gray-stroke-muted": "gray-100",
16
+ "gray-background-fill-muted": "gray-400",
17
+ "gray-background-soft-muted": "gray-30",
18
+ "primary-foreground-onfill": "white",
19
+ "primary-background-fill": "primary",
20
+ };
21
+
22
+ /** Fallback — dark mode (espelha tokens.css `.dark`). */
23
+ const DARK_COLOR_ALIAS_MAP: Record<string, string> = {
24
+ "content-title": "white",
25
+ "content-paragraph": "gray-150",
26
+ "gray-foreground": "gray-100",
27
+ "primary-foreground": "gray-100",
28
+ "secondary-foreground": "secondary-lighten-1",
29
+ "surface-card": "gray-850",
30
+ "gray-foreground-muted": "gray-150",
31
+ "gray-stroke-muted": "gray-700",
32
+ "gray-background-fill-muted": "gray-300",
33
+ "gray-background-soft-muted": "gray-700",
34
+ "primary-foreground-onfill": "primary",
35
+ "primary-background-fill": "gray-70",
36
+ };
37
+
38
+ function toCssVariable(token: ThemeColorToken): string {
39
+ return token.startsWith("--") ? token : `--color-${token}`;
40
+ }
41
+
42
+ function toTokenName(token: ThemeColorToken): string {
43
+ return token.startsWith("--color-") ? token.slice("--color-".length) : token;
44
+ }
45
+
46
+ function toResolvedCssVariable(
47
+ token: ThemeColorToken,
48
+ aliasMap: Record<string, string>
49
+ ): string {
50
+ const tokenName = toTokenName(token);
51
+ return toCssVariable(aliasMap[tokenName] ?? tokenName);
52
+ }
53
+
54
+ function isResolvedColor(value: unknown): value is string {
55
+ return typeof value === "string" && !value.startsWith("var(");
56
+ }
57
+
58
+ /**
59
+ * Resolve uma cor semântica do design system (tokens.native.css) em runtime.
60
+ * Use quando a prop `color` for obrigatória (SVG, stroke Reanimated, etc.).
61
+ */
62
+ function useThemeColor(token: ThemeColorToken): string | undefined {
63
+ const { colorScheme } = useColorScheme();
64
+ const aliasMap =
65
+ colorScheme === "dark" ? DARK_COLOR_ALIAS_MAP : LIGHT_COLOR_ALIAS_MAP;
66
+
67
+ const value = useUnstableNativeVariable(toCssVariable(token));
68
+ const resolvedValue = useUnstableNativeVariable(
69
+ toResolvedCssVariable(token, aliasMap)
70
+ );
71
+
72
+ if (isResolvedColor(value)) return value;
73
+ if (isResolvedColor(resolvedValue)) return resolvedValue;
74
+
75
+ return undefined;
76
+ }
77
+
78
+ export { useThemeColor, toCssVariable };
79
+ export type { ThemeColorToken };
package/src/index.ts CHANGED
@@ -17,6 +17,56 @@ export type { AlertButtonProps, AlertProps } from "@/components/Alert";
17
17
  export { Badge, badgeVariants } from "@/components/Badge";
18
18
  export type { BadgeAnimation, BadgeProps } from "@/components/Badge";
19
19
 
20
+ // Avatar
21
+ export {
22
+ Avatar,
23
+ AvatarFallback,
24
+ AvatarImage,
25
+ avatarVariants,
26
+ } from "@/components/Avatar";
27
+ export type {
28
+ AvatarFallbackProps,
29
+ AvatarImageProps,
30
+ AvatarProps,
31
+ } from "@/components/Avatar";
32
+
33
+ // FallbackImage
34
+ export { FallbackImage } from "@/components/FallbackImage";
35
+ export type { FallbackImageProps } from "@/components/FallbackImage";
36
+
37
+ // CardNews
38
+ export {
39
+ CardNews,
40
+ CardNewsAuthor,
41
+ CardNewsBody,
42
+ CardNewsContent,
43
+ CardNewsDate,
44
+ CardNewsDescription,
45
+ CardNewsFooter,
46
+ CardNewsHeader,
47
+ CardNewsImage,
48
+ CardNewsResourceItem,
49
+ CardNewsResources,
50
+ CardNewsSkeleton,
51
+ CardNewsTags,
52
+ CardNewsTitle,
53
+ } from "@/components/CardNews";
54
+ export type {
55
+ CardNewsAuthorProps,
56
+ CardNewsDateProps,
57
+ CardNewsImageProps,
58
+ CardNewsProps,
59
+ CardNewsResourcesProps,
60
+ } from "@/components/CardNews";
61
+
62
+ // EmptyState
63
+ export { EmptyState } from "@/components/EmptyState";
64
+ export type { EmptyStateProps } from "@/components/EmptyState";
65
+
66
+ // PageHeader
67
+ export { PageHeader } from "@/components/PageHeader";
68
+ export type { PageHeaderProps } from "@/components/PageHeader";
69
+
20
70
  // Button
21
71
  export { Button, buttonVariants, buttonTextVariants } from "@/components/Button";
22
72
  export type { ButtonProps } from "@/components/Button";
@@ -84,6 +134,10 @@ export type { SelectProps, SelectItemProps, SelectSize } from "@/components/Sele
84
134
  export { Separator } from "@/components/Separator";
85
135
  export type { SeparatorProps } from "@/components/Separator";
86
136
 
137
+ // Skeleton
138
+ export { Skeleton } from "@/components/Skeleton";
139
+ export type { SkeletonProps } from "@/components/Skeleton";
140
+
87
141
  // Switch
88
142
  export { Switch, switchVariants } from "@/components/Switch";
89
143
  export type { SwitchProps } from "@/components/Switch";
@@ -191,3 +245,9 @@ export type { TooltipContentProps } from "@/components/Tooltip";
191
245
 
192
246
  // Hooks
193
247
  export { useToast, toast } from "@/hooks/use-toast";
248
+ export { useThemeColor } from "@/hooks/useThemeColor";
249
+ export type { ThemeColorToken } from "@/hooks/useThemeColor";
250
+
251
+ // Theme-aware icon helper
252
+ export { ThemedIcon } from "@/components/ThemedIcon";
253
+ export type { ThemedIconProps } from "@/components/ThemedIcon";