@medway-ui/native 1.6.1 → 1.7.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 (224) 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 +41 -14
  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 +41 -14
  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/setup/tokens.native.css +268 -258
  189. package/src/components/Alert.tsx +3 -1
  190. package/src/components/AlertDialog.tsx +8 -3
  191. package/src/components/Badge.tsx +51 -3
  192. package/src/components/Button.tsx +39 -1
  193. package/src/components/Carousel.tsx +17 -3
  194. package/src/components/Checkbox.tsx +2 -0
  195. package/src/components/Chip.tsx +227 -0
  196. package/src/components/Choice.tsx +139 -0
  197. package/src/components/Dialog.tsx +39 -23
  198. package/src/components/DropdownMenu.tsx +99 -87
  199. package/src/components/EmptyState.tsx +18 -3
  200. package/src/components/Feed.tsx +4 -3
  201. package/src/components/Feedback.tsx +111 -0
  202. package/src/components/Filter.tsx +106 -0
  203. package/src/components/IndexLevel.tsx +8 -10
  204. package/src/components/Input.tsx +142 -54
  205. package/src/components/InvitePopover.tsx +5 -1
  206. package/src/components/MemberRow.tsx +3 -5
  207. package/src/components/PostForm.tsx +16 -9
  208. package/src/components/PrizeCollection.tsx +1 -1
  209. package/src/components/RadioGroup.tsx +8 -0
  210. package/src/components/Select.tsx +1 -1
  211. package/src/components/Sheet.tsx +4 -1
  212. package/src/components/Skeleton.tsx +47 -6
  213. package/src/components/Switch.tsx +2 -0
  214. package/src/components/Tabs.tsx +2 -0
  215. package/src/components/ThemedIcon.tsx +1 -1
  216. package/src/components/Toast.tsx +489 -87
  217. package/src/editor/NoteEditor.tsx +50 -11
  218. package/src/editor/editorHtml.generated.ts +1 -1
  219. package/src/hooks/useThemeColor.ts +757 -64
  220. package/src/hooks/useTrackedText.ts +20 -0
  221. package/src/index.ts +36 -1
  222. package/src/lib/optional-deps.ts +27 -0
  223. package/src/lib/touch-target.ts +12 -0
  224. package/setup/index.tsx +0 -42
@@ -1,5 +1,14 @@
1
- import { type ReactNode } from "react";
2
- import { View, type ViewProps } from "react-native";
1
+ import { useEffect, type ReactNode } from "react";
2
+ import { View, type LayoutChangeEvent, type ViewProps } from "react-native";
3
+ import Animated, {
4
+ Easing,
5
+ cancelAnimation,
6
+ interpolate,
7
+ useAnimatedStyle,
8
+ useSharedValue,
9
+ withRepeat,
10
+ withTiming,
11
+ } from "react-native-reanimated";
3
12
 
4
13
  import { Text } from "@/components/Text";
5
14
  import { cva, type VariantProps } from "class-variance-authority";
@@ -72,12 +81,50 @@ const badgeTextVariants = cva("font-semibold", {
72
81
  },
73
82
  });
74
83
 
84
+ const SHINE_DURATION_MS = 2000;
85
+
86
+ function BadgeShine() {
87
+ const progress = useSharedValue(0);
88
+ const width = useSharedValue(0);
89
+
90
+ const handleLayout = (event: LayoutChangeEvent) => {
91
+ width.value = event.nativeEvent.layout.width;
92
+ };
93
+
94
+ const bandStyle = useAnimatedStyle(() => ({
95
+ width: width.value / 2,
96
+ transform: [
97
+ { translateX: interpolate(progress.value, [0, 1], [width.value, -width.value / 2]) },
98
+ { skewX: "-20deg" },
99
+ ],
100
+ }));
101
+
102
+ useEffect(() => {
103
+ progress.value = withRepeat(
104
+ withTiming(1, { duration: SHINE_DURATION_MS, easing: Easing.linear }),
105
+ -1,
106
+ false
107
+ );
108
+ return () => cancelAnimation(progress);
109
+ }, [progress]);
110
+
111
+ return (
112
+ <View
113
+ pointerEvents="none"
114
+ className="absolute inset-0 overflow-hidden rounded-full"
115
+ onLayout={handleLayout}
116
+ >
117
+ <Animated.View className="absolute bottom-0 top-0 bg-white-24" style={bandStyle} />
118
+ </View>
119
+ );
120
+ }
121
+
75
122
  function Badge({
76
123
  className,
77
124
  variant,
78
125
  size,
79
126
  icon,
80
- animation: _animation,
127
+ animation,
81
128
  children,
82
129
  ...props
83
130
  }: BadgeProps) {
@@ -101,6 +148,7 @@ function Badge({
101
148
  )}
102
149
  {...props}
103
150
  >
151
+ {animation === "shine" ? <BadgeShine /> : null}
104
152
  {icon}
105
153
  {children != null && (
106
154
  <Text className={cn(badgeTextVariants({ variant, size }))}>
@@ -6,6 +6,7 @@ import { cva, type VariantProps } from "class-variance-authority";
6
6
  import { cn } from "@/lib/utils";
7
7
  import { Loading } from "@/components/Loading";
8
8
  import { Badge as BadgeComponent } from "@/components/Badge";
9
+ import { type ThemeColorToken } from "@/hooks/useThemeColor";
9
10
 
10
11
  interface ButtonProps extends Omit<PressableProps, "children">, VariantProps<typeof buttonVariants> {
11
12
  children?: ReactNode;
@@ -17,6 +18,8 @@ interface ButtonProps extends Omit<PressableProps, "children">, VariantProps<typ
17
18
  alert?: string;
18
19
  }
19
20
 
21
+ type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>;
22
+
20
23
  const ICON_ONLY_SIZES: Record<"sm" | "md" | "lg" | "xl", string> = {
21
24
  sm: "h-8 min-h-8 w-8",
22
25
  md: "h-10 min-h-10 w-10",
@@ -38,6 +41,38 @@ const ALERT_DOT_SIZES: Record<"sm" | "md" | "lg" | "xl", number> = {
38
41
  xl: 12,
39
42
  };
40
43
 
44
+ const LOADING_SIZES: Record<"sm" | "md" | "lg" | "xl", number> = {
45
+ sm: 16,
46
+ md: 20,
47
+ lg: 20,
48
+ xl: 24,
49
+ };
50
+
51
+ const FILLED_LOADING_COLOR: ThemeColorToken = "gray-foreground-onfill";
52
+ const SOFT_LOADING_COLOR: ThemeColorToken = "gray-foreground-muted";
53
+
54
+ // Loading desabilita o Pressable, então o spinner segue a cor de texto do estado disabled.
55
+ const LOADING_COLOR_TOKENS: Record<ButtonVariant, ThemeColorToken> = {
56
+ regular: FILLED_LOADING_COLOR,
57
+ "regular-soft": SOFT_LOADING_COLOR,
58
+ "regular-basic": SOFT_LOADING_COLOR,
59
+ emphasis: FILLED_LOADING_COLOR,
60
+ "emphasis-soft": SOFT_LOADING_COLOR,
61
+ "emphasis-basic": SOFT_LOADING_COLOR,
62
+ error: FILLED_LOADING_COLOR,
63
+ "error-soft": SOFT_LOADING_COLOR,
64
+ "error-basic": SOFT_LOADING_COLOR,
65
+ warning: FILLED_LOADING_COLOR,
66
+ "warning-soft": SOFT_LOADING_COLOR,
67
+ "warning-basic": SOFT_LOADING_COLOR,
68
+ success: FILLED_LOADING_COLOR,
69
+ "success-soft": SOFT_LOADING_COLOR,
70
+ "success-basic": SOFT_LOADING_COLOR,
71
+ outlined: SOFT_LOADING_COLOR,
72
+ gradient: FILLED_LOADING_COLOR,
73
+ medbrain: FILLED_LOADING_COLOR,
74
+ };
75
+
41
76
  const buttonVariants = cva(
42
77
  "flex-row items-center justify-center gap-2 rounded-pill font-semibold",
43
78
  {
@@ -175,7 +210,10 @@ function Button({
175
210
  {loading ? (
176
211
  <>
177
212
  <View className="absolute">
178
- <Loading size={resolvedSize === "sm" ? 16 : resolvedSize === "xl" ? 24 : 20} />
213
+ <Loading
214
+ size={LOADING_SIZES[resolvedSize]}
215
+ colorToken={LOADING_COLOR_TOKENS[resolvedVariant]}
216
+ />
179
217
  </View>
180
218
  <View className="opacity-0 flex-row items-center gap-2">
181
219
  {iconLeft}
@@ -5,6 +5,7 @@ import { Text } from "@/components/Text";
5
5
  import { ChevronLeftMd, ChevronRightMd } from "@medway-ui/icons/native";
6
6
 
7
7
  import { Button } from "@/components/Button";
8
+ import { useThemeColor } from "@/hooks/useThemeColor";
8
9
  import { cn } from "@/lib/utils";
9
10
 
10
11
  interface CarouselProps extends ViewProps {
@@ -31,13 +32,23 @@ interface CarouselMetricSlideProps extends ViewProps {
31
32
  description: string;
32
33
  }
33
34
 
35
+ function getSlidePositionLabel(slideIndex?: number, slideCount?: number) {
36
+ if (slideIndex === undefined || slideCount === undefined) return undefined;
37
+ return `Slide ${slideIndex + 1} de ${slideCount}`;
38
+ }
39
+
34
40
  function Carousel({
35
41
  className,
36
42
  children,
43
+ slideIndex,
44
+ slideCount,
37
45
  onPrevious,
38
46
  onNext,
39
47
  ...props
40
48
  }: CarouselProps) {
49
+ const chevronColor = useThemeColor("primary-foreground");
50
+ const slidePositionLabel = getSlidePositionLabel(slideIndex, slideCount);
51
+
41
52
  return (
42
53
  <View
43
54
  className={cn(
@@ -50,18 +61,21 @@ function Carousel({
50
61
  variant="regular-soft"
51
62
  size="md"
52
63
  className="size-10 min-h-10 min-w-10 max-w-10 p-0"
53
- iconLeft={<ChevronLeftMd width={20} height={20} color="#00205B" />}
64
+ iconLeft={<ChevronLeftMd width={20} height={20} color={chevronColor} />}
54
65
  onPress={onPrevious}
55
66
  accessibilityLabel="Slide anterior"
56
67
  />
57
- <View className="h-full min-w-0 flex-1 items-center justify-center px-4">
68
+ <View
69
+ className="h-full min-w-0 flex-1 items-center justify-center px-4"
70
+ accessibilityLabel={slidePositionLabel}
71
+ >
58
72
  {children}
59
73
  </View>
60
74
  <Button
61
75
  variant="regular-soft"
62
76
  size="md"
63
77
  className="size-10 min-h-10 min-w-10 max-w-10 p-0"
64
- iconLeft={<ChevronRightMd width={20} height={20} color="#00205B" />}
78
+ iconLeft={<ChevronRightMd width={20} height={20} color={chevronColor} />}
65
79
  onPress={onNext}
66
80
  accessibilityLabel="Próximo slide"
67
81
  />
@@ -4,6 +4,7 @@ import { cssInterop } from "nativewind";
4
4
  import * as CheckboxPrimitive from "@rn-primitives/checkbox";
5
5
  import { cva, type VariantProps } from "class-variance-authority";
6
6
  import { cn } from "@/lib/utils";
7
+ import { getMinTouchHitSlop } from "@/lib/touch-target";
7
8
  import { Check, RemoveMinus } from "@medway-ui/icons/native";
8
9
  import { useThemeColor } from "@/hooks/useThemeColor";
9
10
 
@@ -113,6 +114,7 @@ function Checkbox({
113
114
  checked={isChecked}
114
115
  onCheckedChange={handleCheckedChange}
115
116
  disabled={disabled}
117
+ hitSlop={getMinTouchHitSlop(dimension)}
116
118
  style={(state) => [
117
119
  rootStyle,
118
120
  typeof style === "function" ? style(state) : style,
@@ -0,0 +1,227 @@
1
+ import { type ReactNode } from "react";
2
+ import { Pressable, View } from "react-native";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+ import { CloseSm } from "@medway-ui/icons/native";
5
+
6
+ import { Text } from "@/components/Text";
7
+ import { ThemedIcon } from "@/components/ThemedIcon";
8
+ import { cn } from "@/lib/utils";
9
+
10
+ const chipVariants = cva(
11
+ "flex-row shrink-0 items-center justify-center rounded-sm",
12
+ {
13
+ variants: {
14
+ size: {
15
+ sm: "gap-2 px-3 py-2",
16
+ md: "gap-2.5 px-3.5 py-2.5",
17
+ lg: "gap-3 px-4 py-3",
18
+ },
19
+ disabled: { true: "", false: "" },
20
+ },
21
+ defaultVariants: { size: "lg", disabled: false },
22
+ }
23
+ );
24
+
25
+ const chipBackgroundVariants = cva("", {
26
+ variants: {
27
+ variant: {
28
+ regular: "bg-primary-background-fill",
29
+ "regular-soft": "bg-primary-background-soft",
30
+ emphasis: "bg-secondary-background-fill",
31
+ "emphasis-soft": "bg-secondary-background-soft",
32
+ gray: "bg-gray-background-fill",
33
+ "gray-soft": "bg-gray-background-soft",
34
+ success: "bg-green-background-fill",
35
+ "success-soft": "bg-green-background-soft",
36
+ error: "bg-red-background-fill",
37
+ "error-soft": "bg-red-background-soft",
38
+ warning: "bg-yellow-background-fill",
39
+ "warning-soft": "bg-yellow-background-soft",
40
+ go: "bg-go-background-fill",
41
+ "go-soft": "bg-go-background-soft",
42
+ cm: "bg-cm-background-fill",
43
+ "cm-soft": "bg-cm-background-soft",
44
+ cir: "bg-cir-background-fill",
45
+ "cir-soft": "bg-cir-background-soft",
46
+ ped: "bg-ped-background-fill",
47
+ "ped-soft": "bg-ped-background-soft",
48
+ prev: "bg-prev-background-fill",
49
+ "prev-soft": "bg-prev-background-soft",
50
+ ia: "bg-secondary-background-fill",
51
+ "ia-soft": "bg-secondary-background-soft",
52
+ },
53
+ disabled: {
54
+ true: "bg-gray-background-soft-muted",
55
+ false: "",
56
+ },
57
+ },
58
+ compoundVariants: [
59
+ {
60
+ variant: undefined,
61
+ disabled: false,
62
+ className: "bg-surface-elevated shadow-sm",
63
+ },
64
+ ],
65
+ defaultVariants: { disabled: false },
66
+ });
67
+
68
+ const chipTextVariants = cva("text-center font-semibold", {
69
+ variants: {
70
+ size: {
71
+ sm: "text-xs",
72
+ md: "text-sm",
73
+ lg: "text-md",
74
+ },
75
+ variant: {
76
+ regular: "text-primary-foreground-onfill",
77
+ "regular-soft": "text-primary-foreground",
78
+ emphasis: "text-secondary-foreground-onfill",
79
+ "emphasis-soft": "text-secondary-foreground",
80
+ gray: "text-gray-foreground-onfill",
81
+ "gray-soft": "text-gray-foreground",
82
+ success: "text-green-foreground-onfill",
83
+ "success-soft": "text-green-foreground",
84
+ error: "text-red-foreground-onfill",
85
+ "error-soft": "text-red-foreground",
86
+ warning: "text-yellow-foreground-onfill",
87
+ "warning-soft": "text-yellow-foreground",
88
+ go: "text-go-foreground-onfill",
89
+ "go-soft": "text-go-foreground",
90
+ cm: "text-cm-foreground-onfill",
91
+ "cm-soft": "text-cm-foreground",
92
+ cir: "text-cir-foreground-onfill",
93
+ "cir-soft": "text-cir-foreground",
94
+ ped: "text-ped-foreground-onfill",
95
+ "ped-soft": "text-ped-foreground",
96
+ prev: "text-prev-foreground-onfill",
97
+ "prev-soft": "text-prev-foreground",
98
+ ia: "text-primary-foreground-onfill",
99
+ "ia-soft": "text-secondary-foreground",
100
+ },
101
+ disabled: {
102
+ true: "font-medium text-gray-foreground-muted",
103
+ false: "",
104
+ },
105
+ },
106
+ compoundVariants: [
107
+ {
108
+ variant: undefined,
109
+ disabled: false,
110
+ className: "text-primary-foreground",
111
+ },
112
+ ],
113
+ defaultVariants: { size: "lg", disabled: false },
114
+ });
115
+
116
+ type ChipSize = NonNullable<VariantProps<typeof chipVariants>["size"]>;
117
+ type ChipVariant = NonNullable<
118
+ VariantProps<typeof chipBackgroundVariants>["variant"]
119
+ >;
120
+
121
+ const CHIP_ICON_SIZE: Record<ChipSize, number> = {
122
+ sm: 16,
123
+ md: 20,
124
+ lg: 24,
125
+ };
126
+
127
+ const CHIP_ICON_COLOR_TOKEN: Record<ChipVariant, string> = {
128
+ regular: "primary-foreground-onfill",
129
+ "regular-soft": "primary-foreground",
130
+ emphasis: "secondary-foreground-onfill",
131
+ "emphasis-soft": "secondary-foreground",
132
+ gray: "gray-foreground-onfill",
133
+ "gray-soft": "gray-foreground",
134
+ success: "green-foreground-onfill",
135
+ "success-soft": "green-foreground",
136
+ error: "red-foreground-onfill",
137
+ "error-soft": "red-foreground",
138
+ warning: "yellow-foreground-onfill",
139
+ "warning-soft": "yellow-foreground",
140
+ go: "go-foreground-onfill",
141
+ "go-soft": "go-foreground",
142
+ cm: "cm-foreground-onfill",
143
+ "cm-soft": "cm-foreground",
144
+ cir: "cir-foreground-onfill",
145
+ "cir-soft": "cir-foreground",
146
+ ped: "ped-foreground-onfill",
147
+ "ped-soft": "ped-foreground",
148
+ prev: "prev-foreground-onfill",
149
+ "prev-soft": "prev-foreground",
150
+ ia: "primary-foreground-onfill",
151
+ "ia-soft": "secondary-foreground",
152
+ };
153
+
154
+ interface ChipProps
155
+ extends Omit<VariantProps<typeof chipVariants>, "disabled"> {
156
+ children: ReactNode;
157
+ variant?: ChipVariant;
158
+ disabled?: boolean;
159
+ onPress?: () => void;
160
+ onDismiss?: () => void;
161
+ dismissAccessibilityLabel?: string;
162
+ className?: string;
163
+ }
164
+
165
+ function Chip({
166
+ children,
167
+ size = "lg",
168
+ variant,
169
+ disabled = false,
170
+ onPress,
171
+ onDismiss,
172
+ dismissAccessibilityLabel,
173
+ className,
174
+ }: ChipProps) {
175
+ const iconSize = CHIP_ICON_SIZE[size ?? "lg"];
176
+ const variantIconColor = variant
177
+ ? CHIP_ICON_COLOR_TOKEN[variant]
178
+ : "primary-foreground";
179
+ const iconColor = disabled ? "gray-foreground-muted" : variantIconColor;
180
+
181
+ const containerClass = cn(
182
+ chipVariants({ size, disabled }),
183
+ chipBackgroundVariants({ variant, disabled }),
184
+ className
185
+ );
186
+
187
+ const content = (
188
+ <>
189
+ <Text className={cn(chipTextVariants({ size, variant, disabled }))}>
190
+ {children}
191
+ </Text>
192
+ {onDismiss ? (
193
+ <Pressable
194
+ onPress={onDismiss}
195
+ disabled={disabled}
196
+ accessibilityRole="button"
197
+ accessibilityLabel={dismissAccessibilityLabel ?? "Remover"}
198
+ hitSlop={6}
199
+ >
200
+ <ThemedIcon
201
+ icon={CloseSm as never}
202
+ size={iconSize}
203
+ colorToken={iconColor}
204
+ />
205
+ </Pressable>
206
+ ) : null}
207
+ </>
208
+ );
209
+
210
+ if (onPress) {
211
+ return (
212
+ <Pressable
213
+ onPress={onPress}
214
+ disabled={disabled}
215
+ accessibilityRole="button"
216
+ className={containerClass}
217
+ >
218
+ {content}
219
+ </Pressable>
220
+ );
221
+ }
222
+
223
+ return <View className={containerClass}>{content}</View>;
224
+ }
225
+
226
+ export { Chip, chipVariants, chipBackgroundVariants, chipTextVariants };
227
+ export type { ChipProps, ChipSize, ChipVariant };
@@ -0,0 +1,139 @@
1
+ import type { ReactNode } from "react";
2
+ import { Pressable, View } from "react-native";
3
+ import * as MedwayIcons from "@medway-ui/icons/native";
4
+
5
+ import { Text } from "@/components/Text";
6
+ import { ThemedIcon } from "@/components/ThemedIcon";
7
+ import { useThemeColor } from "@/hooks/useThemeColor";
8
+ import { cn } from "@/lib/utils";
9
+
10
+ type ChoiceSize = "sm" | "md" | "lg";
11
+
12
+ type ChoiceTheme =
13
+ | "gray"
14
+ | "primary"
15
+ | "secondary"
16
+ | "green"
17
+ | "red"
18
+ | "yellow"
19
+ | "cm"
20
+ | "go"
21
+ | "cir"
22
+ | "ped"
23
+ | "prev";
24
+
25
+ interface ChoiceProps {
26
+ label: string;
27
+ selected?: boolean;
28
+ disabled?: boolean;
29
+ size?: ChoiceSize;
30
+ theme?: ChoiceTheme;
31
+ icon?: ReactNode;
32
+ onPress?: () => void;
33
+ className?: string;
34
+ }
35
+
36
+ const SIZE_MAP: Record<
37
+ ChoiceSize,
38
+ { container: string; text: string; icon: number }
39
+ > = {
40
+ sm: { container: "gap-2 px-3 py-2", text: "text-xs", icon: 16 },
41
+ md: { container: "gap-2.5 px-3.5 py-2.5", text: "text-sm", icon: 20 },
42
+ lg: { container: "gap-3 px-4 py-3", text: "text-base", icon: 24 },
43
+ };
44
+
45
+ const THEME_SELECTED_OUTER_BG: Record<ChoiceTheme, string> = {
46
+ gray: "bg-gray-background-soft",
47
+ primary: "bg-primary-background-fill",
48
+ secondary: "bg-secondary-background-fill",
49
+ green: "bg-green-background-fill",
50
+ red: "bg-red-background-fill",
51
+ yellow: "bg-yellow-background-fill",
52
+ cm: "bg-cm-background-fill",
53
+ go: "bg-go-background-fill",
54
+ cir: "bg-cir-background-fill-emphasis",
55
+ ped: "bg-ped-background-fill-emphasis",
56
+ prev: "bg-prev-background-fill",
57
+ };
58
+
59
+ const THEME_CHECK_COLOR_TOKEN: Record<ChoiceTheme, string> = {
60
+ gray: "secondary-foreground",
61
+ primary: "primary-foreground",
62
+ secondary: "secondary-foreground",
63
+ green: "green-foreground",
64
+ red: "red-foreground",
65
+ yellow: "yellow-foreground",
66
+ cm: "cm-foreground",
67
+ go: "go-foreground",
68
+ cir: "cir-foreground",
69
+ ped: "ped-foreground",
70
+ prev: "prev-foreground",
71
+ };
72
+
73
+ const SHADOW_STYLE = {
74
+ shadowOffset: { width: 0, height: 2 },
75
+ shadowOpacity: 0.08,
76
+ shadowRadius: 4,
77
+ elevation: 2,
78
+ };
79
+
80
+ function Choice({
81
+ label,
82
+ selected = false,
83
+ disabled = false,
84
+ size = "md",
85
+ theme = "gray",
86
+ icon,
87
+ onPress,
88
+ className,
89
+ }: ChoiceProps) {
90
+ const sz = SIZE_MAP[size] ?? SIZE_MAP.md;
91
+ const shadowColor = useThemeColor("gray-700");
92
+
93
+ const outerBg = disabled
94
+ ? "bg-gray-background-soft-muted"
95
+ : selected
96
+ ? THEME_SELECTED_OUTER_BG[theme]
97
+ : "bg-gray-background-soft";
98
+
99
+ const textClass = disabled
100
+ ? "font-medium text-gray-foreground-muted"
101
+ : selected
102
+ ? "font-semibold text-primary-foreground"
103
+ : "font-medium text-gray-foreground";
104
+
105
+ return (
106
+ <Pressable
107
+ onPress={disabled ? undefined : onPress}
108
+ disabled={disabled}
109
+ accessibilityRole="button"
110
+ accessibilityState={{ selected, disabled }}
111
+ className={cn("rounded-md", outerBg, className)}
112
+ style={{ padding: 2 }}
113
+ >
114
+ <View
115
+ className={cn(
116
+ "flex-row items-center justify-center rounded-md",
117
+ sz.container,
118
+ selected ? "bg-surface-card" : "bg-transparent",
119
+ )}
120
+ style={selected ? { ...SHADOW_STYLE, shadowColor } : undefined}
121
+ >
122
+ {icon ? <View className="shrink-0">{icon}</View> : null}
123
+ <Text className={cn("text-center", sz.text, textClass)}>{label}</Text>
124
+ {selected ? (
125
+ <View className="shrink-0">
126
+ <ThemedIcon
127
+ icon={MedwayIcons.Check}
128
+ size={sz.icon}
129
+ colorToken={THEME_CHECK_COLOR_TOKEN[theme]}
130
+ />
131
+ </View>
132
+ ) : null}
133
+ </View>
134
+ </Pressable>
135
+ );
136
+ }
137
+
138
+ export { Choice };
139
+ export type { ChoiceProps, ChoiceTheme };
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode, isValidElement, cloneElement, type ReactElement } from "react";
2
- import { View, Text, ScrollView, type ViewProps } from "react-native";
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
5
  import { useThemeColor } from "@/hooks/useThemeColor";
@@ -43,7 +43,7 @@ function DialogClose({ ...props }: DialogPrimitive.CloseProps) {
43
43
  function DialogOverlay({ className, ...props }: DialogPrimitive.OverlayProps) {
44
44
  return (
45
45
  <DialogPrimitive.Overlay
46
- className={cn("absolute inset-0 bg-black/50", className)}
46
+ className={cn("absolute inset-0 bg-black-48", className)}
47
47
  {...props}
48
48
  />
49
49
  );
@@ -61,7 +61,7 @@ function DialogContainer({
61
61
  return (
62
62
  <DialogPrimitive.Portal>
63
63
  <DialogPrimitive.Overlay
64
- className={cn("absolute inset-0 bg-black/50", overlayClassName)}
64
+ className={cn("absolute inset-0 bg-black-48", overlayClassName)}
65
65
  />
66
66
  <DialogPrimitive.Content
67
67
  className="absolute inset-0 items-center justify-center px-4"
@@ -75,7 +75,12 @@ function DialogContainer({
75
75
  >
76
76
  {children}
77
77
  {showCloseButton && (
78
- <DialogPrimitive.Close className="absolute right-3 top-3 rounded-pill p-1.5 active:bg-gray-background-soft">
78
+ <DialogPrimitive.Close
79
+ accessibilityRole="button"
80
+ accessibilityLabel="Fechar"
81
+ hitSlop={8}
82
+ className="absolute right-3 top-3 rounded-pill p-1.5 active:bg-gray-background-soft"
83
+ >
79
84
  {closeIconColor && <CloseMd width={16} height={16} color={closeIconColor} />}
80
85
  </DialogPrimitive.Close>
81
86
  )}
@@ -131,28 +136,39 @@ function DialogFooter({ className, separator = false, children, ...props }: Dial
131
136
  );
132
137
  }
133
138
 
134
- function DialogTitle({ className, iconLeft, iconRight, children, ...props }: DialogTitleProps) {
135
- const renderIcon = (icon: ReactNode) => {
136
- if (isValidElement(icon)) {
137
- return cloneElement(icon as ReactElement<{ size?: number; strokeWidth?: number }>, {
138
- size: 32,
139
- strokeWidth: 2.2,
140
- });
139
+ const DIALOG_TITLE_ICON_SIZE = 32;
140
+ const DIALOG_TITLE_CLASS_NAME = "text-title-md font-semibold leading-title-md text-content-title";
141
+
142
+ function renderTitleIcon(icon: ReactNode) {
143
+ if (!isValidElement(icon)) return icon;
144
+
145
+ return cloneElement(
146
+ icon as ReactElement<{ width?: number; height?: number; strokeWidth?: number }>,
147
+ {
148
+ width: DIALOG_TITLE_ICON_SIZE,
149
+ height: DIALOG_TITLE_ICON_SIZE,
150
+ strokeWidth: 2.2,
141
151
  }
142
- return icon;
143
- };
152
+ );
153
+ }
154
+
155
+ function DialogTitle({ className, iconLeft, iconRight, children, ...props }: DialogTitleProps) {
156
+ if (!iconLeft && !iconRight) {
157
+ return (
158
+ <DialogPrimitive.Title className={cn(DIALOG_TITLE_CLASS_NAME, className)} {...props}>
159
+ {children}
160
+ </DialogPrimitive.Title>
161
+ );
162
+ }
144
163
 
145
164
  return (
146
- <DialogPrimitive.Title
147
- className={cn("flex-row items-center gap-3", className)}
148
- {...props}
149
- >
150
- {iconLeft && renderIcon(iconLeft)}
151
- <Text className="text-title-md font-semibold leading-title-md text-content-title flex-1">
152
- {children as string}
153
- </Text>
154
- {iconRight && renderIcon(iconRight)}
155
- </DialogPrimitive.Title>
165
+ <View className={cn("flex-row items-center gap-3", className)}>
166
+ {iconLeft ? renderTitleIcon(iconLeft) : null}
167
+ <DialogPrimitive.Title className={cn(DIALOG_TITLE_CLASS_NAME, "flex-1")} {...props}>
168
+ {children}
169
+ </DialogPrimitive.Title>
170
+ {iconRight ? renderTitleIcon(iconRight) : null}
171
+ </View>
156
172
  );
157
173
  }
158
174