@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.
- package/dist/components/buttons/CustomizableButton.js +1 -1
- package/dist/components/dropdowns/DropDown.js +1 -1
- package/dist/components/headers/HeaderGlobal.js +1 -1
- package/dist/components/inputs/InputComment.js +1 -1
- package/dist/components/inputs/InputField.js +1 -1
- package/dist/components/menus/MenuChannelMeta.js +1 -1
- package/dist/components/menus/MenuInteraction.js +1 -1
- package/dist/components/menus/MenuNav.js +1 -1
- package/dist/components/titles/CustomizedTitle.js +1 -1
- package/dist/styles/MenuChannelMetaStyles.js +1 -1
- package/dist/utils/DateViews.js +1 -1
- package/package.json +38 -34
- package/src/components/buttons/CustomizableButton.jsx +85 -0
- package/src/components/dropdowns/DropDown.jsx +60 -0
- package/src/components/headers/HeaderGlobal.jsx +38 -0
- package/src/components/inputs/InputComment.jsx +27 -0
- package/src/components/inputs/InputField.jsx +113 -0
- package/src/components/menus/MenuChannelMeta.jsx +115 -0
- package/src/components/menus/MenuInteraction.jsx +68 -0
- package/src/components/menus/MenuNav.jsx +48 -0
- package/src/components/titles/CustomizedTitle.jsx +62 -0
- package/src/index.js +11 -0
- package/src/styles/MenuChannelMetaStyles.js +127 -0
- package/src/styles/MenuInteractionStyles.js +23 -0
- package/src/styles/commentInputStyles.js +27 -0
- package/src/styles/customizableButtonStyles.js +30 -0
- package/src/styles/dropDownStyles.js +57 -0
- package/src/styles/globalStyles.js +27 -0
- package/src/styles/headerGlobalStyles.js +30 -0
- package/src/styles/inputStyles.js +41 -0
- package/src/styles/menuNavStyles.js +22 -0
- package/src/utils/DateViews.jsx +72 -0
- package/src/utils/dateHelpers.js +0 -0
- package/src/utils/formatters.js +0 -0
- package/src/utils/index.js +0 -0
- package/src/utils/normalizeUrl.js +14 -0
- 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
|