@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.
- package/README.md +27 -2
- package/lib/commonjs/components/Alert.js +3 -1
- package/lib/commonjs/components/Alert.js.map +1 -1
- package/lib/commonjs/components/AlertDialog.js +5 -2
- package/lib/commonjs/components/AlertDialog.js.map +1 -1
- package/lib/commonjs/components/Badge.js +37 -2
- package/lib/commonjs/components/Badge.js.map +1 -1
- package/lib/commonjs/components/Button.js +32 -1
- package/lib/commonjs/components/Button.js.map +1 -1
- package/lib/commonjs/components/Carousel.js +12 -2
- package/lib/commonjs/components/Carousel.js.map +1 -1
- package/lib/commonjs/components/Checkbox.js +2 -0
- package/lib/commonjs/components/Checkbox.js.map +1 -1
- package/lib/commonjs/components/Chip.js +208 -0
- package/lib/commonjs/components/Chip.js.map +1 -0
- package/lib/commonjs/components/Choice.js +116 -0
- package/lib/commonjs/components/Choice.js.map +1 -0
- package/lib/commonjs/components/Dialog.js +27 -16
- package/lib/commonjs/components/Dialog.js.map +1 -1
- package/lib/commonjs/components/DropdownMenu.js +73 -57
- package/lib/commonjs/components/DropdownMenu.js.map +1 -1
- package/lib/commonjs/components/EmptyState.js +9 -2
- package/lib/commonjs/components/EmptyState.js.map +1 -1
- package/lib/commonjs/components/Feed.js +7 -3
- package/lib/commonjs/components/Feed.js.map +1 -1
- package/lib/commonjs/components/Feedback.js +77 -0
- package/lib/commonjs/components/Feedback.js.map +1 -0
- package/lib/commonjs/components/Filter.js +93 -0
- package/lib/commonjs/components/Filter.js.map +1 -0
- package/lib/commonjs/components/IndexLevel.js +4 -3
- package/lib/commonjs/components/IndexLevel.js.map +1 -1
- package/lib/commonjs/components/Input.js +149 -45
- package/lib/commonjs/components/Input.js.map +1 -1
- package/lib/commonjs/components/InvitePopover.js +4 -1
- package/lib/commonjs/components/InvitePopover.js.map +1 -1
- package/lib/commonjs/components/MemberRow.js +3 -4
- package/lib/commonjs/components/MemberRow.js.map +1 -1
- package/lib/commonjs/components/PostForm.js +18 -8
- package/lib/commonjs/components/PostForm.js.map +1 -1
- package/lib/commonjs/components/PrizeCollection.js +1 -1
- package/lib/commonjs/components/RadioGroup.js +7 -0
- package/lib/commonjs/components/RadioGroup.js.map +1 -1
- package/lib/commonjs/components/Select.js +1 -1
- package/lib/commonjs/components/Sheet.js +4 -1
- package/lib/commonjs/components/Sheet.js.map +1 -1
- package/lib/commonjs/components/Skeleton.js +55 -4
- package/lib/commonjs/components/Skeleton.js.map +1 -1
- package/lib/commonjs/components/Switch.js +2 -0
- package/lib/commonjs/components/Switch.js.map +1 -1
- package/lib/commonjs/components/Tabs.js +2 -0
- package/lib/commonjs/components/Tabs.js.map +1 -1
- package/lib/commonjs/components/Toast.js +355 -79
- package/lib/commonjs/components/Toast.js.map +1 -1
- package/lib/commonjs/editor/NoteEditor.js +41 -14
- package/lib/commonjs/editor/NoteEditor.js.map +1 -1
- package/lib/commonjs/editor/editorHtml.generated.js +1 -1
- package/lib/commonjs/editor/editorHtml.generated.js.map +1 -1
- package/lib/commonjs/hooks/useThemeColor.js +693 -65
- package/lib/commonjs/hooks/useThemeColor.js.map +1 -1
- package/lib/commonjs/hooks/useTrackedText.js +21 -0
- package/lib/commonjs/hooks/useTrackedText.js.map +1 -0
- package/lib/commonjs/index.js +46 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/lib/optional-deps.js +20 -0
- package/lib/commonjs/lib/optional-deps.js.map +1 -0
- package/lib/commonjs/lib/touch-target.js +19 -0
- package/lib/commonjs/lib/touch-target.js.map +1 -0
- package/lib/module/components/Alert.js +3 -1
- package/lib/module/components/Alert.js.map +1 -1
- package/lib/module/components/AlertDialog.js +5 -2
- package/lib/module/components/AlertDialog.js.map +1 -1
- package/lib/module/components/Badge.js +36 -2
- package/lib/module/components/Badge.js.map +1 -1
- package/lib/module/components/Button.js +32 -1
- package/lib/module/components/Button.js.map +1 -1
- package/lib/module/components/Carousel.js +12 -2
- package/lib/module/components/Carousel.js.map +1 -1
- package/lib/module/components/Checkbox.js +2 -0
- package/lib/module/components/Checkbox.js.map +1 -1
- package/lib/module/components/Chip.js +204 -0
- package/lib/module/components/Chip.js.map +1 -0
- package/lib/module/components/Choice.js +112 -0
- package/lib/module/components/Choice.js.map +1 -0
- package/lib/module/components/Dialog.js +28 -17
- package/lib/module/components/Dialog.js.map +1 -1
- package/lib/module/components/DropdownMenu.js +74 -58
- package/lib/module/components/DropdownMenu.js.map +1 -1
- package/lib/module/components/EmptyState.js +9 -2
- package/lib/module/components/EmptyState.js.map +1 -1
- package/lib/module/components/Feed.js +7 -3
- package/lib/module/components/Feed.js.map +1 -1
- package/lib/module/components/Feedback.js +74 -0
- package/lib/module/components/Feedback.js.map +1 -0
- package/lib/module/components/Filter.js +89 -0
- package/lib/module/components/Filter.js.map +1 -0
- package/lib/module/components/IndexLevel.js +4 -3
- package/lib/module/components/IndexLevel.js.map +1 -1
- package/lib/module/components/Input.js +149 -45
- package/lib/module/components/Input.js.map +1 -1
- package/lib/module/components/InvitePopover.js +4 -1
- package/lib/module/components/InvitePopover.js.map +1 -1
- package/lib/module/components/MemberRow.js +3 -4
- package/lib/module/components/MemberRow.js.map +1 -1
- package/lib/module/components/PostForm.js +18 -8
- package/lib/module/components/PostForm.js.map +1 -1
- package/lib/module/components/PrizeCollection.js +1 -1
- package/lib/module/components/RadioGroup.js +7 -0
- package/lib/module/components/RadioGroup.js.map +1 -1
- package/lib/module/components/Select.js +1 -1
- package/lib/module/components/Sheet.js +4 -1
- package/lib/module/components/Sheet.js.map +1 -1
- package/lib/module/components/Skeleton.js +56 -5
- package/lib/module/components/Skeleton.js.map +1 -1
- package/lib/module/components/Switch.js +2 -0
- package/lib/module/components/Switch.js.map +1 -1
- package/lib/module/components/Tabs.js +2 -0
- package/lib/module/components/Tabs.js.map +1 -1
- package/lib/module/components/Toast.js +360 -84
- package/lib/module/components/Toast.js.map +1 -1
- package/lib/module/editor/NoteEditor.js +41 -14
- package/lib/module/editor/NoteEditor.js.map +1 -1
- package/lib/module/editor/editorHtml.generated.js +1 -1
- package/lib/module/editor/editorHtml.generated.js.map +1 -1
- package/lib/module/hooks/useThemeColor.js +693 -65
- package/lib/module/hooks/useThemeColor.js.map +1 -1
- package/lib/module/hooks/useTrackedText.js +18 -0
- package/lib/module/hooks/useTrackedText.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/lib/optional-deps.js +16 -0
- package/lib/module/lib/optional-deps.js.map +1 -0
- package/lib/module/lib/touch-target.js +15 -0
- package/lib/module/lib/touch-target.js.map +1 -0
- package/lib/typescript/components/Alert.d.ts.map +1 -1
- package/lib/typescript/components/AlertDialog.d.ts.map +1 -1
- package/lib/typescript/components/Badge.d.ts +1 -1
- package/lib/typescript/components/Badge.d.ts.map +1 -1
- package/lib/typescript/components/Button.d.ts.map +1 -1
- package/lib/typescript/components/Carousel.d.ts +1 -1
- package/lib/typescript/components/Carousel.d.ts.map +1 -1
- package/lib/typescript/components/Checkbox.d.ts.map +1 -1
- package/lib/typescript/components/Chip.d.ts +30 -0
- package/lib/typescript/components/Chip.d.ts.map +1 -0
- package/lib/typescript/components/Choice.d.ts +17 -0
- package/lib/typescript/components/Choice.d.ts.map +1 -0
- package/lib/typescript/components/Dialog.d.ts.map +1 -1
- package/lib/typescript/components/DropdownMenu.d.ts +0 -7
- package/lib/typescript/components/DropdownMenu.d.ts.map +1 -1
- package/lib/typescript/components/EmptyState.d.ts +3 -2
- package/lib/typescript/components/EmptyState.d.ts.map +1 -1
- package/lib/typescript/components/Feed.d.ts.map +1 -1
- package/lib/typescript/components/Feedback.d.ts +17 -0
- package/lib/typescript/components/Feedback.d.ts.map +1 -0
- package/lib/typescript/components/Filter.d.ts +17 -0
- package/lib/typescript/components/Filter.d.ts.map +1 -0
- package/lib/typescript/components/IndexLevel.d.ts.map +1 -1
- package/lib/typescript/components/Input.d.ts +3 -3
- package/lib/typescript/components/Input.d.ts.map +1 -1
- package/lib/typescript/components/InvitePopover.d.ts.map +1 -1
- package/lib/typescript/components/MemberRow.d.ts.map +1 -1
- package/lib/typescript/components/PostForm.d.ts.map +1 -1
- package/lib/typescript/components/RadioGroup.d.ts.map +1 -1
- package/lib/typescript/components/Sheet.d.ts.map +1 -1
- package/lib/typescript/components/Skeleton.d.ts +1 -1
- package/lib/typescript/components/Skeleton.d.ts.map +1 -1
- package/lib/typescript/components/Switch.d.ts.map +1 -1
- package/lib/typescript/components/Tabs.d.ts.map +1 -1
- package/lib/typescript/components/Tag.d.ts +2 -2
- package/lib/typescript/components/ThemedIcon.d.ts +1 -1
- package/lib/typescript/components/ThemedIcon.d.ts.map +1 -1
- package/lib/typescript/components/Toast.d.ts +24 -13
- package/lib/typescript/components/Toast.d.ts.map +1 -1
- package/lib/typescript/editor/NoteEditor.d.ts.map +1 -1
- package/lib/typescript/editor/editorHtml.generated.d.ts.map +1 -1
- package/lib/typescript/hooks/useThemeColor.d.ts +0 -10
- package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -1
- package/lib/typescript/hooks/useTrackedText.d.ts +6 -0
- package/lib/typescript/hooks/useTrackedText.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +9 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/lib/optional-deps.d.ts +23 -0
- package/lib/typescript/lib/optional-deps.d.ts.map +1 -0
- package/lib/typescript/lib/touch-target.d.ts +5 -0
- package/lib/typescript/lib/touch-target.d.ts.map +1 -0
- package/package.json +6 -1
- package/setup/createMedwayUiNavigator.tsx +4 -7
- package/setup/metro.js +12 -3
- package/setup/tokens.native.css +268 -258
- package/src/components/Alert.tsx +3 -1
- package/src/components/AlertDialog.tsx +8 -3
- package/src/components/Badge.tsx +51 -3
- package/src/components/Button.tsx +39 -1
- package/src/components/Carousel.tsx +17 -3
- package/src/components/Checkbox.tsx +2 -0
- package/src/components/Chip.tsx +227 -0
- package/src/components/Choice.tsx +139 -0
- package/src/components/Dialog.tsx +39 -23
- package/src/components/DropdownMenu.tsx +99 -87
- package/src/components/EmptyState.tsx +18 -3
- package/src/components/Feed.tsx +4 -3
- package/src/components/Feedback.tsx +111 -0
- package/src/components/Filter.tsx +106 -0
- package/src/components/IndexLevel.tsx +8 -10
- package/src/components/Input.tsx +142 -54
- package/src/components/InvitePopover.tsx +5 -1
- package/src/components/MemberRow.tsx +3 -5
- package/src/components/PostForm.tsx +16 -9
- package/src/components/PrizeCollection.tsx +1 -1
- package/src/components/RadioGroup.tsx +8 -0
- package/src/components/Select.tsx +1 -1
- package/src/components/Sheet.tsx +4 -1
- package/src/components/Skeleton.tsx +47 -6
- package/src/components/Switch.tsx +2 -0
- package/src/components/Tabs.tsx +2 -0
- package/src/components/ThemedIcon.tsx +1 -1
- package/src/components/Toast.tsx +489 -87
- package/src/editor/NoteEditor.tsx +50 -11
- package/src/editor/editorHtml.generated.ts +1 -1
- package/src/hooks/useThemeColor.ts +757 -64
- package/src/hooks/useTrackedText.ts +20 -0
- package/src/index.ts +36 -1
- package/src/lib/optional-deps.ts +27 -0
- package/src/lib/touch-target.ts +12 -0
- package/setup/index.tsx +0 -42
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useEffect,
|
|
6
6
|
useId,
|
|
7
7
|
useMemo,
|
|
8
|
+
useState,
|
|
8
9
|
type ReactNode,
|
|
9
10
|
} from "react";
|
|
10
11
|
import { Dimensions, Pressable, StyleSheet, View, type PressableProps, type ViewProps } from "react-native";
|
|
@@ -12,7 +13,8 @@ import { Dimensions, Pressable, StyleSheet, View, type PressableProps, type View
|
|
|
12
13
|
import { Text } from "@/components/Text";
|
|
13
14
|
import * as DropdownMenuPrimitive from "@rn-primitives/dropdown-menu";
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import { useThemeColor } from "@/hooks/useThemeColor";
|
|
17
|
+
import { useOverlayHost, type HostOrigin } from "@/lib/overlay-host";
|
|
16
18
|
import { cn } from "@/lib/utils";
|
|
17
19
|
|
|
18
20
|
interface DropdownMenuProps extends DropdownMenuPrimitive.RootProps {
|
|
@@ -43,6 +45,14 @@ type MenuActionsContextValue = {
|
|
|
43
45
|
close: () => void;
|
|
44
46
|
};
|
|
45
47
|
|
|
48
|
+
type MenuAlign = NonNullable<DropdownMenuContentProps["align"]>;
|
|
49
|
+
|
|
50
|
+
type MenuPosition = { top: number; left: number };
|
|
51
|
+
|
|
52
|
+
type TriggerPosition = NonNullable<
|
|
53
|
+
ReturnType<typeof DropdownMenuPrimitive.useRootContext>["triggerPosition"]
|
|
54
|
+
>;
|
|
55
|
+
|
|
46
56
|
const MenuActionsContext = createContext<MenuActionsContextValue | null>(null);
|
|
47
57
|
|
|
48
58
|
function useMenuActions() {
|
|
@@ -90,6 +100,7 @@ const DropdownMenuTrigger = forwardRef<
|
|
|
90
100
|
ref={ref}
|
|
91
101
|
collapsable={false}
|
|
92
102
|
accessibilityRole="button"
|
|
103
|
+
hitSlop={6}
|
|
93
104
|
className={cn("items-center justify-center", className)}
|
|
94
105
|
style={(state) => [
|
|
95
106
|
{
|
|
@@ -107,13 +118,36 @@ const DropdownMenuTrigger = forwardRef<
|
|
|
107
118
|
);
|
|
108
119
|
});
|
|
109
120
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
const MENU_WIDTH = 200;
|
|
122
|
+
const MENU_EDGE_GAP = 8;
|
|
123
|
+
|
|
124
|
+
// triggerPosition vem em coordenadas de janela; o OverlayHost fica abaixo do header, então subtrai a origem.
|
|
125
|
+
function computeMenuPosition({
|
|
126
|
+
triggerPosition,
|
|
127
|
+
hostOrigin,
|
|
128
|
+
align,
|
|
129
|
+
sideOffset,
|
|
130
|
+
}: {
|
|
131
|
+
triggerPosition: TriggerPosition;
|
|
132
|
+
hostOrigin: HostOrigin;
|
|
133
|
+
align: MenuAlign;
|
|
134
|
+
sideOffset: number;
|
|
135
|
+
}): MenuPosition {
|
|
136
|
+
const windowWidth = Dimensions.get("window").width;
|
|
137
|
+
const alignOffset: Record<MenuAlign, number> = {
|
|
138
|
+
start: 0,
|
|
139
|
+
center: triggerPosition.width / 2 - MENU_WIDTH / 2,
|
|
140
|
+
end: triggerPosition.width - MENU_WIDTH,
|
|
141
|
+
};
|
|
142
|
+
const rawLeft = triggerPosition.pageX + alignOffset[align] - hostOrigin.x;
|
|
143
|
+
const maxLeft = windowWidth - hostOrigin.x - MENU_WIDTH - MENU_EDGE_GAP;
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
left: Math.max(MENU_EDGE_GAP, Math.min(rawLeft, maxLeft)),
|
|
147
|
+
top: triggerPosition.pageY + triggerPosition.height + sideOffset - hostOrigin.y,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
117
151
|
function DropdownMenuContent({
|
|
118
152
|
className,
|
|
119
153
|
align = "end",
|
|
@@ -125,103 +159,83 @@ function DropdownMenuContent({
|
|
|
125
159
|
const overlay = useOverlayHost();
|
|
126
160
|
const { close } = useMenuActions();
|
|
127
161
|
const { open, triggerPosition } = DropdownMenuPrimitive.useRootContext();
|
|
162
|
+
const [menuPosition, setMenuPosition] = useState<MenuPosition | null>(null);
|
|
163
|
+
const surfaceColor = useThemeColor("surface-elevated");
|
|
164
|
+
const borderColor = useThemeColor("gray-stroke-muted");
|
|
165
|
+
const backdropColor = useThemeColor("black-48");
|
|
128
166
|
|
|
129
167
|
useEffect(() => {
|
|
130
168
|
if (!open || !triggerPosition) {
|
|
131
|
-
|
|
169
|
+
setMenuPosition(null);
|
|
132
170
|
return;
|
|
133
171
|
}
|
|
134
172
|
|
|
135
173
|
let cancelled = false;
|
|
136
|
-
const menuWidth = 200;
|
|
137
|
-
const windowWidth = Dimensions.get("window").width;
|
|
138
174
|
|
|
139
175
|
overlay.measureHostOrigin((hostOrigin) => {
|
|
140
|
-
if (cancelled)
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
let left =
|
|
145
|
-
triggerPosition.pageX - hostOrigin.x;
|
|
146
|
-
|
|
147
|
-
if (align === "end") {
|
|
148
|
-
left =
|
|
149
|
-
triggerPosition.pageX +
|
|
150
|
-
triggerPosition.width -
|
|
151
|
-
menuWidth -
|
|
152
|
-
hostOrigin.x;
|
|
153
|
-
} else if (align === "center") {
|
|
154
|
-
left =
|
|
155
|
-
triggerPosition.pageX +
|
|
156
|
-
triggerPosition.width / 2 -
|
|
157
|
-
menuWidth / 2 -
|
|
158
|
-
hostOrigin.x;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
left = Math.max(8, Math.min(left, windowWidth - hostOrigin.x - menuWidth - 8));
|
|
162
|
-
|
|
163
|
-
const top =
|
|
164
|
-
triggerPosition.pageY +
|
|
165
|
-
triggerPosition.height +
|
|
166
|
-
sideOffset -
|
|
167
|
-
hostOrigin.y;
|
|
168
|
-
|
|
169
|
-
overlay.mount(
|
|
170
|
-
id,
|
|
171
|
-
<MenuActionsContext.Provider value={{ close }}>
|
|
172
|
-
<View style={styles.layer} pointerEvents="box-none">
|
|
173
|
-
<Pressable
|
|
174
|
-
accessibilityLabel="Fechar menu"
|
|
175
|
-
style={[
|
|
176
|
-
StyleSheet.absoluteFill,
|
|
177
|
-
showOverlay ? styles.backdrop : null,
|
|
178
|
-
]}
|
|
179
|
-
onPress={close}
|
|
180
|
-
/>
|
|
181
|
-
<View
|
|
182
|
-
className={cn(
|
|
183
|
-
"overflow-hidden rounded-md border border-gray-stroke-muted bg-surface-elevated p-1 shadow-md",
|
|
184
|
-
className
|
|
185
|
-
)}
|
|
186
|
-
// Fallback visual caso NativeWind não aplique no host de overlay
|
|
187
|
-
style={[
|
|
188
|
-
styles.menu,
|
|
189
|
-
{
|
|
190
|
-
top,
|
|
191
|
-
left,
|
|
192
|
-
width: menuWidth,
|
|
193
|
-
backgroundColor: "white",
|
|
194
|
-
borderRadius: 8,
|
|
195
|
-
borderWidth: 1,
|
|
196
|
-
borderColor: "#E7E9EF",
|
|
197
|
-
padding: 4,
|
|
198
|
-
},
|
|
199
|
-
]}
|
|
200
|
-
>
|
|
201
|
-
{children}
|
|
202
|
-
</View>
|
|
203
|
-
</View>
|
|
204
|
-
</MenuActionsContext.Provider>
|
|
205
|
-
);
|
|
176
|
+
if (cancelled) return;
|
|
177
|
+
setMenuPosition(computeMenuPosition({ triggerPosition, hostOrigin, align, sideOffset }));
|
|
206
178
|
});
|
|
207
179
|
|
|
208
180
|
return () => {
|
|
209
181
|
cancelled = true;
|
|
210
|
-
overlay.unmount(id);
|
|
211
182
|
};
|
|
183
|
+
}, [align, open, overlay, sideOffset, triggerPosition]);
|
|
184
|
+
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!menuPosition) {
|
|
187
|
+
overlay.unmount(id);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
overlay.mount(
|
|
192
|
+
id,
|
|
193
|
+
<MenuActionsContext.Provider value={{ close }}>
|
|
194
|
+
<View style={styles.layer} pointerEvents="box-none">
|
|
195
|
+
<Pressable
|
|
196
|
+
accessibilityLabel="Fechar menu"
|
|
197
|
+
style={[
|
|
198
|
+
StyleSheet.absoluteFill,
|
|
199
|
+
showOverlay ? { backgroundColor: backdropColor } : null,
|
|
200
|
+
]}
|
|
201
|
+
onPress={close}
|
|
202
|
+
/>
|
|
203
|
+
<View
|
|
204
|
+
className={cn(
|
|
205
|
+
"overflow-hidden rounded-md border border-gray-stroke-muted bg-surface-elevated p-1 shadow-md",
|
|
206
|
+
className
|
|
207
|
+
)}
|
|
208
|
+
style={[
|
|
209
|
+
styles.menu,
|
|
210
|
+
{
|
|
211
|
+
top: menuPosition.top,
|
|
212
|
+
left: menuPosition.left,
|
|
213
|
+
width: MENU_WIDTH,
|
|
214
|
+
backgroundColor: surfaceColor,
|
|
215
|
+
borderColor,
|
|
216
|
+
},
|
|
217
|
+
]}
|
|
218
|
+
>
|
|
219
|
+
{children}
|
|
220
|
+
</View>
|
|
221
|
+
</View>
|
|
222
|
+
</MenuActionsContext.Provider>
|
|
223
|
+
);
|
|
212
224
|
}, [
|
|
213
|
-
|
|
225
|
+
backdropColor,
|
|
226
|
+
borderColor,
|
|
214
227
|
children,
|
|
215
228
|
className,
|
|
216
229
|
close,
|
|
217
230
|
id,
|
|
218
|
-
|
|
231
|
+
menuPosition,
|
|
219
232
|
overlay,
|
|
220
233
|
showOverlay,
|
|
221
|
-
|
|
222
|
-
triggerPosition,
|
|
234
|
+
surfaceColor,
|
|
223
235
|
]);
|
|
224
236
|
|
|
237
|
+
useEffect(() => () => overlay.unmount(id), [id, overlay]);
|
|
238
|
+
|
|
225
239
|
return null;
|
|
226
240
|
}
|
|
227
241
|
|
|
@@ -284,15 +298,13 @@ const styles = StyleSheet.create({
|
|
|
284
298
|
layer: {
|
|
285
299
|
...StyleSheet.absoluteFill,
|
|
286
300
|
},
|
|
287
|
-
backdrop: {
|
|
288
|
-
// Alinhado ao Dialog/Sheet (`bg-black/50`); style explícito porque
|
|
289
|
-
// NativeWind pode não aplicar classes montadas no OverlayHost.
|
|
290
|
-
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
291
|
-
},
|
|
292
301
|
menu: {
|
|
293
302
|
position: "absolute",
|
|
294
303
|
zIndex: 2,
|
|
295
304
|
elevation: 8,
|
|
305
|
+
borderRadius: 8,
|
|
306
|
+
borderWidth: 1,
|
|
307
|
+
padding: 4,
|
|
296
308
|
},
|
|
297
309
|
});
|
|
298
310
|
|
|
@@ -9,16 +9,26 @@ interface EmptyStateProps extends ViewProps {
|
|
|
9
9
|
title: string;
|
|
10
10
|
description?: string;
|
|
11
11
|
action?: ReactNode;
|
|
12
|
-
variant?:
|
|
12
|
+
variant?: EmptyStateVariant;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
type EmptyStateVariant = "regular" | "emphasis" | "success" | "warning" | "error";
|
|
16
|
+
|
|
17
|
+
const ICON_BACKGROUND_BY_VARIANT: Record<EmptyStateVariant, string> = {
|
|
18
|
+
regular: "bg-gray-background-soft",
|
|
19
|
+
emphasis: "bg-secondary-background-soft",
|
|
20
|
+
success: "bg-green-background-soft",
|
|
21
|
+
warning: "bg-yellow-background-soft",
|
|
22
|
+
error: "bg-red-background-soft",
|
|
23
|
+
};
|
|
24
|
+
|
|
15
25
|
function EmptyState({
|
|
16
26
|
className,
|
|
17
27
|
icon,
|
|
18
28
|
title,
|
|
19
29
|
description,
|
|
20
30
|
action,
|
|
21
|
-
variant
|
|
31
|
+
variant = "regular",
|
|
22
32
|
...props
|
|
23
33
|
}: EmptyStateProps) {
|
|
24
34
|
return (
|
|
@@ -30,7 +40,12 @@ function EmptyState({
|
|
|
30
40
|
{...props}
|
|
31
41
|
>
|
|
32
42
|
{icon ? (
|
|
33
|
-
<View
|
|
43
|
+
<View
|
|
44
|
+
className={cn(
|
|
45
|
+
"size-16 items-center justify-center rounded-full",
|
|
46
|
+
ICON_BACKGROUND_BY_VARIANT[variant]
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
34
49
|
{icon}
|
|
35
50
|
</View>
|
|
36
51
|
) : null}
|
package/src/components/Feed.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { TextInput, View, type TextInputProps, type ViewProps } from "react-nati
|
|
|
4
4
|
import { Text } from "@/components/Text";
|
|
5
5
|
|
|
6
6
|
import { InputText, inputVariants } from "@/components/Input";
|
|
7
|
+
import { useTrackedText } from "@/hooks/useTrackedText";
|
|
7
8
|
import { cn } from "@/lib/utils";
|
|
8
9
|
|
|
9
10
|
interface FeedProps extends ViewProps {
|
|
@@ -53,7 +54,7 @@ function FeedComposer({
|
|
|
53
54
|
editable = true,
|
|
54
55
|
...props
|
|
55
56
|
}: FeedComposerProps) {
|
|
56
|
-
const
|
|
57
|
+
const { text, handleChangeText } = useTrackedText(value, defaultValue, onChangeText);
|
|
57
58
|
|
|
58
59
|
return (
|
|
59
60
|
<View className={cn("gap-1", className)}>
|
|
@@ -67,7 +68,7 @@ function FeedComposer({
|
|
|
67
68
|
maxLength={maxLength}
|
|
68
69
|
value={value}
|
|
69
70
|
defaultValue={defaultValue}
|
|
70
|
-
onChangeText={
|
|
71
|
+
onChangeText={handleChangeText}
|
|
71
72
|
placeholder={placeholder}
|
|
72
73
|
editable={editable}
|
|
73
74
|
className={cn(
|
|
@@ -86,7 +87,7 @@ function FeedComposer({
|
|
|
86
87
|
/>
|
|
87
88
|
</View>
|
|
88
89
|
<InputText className="text-right text-xs font-medium text-gray-foreground">
|
|
89
|
-
{
|
|
90
|
+
{text.length} / {maxLength}
|
|
90
91
|
</InputText>
|
|
91
92
|
</View>
|
|
92
93
|
);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { cloneElement, isValidElement, type ReactNode } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { Text } from "@/components/Text";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
type FeedbackType = "success" | "error" | "warning";
|
|
8
|
+
type FeedbackVariant = "filled" | "soft" | "basic";
|
|
9
|
+
type FeedbackSize = "sm" | "md";
|
|
10
|
+
|
|
11
|
+
interface FeedbackProps {
|
|
12
|
+
text: string;
|
|
13
|
+
type?: FeedbackType;
|
|
14
|
+
variant?: FeedbackVariant;
|
|
15
|
+
size?: FeedbackSize;
|
|
16
|
+
icon?: ReactNode;
|
|
17
|
+
showIcon?: boolean;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const SIZE_MAP: Record<
|
|
22
|
+
FeedbackSize,
|
|
23
|
+
{ gap: string; padding: string; text: string; iconSize: number }
|
|
24
|
+
> = {
|
|
25
|
+
sm: {
|
|
26
|
+
gap: "gap-1.5",
|
|
27
|
+
padding: "px-2 py-1",
|
|
28
|
+
text: "text-xs leading-xs",
|
|
29
|
+
iconSize: 16,
|
|
30
|
+
},
|
|
31
|
+
md: {
|
|
32
|
+
gap: "gap-2",
|
|
33
|
+
padding: "px-2.5 py-1.5",
|
|
34
|
+
text: "text-sm leading-sm",
|
|
35
|
+
iconSize: 20,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const FILLED_BG: Record<FeedbackType, string> = {
|
|
40
|
+
success: "bg-feedback-success-background-fill",
|
|
41
|
+
error: "bg-feedback-error-background-fill",
|
|
42
|
+
warning: "bg-feedback-warning-background-fill",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const SOFT_BG: Record<FeedbackType, string> = {
|
|
46
|
+
success: "bg-feedback-success-background-soft",
|
|
47
|
+
error: "bg-feedback-error-background-soft",
|
|
48
|
+
warning: "bg-feedback-warning-background-soft",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const FILLED_TEXT_COLOR: Record<FeedbackType, string> = {
|
|
52
|
+
success: "text-feedback-success-foreground-onfill",
|
|
53
|
+
error: "text-feedback-error-foreground-onfill",
|
|
54
|
+
warning: "text-feedback-warning-foreground-onfill",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const DEFAULT_TEXT_COLOR: Record<FeedbackType, string> = {
|
|
58
|
+
success: "text-feedback-success-foreground",
|
|
59
|
+
error: "text-feedback-error-foreground",
|
|
60
|
+
warning: "text-feedback-warning-foreground",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function Feedback({
|
|
64
|
+
text,
|
|
65
|
+
type = "success",
|
|
66
|
+
variant = "filled",
|
|
67
|
+
size = "md",
|
|
68
|
+
icon,
|
|
69
|
+
showIcon = true,
|
|
70
|
+
className,
|
|
71
|
+
}: FeedbackProps) {
|
|
72
|
+
const sz = SIZE_MAP[size];
|
|
73
|
+
|
|
74
|
+
const isBasic = variant === "basic";
|
|
75
|
+
const bg = variant === "filled" ? FILLED_BG[type] : SOFT_BG[type];
|
|
76
|
+
const textColor =
|
|
77
|
+
variant === "filled" ? FILLED_TEXT_COLOR[type] : DEFAULT_TEXT_COLOR[type];
|
|
78
|
+
|
|
79
|
+
const shouldRenderIcon = showIcon && !!icon;
|
|
80
|
+
|
|
81
|
+
// Aplica o tamanho do size ao ícone, sem sobrescrever width/height explícitos.
|
|
82
|
+
const sizedIcon = isValidElement<{ width?: number; height?: number }>(icon)
|
|
83
|
+
? cloneElement(icon, {
|
|
84
|
+
width: icon.props.width ?? sz.iconSize,
|
|
85
|
+
height: icon.props.height ?? sz.iconSize,
|
|
86
|
+
})
|
|
87
|
+
: icon;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<View
|
|
91
|
+
className={cn(
|
|
92
|
+
"flex-row items-center justify-center",
|
|
93
|
+
shouldRenderIcon && sz.gap,
|
|
94
|
+
!isBasic && sz.padding,
|
|
95
|
+
!isBasic && "rounded-md",
|
|
96
|
+
!isBasic && bg,
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{shouldRenderIcon ? <View className="shrink-0">{sizedIcon}</View> : null}
|
|
101
|
+
<Text
|
|
102
|
+
className={cn("text-center font-semibold", sz.text, textColor)}
|
|
103
|
+
>
|
|
104
|
+
{text}
|
|
105
|
+
</Text>
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { Feedback };
|
|
111
|
+
export type { FeedbackProps, FeedbackSize, FeedbackType, FeedbackVariant };
|
|
@@ -0,0 +1,106 @@
|
|
|
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 FilterSize = "sm" | "md" | "lg";
|
|
11
|
+
|
|
12
|
+
interface FilterProps {
|
|
13
|
+
label: string;
|
|
14
|
+
criteria?: string;
|
|
15
|
+
showCriteria?: boolean;
|
|
16
|
+
selected?: boolean;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
size?: FilterSize;
|
|
19
|
+
icon?: ReactNode;
|
|
20
|
+
onPress?: () => void;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SIZE_MAP: Record<
|
|
25
|
+
FilterSize,
|
|
26
|
+
{ container: string; text: string; icon: number }
|
|
27
|
+
> = {
|
|
28
|
+
sm: { container: "gap-2 px-3 py-2", text: "text-xs", icon: 16 },
|
|
29
|
+
md: { container: "gap-2.5 px-3.5 py-2.5", text: "text-sm", icon: 20 },
|
|
30
|
+
lg: { container: "gap-3 px-4 py-3", text: "text-base", icon: 24 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const SHADOW_STYLE = {
|
|
34
|
+
shadowOffset: { width: 0, height: 2 },
|
|
35
|
+
shadowOpacity: 0.08,
|
|
36
|
+
shadowRadius: 4,
|
|
37
|
+
elevation: 2,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function Filter({
|
|
41
|
+
label,
|
|
42
|
+
criteria,
|
|
43
|
+
showCriteria = true,
|
|
44
|
+
selected = false,
|
|
45
|
+
disabled = false,
|
|
46
|
+
size = "md",
|
|
47
|
+
icon,
|
|
48
|
+
onPress,
|
|
49
|
+
className,
|
|
50
|
+
}: FilterProps) {
|
|
51
|
+
const sz = SIZE_MAP[size] ?? SIZE_MAP.md;
|
|
52
|
+
const shadowColor = useThemeColor("gray-700");
|
|
53
|
+
|
|
54
|
+
const outerBg = disabled
|
|
55
|
+
? "bg-gray-background-soft-muted"
|
|
56
|
+
: "bg-gray-background-soft";
|
|
57
|
+
|
|
58
|
+
const textClass = disabled
|
|
59
|
+
? "font-medium text-gray-foreground-muted"
|
|
60
|
+
: selected
|
|
61
|
+
? "font-semibold text-primary-foreground"
|
|
62
|
+
: "font-medium text-gray-foreground";
|
|
63
|
+
|
|
64
|
+
const dropdownColorToken = disabled
|
|
65
|
+
? "gray-foreground-muted"
|
|
66
|
+
: selected
|
|
67
|
+
? "primary-foreground"
|
|
68
|
+
: "gray-foreground";
|
|
69
|
+
|
|
70
|
+
const hasCriteria = showCriteria && !!criteria;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Pressable
|
|
74
|
+
onPress={disabled ? undefined : onPress}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
accessibilityRole="button"
|
|
77
|
+
accessibilityState={{ selected, disabled }}
|
|
78
|
+
className={cn("rounded-md", outerBg, className)}
|
|
79
|
+
style={{ padding: 2 }}
|
|
80
|
+
>
|
|
81
|
+
<View
|
|
82
|
+
className={cn(
|
|
83
|
+
"flex-row items-center justify-center rounded-md",
|
|
84
|
+
sz.container,
|
|
85
|
+
selected ? "bg-surface-card" : "bg-transparent",
|
|
86
|
+
)}
|
|
87
|
+
style={selected ? { ...SHADOW_STYLE, shadowColor } : undefined}
|
|
88
|
+
>
|
|
89
|
+
{icon ? <View className="shrink-0">{icon}</View> : null}
|
|
90
|
+
<Text className={cn("text-center", sz.text, textClass)}>
|
|
91
|
+
{hasCriteria ? `${label}: ${criteria}` : label}
|
|
92
|
+
</Text>
|
|
93
|
+
<View className="shrink-0">
|
|
94
|
+
<ThemedIcon
|
|
95
|
+
icon={MedwayIcons.ChevronDown as never}
|
|
96
|
+
size={sz.icon}
|
|
97
|
+
colorToken={dropdownColorToken}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
</View>
|
|
101
|
+
</Pressable>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { Filter };
|
|
106
|
+
export type { FilterProps };
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
withAlpha,
|
|
25
25
|
type ThemeColorToken,
|
|
26
26
|
} from "@/hooks/useThemeColor";
|
|
27
|
+
import {
|
|
28
|
+
loadLinearGradient,
|
|
29
|
+
type LinearGradientComponent,
|
|
30
|
+
} from "@/lib/optional-deps";
|
|
27
31
|
import { cn } from "@/lib/utils";
|
|
28
32
|
|
|
29
33
|
/**
|
|
@@ -195,13 +199,7 @@ type RingDeps = {
|
|
|
195
199
|
maskElement: ReactNode;
|
|
196
200
|
children?: ReactNode;
|
|
197
201
|
}>;
|
|
198
|
-
LinearGradient:
|
|
199
|
-
colors: string[];
|
|
200
|
-
locations?: number[];
|
|
201
|
-
start?: { x: number; y: number };
|
|
202
|
-
end?: { x: number; y: number };
|
|
203
|
-
style?: object;
|
|
204
|
-
}>;
|
|
202
|
+
LinearGradient: LinearGradientComponent;
|
|
205
203
|
};
|
|
206
204
|
|
|
207
205
|
/** Lazy-load optional peers so the static chip works without them installed. */
|
|
@@ -209,11 +207,11 @@ function loadRingDeps(): RingDeps | null {
|
|
|
209
207
|
try {
|
|
210
208
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
211
209
|
const masked = require("@react-native-masked-view/masked-view");
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
const LinearGradient = loadLinearGradient();
|
|
211
|
+
if (!LinearGradient) return null;
|
|
214
212
|
return {
|
|
215
213
|
MaskedView: masked.default ?? masked,
|
|
216
|
-
LinearGradient
|
|
214
|
+
LinearGradient,
|
|
217
215
|
};
|
|
218
216
|
} catch {
|
|
219
217
|
return null;
|