@wereform/pkgm-shared 1.0.2 → 1.0.4

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 (37) hide show
  1. package/dist/components/buttons/CustomizableButton.js +1 -1
  2. package/dist/components/dropdowns/DropDown.js +1 -1
  3. package/dist/components/headers/HeaderGlobal.js +1 -1
  4. package/dist/components/inputs/InputComment.js +1 -1
  5. package/dist/components/inputs/InputField.js +1 -1
  6. package/dist/components/menus/MenuChannelMeta.js +1 -1
  7. package/dist/components/menus/MenuInteraction.js +1 -1
  8. package/dist/components/menus/MenuNav.js +1 -1
  9. package/dist/components/titles/CustomizedTitle.js +1 -1
  10. package/dist/styles/MenuChannelMetaStyles.js +1 -1
  11. package/dist/utils/DateViews.js +1 -1
  12. package/package.json +38 -34
  13. package/src/components/buttons/CustomizableButton.jsx +85 -0
  14. package/src/components/dropdowns/DropDown.jsx +60 -0
  15. package/src/components/headers/HeaderGlobal.jsx +38 -0
  16. package/src/components/inputs/InputComment.jsx +27 -0
  17. package/src/components/inputs/InputField.jsx +113 -0
  18. package/src/components/menus/MenuChannelMeta.jsx +115 -0
  19. package/src/components/menus/MenuInteraction.jsx +68 -0
  20. package/src/components/menus/MenuNav.jsx +48 -0
  21. package/src/components/titles/CustomizedTitle.jsx +62 -0
  22. package/src/index.js +11 -0
  23. package/src/styles/MenuChannelMetaStyles.js +127 -0
  24. package/src/styles/MenuInteractionStyles.js +23 -0
  25. package/src/styles/commentInputStyles.js +27 -0
  26. package/src/styles/customizableButtonStyles.js +30 -0
  27. package/src/styles/dropDownStyles.js +57 -0
  28. package/src/styles/globalStyles.js +27 -0
  29. package/src/styles/headerGlobalStyles.js +30 -0
  30. package/src/styles/inputStyles.js +41 -0
  31. package/src/styles/menuNavStyles.js +22 -0
  32. package/src/utils/DateViews.jsx +72 -0
  33. package/src/utils/dateHelpers.js +0 -0
  34. package/src/utils/formatters.js +0 -0
  35. package/src/utils/index.js +0 -0
  36. package/src/utils/normalizeUrl.js +14 -0
  37. package/src/utils/validators.js +0 -0
@@ -0,0 +1,68 @@
1
+ import { TouchableOpacity, View } from "react-native";
2
+ import { MenuInteractionStyles } from "../../styles/MenuInteractionStyles";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+ import { useState } from "react";
5
+
6
+ /**
7
+ * MenuInteraction
8
+ *
9
+ * Renders the primary interaction controls for a post or wire.
10
+ * Includes share, comment, repost, like, dislike recognized actions.
11
+ *
12
+ * Props:
13
+ * - containerStyles: override styles for root container
14
+ * - columnMainIconStyles: override styles for icon column
15
+ * - pressed: callback to trigger overflow / context menu
16
+ *
17
+ * Notes:
18
+ * - Icons are presentation-only
19
+ * - Interaction handlers are expected to be wired upstream
20
+ */
21
+
22
+ export const MenuInteraction = ({
23
+ containerStyles,
24
+ columnMainIconStyles,
25
+ pressed,
26
+ }) => {
27
+ return (
28
+ <View style={[MenuInteractionStyles.container, containerStyles]}>
29
+ <View
30
+ style={[MenuInteractionStyles.column_mainIcons, columnMainIconStyles]}
31
+ >
32
+ <TouchableOpacity>
33
+ <View style={MenuInteractionStyles.icon}>
34
+ <Ionicons name="arrow-redo" size={24} color="white" />
35
+ </View>
36
+ </TouchableOpacity>
37
+
38
+ <TouchableOpacity>
39
+ <View style={MenuInteractionStyles.icon}>
40
+ <Ionicons name="chatbubble-ellipses" size={24} color="white" />
41
+ </View>
42
+ </TouchableOpacity>
43
+
44
+ <TouchableOpacity>
45
+ <View style={MenuInteractionStyles.icon}>
46
+ <Ionicons name="repeat-outline" size={24} color="white" />
47
+ </View>
48
+ </TouchableOpacity>
49
+
50
+ <TouchableOpacity>
51
+ <View style={MenuInteractionStyles.icon}>
52
+ <Ionicons name="thumbs-up-outline" size={24} color="white" />
53
+ </View>
54
+ </TouchableOpacity>
55
+
56
+ <TouchableOpacity>
57
+ <View style={MenuInteractionStyles.icon}>
58
+ <Ionicons name="thumbs-down-outline" size={24} color="white" />
59
+ </View>
60
+ </TouchableOpacity>
61
+ </View>
62
+
63
+ <TouchableOpacity onPress={() => pressed(true)}>
64
+ <Ionicons name="ellipsis-vertical-outline" size={24} color="white" />
65
+ </TouchableOpacity>
66
+ </View>
67
+ );
68
+ };
@@ -0,0 +1,48 @@
1
+ import { TouchableOpacity, View } from "react-native";
2
+ import { menuNavStyles } from "../../styles/menuNavStyles";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+
5
+ /**
6
+ * MenuNav
7
+ *
8
+ * Bottom navigation bar for core app sections.
9
+ * Icons and handlers are injected to keep this component stateless.
10
+ *
11
+ * Props:
12
+ * - homeIcon, wireIcon, uploadIcon, channelIcon, profileIcon: ReactNodes
13
+ * - onPressHome, onPressWire, onPressUpload, onPressChannel, onPressProfile: callbacks
14
+ *
15
+ * Notes:
16
+ * - Falls back to default profile icon if none is provided
17
+ * - Designed to be reusable across screens
18
+ */
19
+
20
+ export function MenuNav({
21
+ homeIcon,
22
+ wireIcon,
23
+ uploadIcon,
24
+ channelIcon,
25
+ profileIcon,
26
+
27
+ onPressHome,
28
+ onPressWire,
29
+ onPressUpload,
30
+ onPressChannel,
31
+ onPressProfile,
32
+ }) {
33
+ return (
34
+ <View style={menuNavStyles.container}>
35
+ <TouchableOpacity onPress={onPressHome}>{homeIcon}</TouchableOpacity>
36
+ <TouchableOpacity onPress={onPressWire}>{wireIcon}</TouchableOpacity>
37
+ <TouchableOpacity onPress={onPressUpload}>{uploadIcon}</TouchableOpacity>
38
+ <TouchableOpacity onPress={onPressChannel}>
39
+ {channelIcon}
40
+ </TouchableOpacity>
41
+ <TouchableOpacity onPress={onPressProfile}>
42
+ {profileIcon || (
43
+ <Ionicons name="person-outline" size={24} color="white" />
44
+ )}
45
+ </TouchableOpacity>
46
+ </View>
47
+ );
48
+ }
@@ -0,0 +1,62 @@
1
+ import { Text, View } from "react-native";
2
+
3
+ /**
4
+ * CustomizedTitle
5
+ *
6
+ * Reusable title-rendering component with built-in truncation
7
+ * and flexible typography control.
8
+ *
9
+ * Behavior:
10
+ * - Automatically truncates long titles to 75 characters
11
+ * - Appends an ellipsis (…) when truncation occurs
12
+ * - Falls back to a default title when no title is provided
13
+ * - Limits rendering to two lines for layout stability
14
+ *
15
+ * Props:
16
+ * - title: string (raw title text)
17
+ * - fontSize: number (text size override)
18
+ * - fontFamily: string (font family override)
19
+ * - textColor: string (text color override)
20
+ * - style: ViewStyle (outer container style override)
21
+ * - textStyle: TextStyle (text style override)
22
+ * - dateTextStyles: reserved for future extensions
23
+ *
24
+ * Notes:
25
+ * - Designed for feed cards, headers, and compact layouts
26
+ * - Uses flexShrink and wrapping to avoid layout overflow
27
+ */
28
+
29
+ export function CustomizedTitle({
30
+ title,
31
+ fontSize,
32
+ fontFamily,
33
+ textColor,
34
+ style,
35
+ textStyle,
36
+ dateTextStyles,
37
+ }) {
38
+ const displayTitle =
39
+ title && title.length > 75
40
+ ? title.slice(0, 75) + "…" // adds an ellipsis when truncated
41
+ : title || "Here is your default Title!";
42
+
43
+ return (
44
+ <View style={[{ width: "100%" }, style]}>
45
+ <Text
46
+ style={[
47
+ {
48
+ flexShrink: 1,
49
+ fontFamily: fontFamily,
50
+ fontSize: fontSize,
51
+ color: textColor,
52
+ flexWrap: "wrap",
53
+ },
54
+ textStyle,
55
+ ]}
56
+ numberOfLines={2}
57
+ >
58
+ {displayTitle}
59
+ </Text>
60
+ </View>
61
+ );
62
+ }
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { InputField } from "./components/inputs/InputField";
2
+ export { MenuNav } from "./components/menus/MenuNav";
3
+ export { HeaderGlobal } from "./components/headers/HeaderGlobal";
4
+ export { MenuInteraction } from "./components/menus/MenuInteraction";
5
+ export { MenuChannelMeta } from "./components/menus/MenuChannelMeta";
6
+ export { InputComment } from "./components/inputs/InputComment.jsx";
7
+ export { DropDown } from "./components/dropdowns/DropDown.jsx";
8
+ export { CustomizedTitle } from "./components/titles/CustomizedTitle.jsx";
9
+ export { CustomizedButton } from "./components/buttons/CustomizableButton.jsx";
10
+ export { DateViews } from "./utils/DateViews.jsx";
11
+ export { default as normalizeUrl } from "./utils/normalizeUrl.js";
@@ -0,0 +1,127 @@
1
+ import { Platform, StyleSheet, useWindowDimensions } from "react-native";
2
+ import { globalStyles } from "./globalStyles";
3
+
4
+ /* Channel metadata styles for menu/feed context */
5
+ export const MenuChannelMetaStyles = StyleSheet.create({
6
+ container: {
7
+ // Outer metadata wrapper
8
+ width: "auto",
9
+ marginRight: 12,
10
+ marginLeft: 12,
11
+ },
12
+
13
+ dateViewsContainer: {
14
+ // Row for date and view count
15
+ width: "100%",
16
+ flexDirection: "row",
17
+ justifyContent: "space-between",
18
+ marginTop: 12,
19
+ paddingRight: 6,
20
+ paddingLeft: 6,
21
+ },
22
+
23
+ dateContainer: {
24
+ // Date icon and text container
25
+ flexDirection: "row",
26
+ rowGap: 20,
27
+ },
28
+
29
+ dateIcon: {
30
+ // Date icon spacing
31
+ marginRight: 8,
32
+ },
33
+
34
+ viewsContainer: {
35
+ // Views icon and text container
36
+ flexDirection: "row",
37
+ alignItems: "center",
38
+ },
39
+
40
+ eyeIcon: {
41
+ // Eye icon spacing
42
+ marginRight: 8,
43
+ marginLeft: 8,
44
+ },
45
+
46
+ dateText: {
47
+ // Date text style
48
+ color: globalStyles.colors.colorPrimary600,
49
+ },
50
+
51
+ viewsText: {
52
+ // Views count text style
53
+ color: globalStyles.colors.colorPrimary600,
54
+ },
55
+
56
+ channelMetaContainer: {
57
+ // Channel identity and subscribe container
58
+ flexDirection: "row",
59
+ backgroundColor: globalStyles.colors.colorPrimary200,
60
+ padding: 12,
61
+ marginBottom: 6,
62
+ marginTop: 12,
63
+ borderRadius: globalStyles.borders.borderPrimary100,
64
+ justifyContent: "space-between",
65
+ },
66
+
67
+ channelMetaContainer_ColumnOne: {
68
+ // Left column with avatar and name
69
+ flexDirection: "row",
70
+ },
71
+
72
+ userImage: {
73
+ // Channel avatar image
74
+ width: 40,
75
+ height: 40,
76
+ borderRadius: globalStyles.borders.borderPrimary50,
77
+ },
78
+
79
+ nameSubcountContainer: {
80
+ // Username and subscriber count stack
81
+ flexDirection: "column",
82
+ paddingLeft: 12,
83
+ },
84
+
85
+ userName: {
86
+ // Channel display name
87
+ color: "#fff",
88
+ fontSize: Platform.OS === "ios" ? 16 : 14,
89
+ paddingBottom: 4,
90
+ fontWeight: "bold",
91
+ },
92
+
93
+ subCountContainer: {
94
+ // Subscriber count row
95
+ flexDirection: "row",
96
+ },
97
+
98
+ subCount: {
99
+ // Numeric subscriber count
100
+ color: globalStyles.colors.colorPrimary600,
101
+ paddingRight: 6,
102
+ fontSize: 12,
103
+ fontWeight: "bold",
104
+ },
105
+
106
+ subText: {
107
+ // Subscriber label text
108
+ color: "#fff",
109
+ fontSize: 12,
110
+ },
111
+
112
+ subscribeButtonContainer: {
113
+ // Subscribe action button
114
+ backgroundColor: globalStyles.colors.colorPrimary600,
115
+ width: Platform.OS === "ios" ? 100 : 80,
116
+ borderRadius: globalStyles.borders.borderPrimary400,
117
+ alignItems: "center",
118
+ justifyContent: "center",
119
+ },
120
+
121
+ subscribeButtonText: {
122
+ // Subscribe button label
123
+ color: "#fff",
124
+ fontWeight: "bold",
125
+ fontSize: Platform === "ios" ? 18 : 12,
126
+ },
127
+ });
@@ -0,0 +1,23 @@
1
+ /* Interaction bar styles for menu actions */
2
+ export const MenuInteractionStyles = {
3
+ container: {
4
+ // Interaction bar container
5
+ width: "100%",
6
+ height: 40,
7
+ flexDirection: "row",
8
+ justifyContent: "space-between",
9
+ alignItems: "center",
10
+ paddingRight: 16,
11
+ },
12
+
13
+ column_mainIcons: {
14
+ // Primary interaction icons row
15
+ flexDirection: "row",
16
+ justifyContent: "space-evenly",
17
+ },
18
+
19
+ icon: {
20
+ // Individual icon spacing
21
+ paddingLeft: 18,
22
+ },
23
+ };
@@ -0,0 +1,27 @@
1
+ import { globalStyles } from "./globalStyles";
2
+
3
+ /* Comment input field layout styles */
4
+ export const commentInputStyles = {
5
+ container: {
6
+ // Comment input container
7
+ flexDirection: "row",
8
+ width: "auto",
9
+ backgroundColor: globalStyles.colors.colorPrimary350,
10
+ borderRadius: globalStyles.borders.borderPrimary200,
11
+ height: 50,
12
+ justifyContent: "space-between",
13
+ alignItems: "center",
14
+ justifySelf: "center",
15
+ marginRight: 12,
16
+ marginLeft: 12,
17
+ paddingLeft: 12,
18
+ paddingRight: 24,
19
+ },
20
+
21
+ input: {
22
+ // Comment text input
23
+ paddingLeft: 8,
24
+ paddingRight: 8,
25
+ color: "#fff",
26
+ },
27
+ };
@@ -0,0 +1,30 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { globalStyles } from "./globalStyles";
3
+
4
+ /* Reusable customizable button styles */
5
+ export const customizableButtonStyles = StyleSheet.create({
6
+ container: {
7
+ // Outer wrapper container
8
+ },
9
+
10
+ buttonContainer: {
11
+ // Button surface container
12
+ height: 50,
13
+ width: "auto",
14
+ flexGrow: 1,
15
+ justifyContent: "center",
16
+ alignItems: "center",
17
+ borderRadius: globalStyles.borders.borderPrimary100,
18
+ backgroundColor: "#fff",
19
+ },
20
+
21
+ buttonText: {
22
+ // Button label text
23
+ flexDirection: "row",
24
+ color: "#000",
25
+ fontSize: 16,
26
+ textAlign: "center",
27
+ textAlignVertical: "center",
28
+ alignItems: "center",
29
+ },
30
+ });
@@ -0,0 +1,57 @@
1
+ import { globalStyles } from "./globalStyles";
2
+
3
+ /* Dropdown selection component styles */
4
+ export const dropDownStyles = {
5
+ container: {
6
+ // Outer dropdown wrapper
7
+ width: "auto",
8
+ },
9
+
10
+ picker: {
11
+ // Native picker control
12
+ height: 40,
13
+ flex: 1,
14
+ },
15
+
16
+ overlayStyle: {
17
+ // Modal overlay background
18
+ backgroundColor: globalStyles.colors.colorPrimary50,
19
+ },
20
+
21
+ optionTextStyle: {
22
+ // Dropdown option text
23
+ color: "#fff",
24
+ },
25
+
26
+ optionContainerStyle: {
27
+ // Dropdown option container
28
+ backgroundColor: globalStyles.colors.colorPrimary200,
29
+ borderWidth: 1,
30
+ borderRadius: globalStyles.borders.borderPrimary200,
31
+ justifyContent: "space-between",
32
+ },
33
+
34
+ cancelContainerStyle: {
35
+ // Cancel action container
36
+ backgroundColor: "#fff",
37
+ },
38
+
39
+ dropDownContainer: {
40
+ // Selected value display container
41
+ flexDirection: "row",
42
+ width: "100%",
43
+ justifyContent: "space-between",
44
+ borderRadius: globalStyles.borders.borderPrimary200,
45
+ height: 50,
46
+ alignItems: "center",
47
+ paddingRight: 12,
48
+ paddingLeft: 12,
49
+ backgroundColor: globalStyles.colors.colorPrimary200,
50
+ },
51
+
52
+ dropDownText: {
53
+ // Selected value text
54
+ color: "#fff",
55
+ paddingLeft: 12,
56
+ },
57
+ };
@@ -0,0 +1,27 @@
1
+ /* Global design tokens for colors and border radii */
2
+ export const globalStyles = {
3
+ colors: {
4
+ // Primary background shades
5
+ colorPrimary50: "rgba(21, 21, 21, .7)",
6
+ colorPrimary100: "#151515",
7
+ colorPrimary200: "#252525",
8
+ colorPrimary250: "rgba(37, 37, 37, .9)",
9
+ colorPrimary300: "#3C3C3C",
10
+ colorPrimary350: "rgba(60,60,60,.2)",
11
+ colorPrimary400: "#7F7F7F",
12
+ colorPrimary500: "#D3D3D3",
13
+
14
+ // Accent and action colors
15
+ colorPrimary600: "#ff6600ff",
16
+ colorPrimary700: "#FF8800",
17
+ },
18
+
19
+ borders: {
20
+ // Standard border radius scale
21
+ borderPrimary50: 8,
22
+ borderPrimary100: 10,
23
+ borderPrimary200: 12,
24
+ borderPrimary300: 15,
25
+ borderPrimary400: 30,
26
+ },
27
+ };
@@ -0,0 +1,30 @@
1
+ /* Global header layout styles */
2
+ export const headerGlobalStyles = {
3
+ container: {
4
+ // Main header container
5
+ height: 75,
6
+ width: "100%",
7
+ flexDirection: "row",
8
+ justifyContent: "space-between",
9
+ alignItems: "center",
10
+ marginTop: -5,
11
+ },
12
+
13
+ iconsContainer: {
14
+ // Right-side icon group
15
+ flexDirection: "row",
16
+ },
17
+
18
+ icons: {
19
+ // Individual icon spacing
20
+ paddingRight: 20,
21
+ },
22
+
23
+ image: {
24
+ // Header logo image
25
+ width: 120,
26
+ height: 120,
27
+ resizeMode: "contain",
28
+ marginLeft: 24,
29
+ },
30
+ };
@@ -0,0 +1,41 @@
1
+ /* Reusable input field styles */
2
+ export const inputStyles = {
3
+ label: {
4
+ // Input label text
5
+ fontSize: 14,
6
+ marginBottom: 5,
7
+ color: "#333",
8
+ },
9
+
10
+ inputWrapper: {
11
+ // Input container with icon support
12
+ flexDirection: "row",
13
+ alignItems: "center",
14
+ borderRadius: 8,
15
+ borderWidth: 1,
16
+ borderColor: "rgba(37, 37, 37, 0)",
17
+ overflow: "hidden",
18
+ },
19
+
20
+ input: {
21
+ // Text input field
22
+ width: "auto",
23
+ paddingLeft: 14,
24
+ paddingRight: 14,
25
+ fontFamily: "Inter",
26
+ fontWeight: "700",
27
+ color: "#3c3c3c",
28
+ },
29
+
30
+ icon: {
31
+ // Input icon spacing
32
+ marginHorizontal: 5,
33
+ },
34
+
35
+ error: {
36
+ // Validation error text
37
+ color: "red",
38
+ fontSize: 12,
39
+ marginTop: 4,
40
+ },
41
+ };
@@ -0,0 +1,22 @@
1
+ import { StyleSheet } from "react-native";
2
+ import { globalStyles } from "./globalStyles";
3
+
4
+ /* Bottom menu navigation bar styles */
5
+ export const menuNavStyles = StyleSheet.create({
6
+ container: {
7
+ // Bottom navigation container
8
+ flexDirection: "row",
9
+ justifyContent: "space-evenly",
10
+ backgroundColor: globalStyles.colors.colorPrimary200,
11
+ height: 60,
12
+ width: "80%",
13
+ alignSelf: "center",
14
+ alignItems: "center",
15
+ borderRadius: globalStyles.borders.borderPrimary300,
16
+ },
17
+
18
+ uploadIcon: {
19
+ // Highlighted upload icon
20
+ color: globalStyles.colors.colorPrimary600,
21
+ },
22
+ });
@@ -0,0 +1,72 @@
1
+ import { StyleSheet, Text, View } from "react-native";
2
+ import { globalStyles } from "../styles/globalStyles";
3
+ import { Ionicons } from "@expo/vector-icons";
4
+
5
+ export function DateViews({
6
+ timeAgo,
7
+ viewsText,
8
+ containerStyles,
9
+ dateTextStyles,
10
+ viewsTextStyles,
11
+ dateContainerStyles,
12
+ }) {
13
+ return (
14
+ <View style={[styles.dateViewsContainer, containerStyles]}>
15
+ <View style={[styles.dateContainer, dateContainerStyles]}>
16
+ <View style={[styles.dateIcon]}>
17
+ <Ionicons name="calendar-clear-outline" size={16} color="white" />
18
+ </View>
19
+ <Text style={[styles.dateText, dateTextStyles]}>
20
+ {timeAgo || "14 Hours Ago"}
21
+ </Text>
22
+ </View>
23
+ <View style={styles.viewsContainer}>
24
+ <View style={[styles.eyeIcon]}>
25
+ <Ionicons name="eye-outline" size={16} color="white" />
26
+ </View>
27
+ <Text style={[styles.viewsText, viewsTextStyles]}>
28
+ {viewsText || "123.400"}
29
+ <Text style={{ color: "white" }}>{" Views"}</Text>
30
+ </Text>
31
+ </View>
32
+ </View>
33
+ );
34
+ }
35
+
36
+ const styles = StyleSheet.create({
37
+ dateViewsContainer: {
38
+ width: "100%",
39
+ height: 30,
40
+ flexDirection: "row",
41
+ justifyContent: "space-between",
42
+ marginTop: 12,
43
+ paddingRight: 8,
44
+ // paddingLeft: 8,
45
+ },
46
+
47
+ dateContainer: {
48
+ flexDirection: "row",
49
+ rowGap: 20,
50
+ paddingRight: 16,
51
+ },
52
+
53
+ dateIcon: {
54
+ marginRight: 8,
55
+ },
56
+
57
+ viewsContainer: {
58
+ flexDirection: "row",
59
+ },
60
+
61
+ eyeIcon: {
62
+ marginRight: 8,
63
+ },
64
+
65
+ dateText: {
66
+ color: globalStyles.colors.colorPrimary600,
67
+ },
68
+
69
+ viewsText: {
70
+ color: globalStyles.colors.colorPrimary600,
71
+ },
72
+ });
File without changes
File without changes
File without changes
@@ -0,0 +1,14 @@
1
+ const normalizeUrl = url => {
2
+ if (!url) return null;
3
+
4
+ // If it already starts with http:// or https://, return as-is
5
+ if (/^https?:\/\//i.test(url)) return url;
6
+
7
+ // If it looks like a domain (e.g. google.com or mysite.org)
8
+ if (/^[\w.-]+\.[a-z]{2,}$/i.test(url)) return `https://${url}`;
9
+
10
+ // Otherwise, not a valid link — return null or handle differently
11
+ return null;
12
+ };
13
+
14
+ export default normalizeUrl;
File without changes