rn-vs-lb 1.0.65 → 1.0.67
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 +1 -0
- package/components/Button/Button.stories.tsx +1 -1
- package/components/Button/DeleteAccountButton.stories.tsx +1 -1
- package/components/Button/PostButton.stories.tsx +1 -1
- package/components/Button/TelegramFeedbackLink.stories.tsx +1 -1
- package/components/Cards/BusinessIdeaCard.stories.tsx +73 -0
- package/components/Cards/BusinessIdeaCard.tsx +251 -0
- package/components/Cards/EventCard.stories.tsx +1 -1
- package/components/Cards/PlaceCard.stories.tsx +1 -1
- package/components/Cards/index.tsx +1 -0
- package/components/Chat/ChatItem.stories.tsx +1 -1
- package/components/Chat/ChatTab/ChatTabs.stories.tsx +1 -1
- package/components/Chat/ChatTab/SubTabButton.stories.tsx +1 -1
- package/components/Chat/InputMessage.stories.tsx +1 -1
- package/components/Chat/InputMessage.tsx +2 -1
- package/components/Chat/MessageItem/LinkyText.tsx +9 -1
- package/components/Chat/MessageItem/MessageItem.stories.tsx +1 -1
- package/components/Chat/MessageItem/styles.ts +3 -0
- package/components/Chat/PinnedMessagesBar/PinnedMessagesBar.stories.tsx +1 -1
- package/components/Gallery/AiAgentGallery.stories.tsx +1 -1
- package/components/Gallery/ProfileMediaGallery.stories.tsx +66 -0
- package/components/Gallery/ProfileMediaGallery.tsx +239 -0
- package/components/Gallery/ProfileMediaNavigationItem.tsx +73 -0
- package/components/Gallery/ProfileSelfiesGallery.pure.tsx +97 -0
- package/components/Gallery/ProfileSelfiesGallery.stories.tsx +86 -0
- package/components/Gallery/index.ts +4 -1
- package/components/Header/HeaderDefault.stories.tsx +1 -1
- package/components/Header/HeaderEdit.stories.tsx +1 -1
- package/components/Header/HeaderHome.stories.tsx +1 -1
- package/components/Header/HeaderSwitcher.stories.tsx +1 -1
- package/components/Header/HeaderWithImg.stories.tsx +1 -1
- package/components/Modals/GalleryModal.stories.tsx +1 -1
- package/components/Modals/GuestAiChatModal.stories.tsx +1 -1
- package/components/Modals/ReportModal.stories.tsx +1 -1
- package/components/Modals/ReportModal.tsx +1 -1
- package/components/Poll/CommentItem.stories.tsx +1 -1
- package/components/Poll/PollCardList.stories.tsx +1 -1
- package/components/Posts/EventCardList.stories.tsx +1 -1
- package/components/Posts/PlaceCardList.stories.tsx +1 -1
- package/components/Prank/HeroPrankCard.stories.tsx +47 -0
- package/components/Prank/HeroPrankCard.tsx +114 -0
- package/components/Prank/HomelessPrankPage.stories.tsx +58 -0
- package/components/Prank/UploadPromptCard.stories.tsx +55 -0
- package/components/Prank/UploadPromptCard.tsx +130 -0
- package/components/Prank/index.ts +5 -0
- package/components/Profile/ModalProfilePhoto.stories.tsx +1 -1
- package/components/Profile/ProfileCard/ProfileCard.stories.tsx +1 -1
- package/components/Profile/ProfilePhotoBanner.stories.tsx +1 -1
- package/components/Profile/ProfilePhotoUpload/ProfilePhotoUpload.stories.tsx +1 -1
- package/components/Profile/ProfileSummary/ProfileSummary.stories.tsx +52 -0
- package/components/Profile/ProfileSummary/ProfileSummary.tsx +188 -0
- package/components/Profile/ProfileTabs/UserProfileTabs.stories.tsx +1 -1
- package/components/Profile/index.ts +2 -0
- package/components/Screens/AppScreens.stories.tsx +572 -0
- package/components/Screens/HomeScreen.stories.tsx +159 -0
- package/components/Screens/HoroscopeScreen.stories.tsx +299 -0
- package/components/Screens/LibraryScreen.stories.tsx +97 -0
- package/components/Settings/AppSettingsScreen.stories.tsx +134 -0
- package/components/Settings/AppSettingsScreen.tsx +215 -0
- package/components/Settings/SettingsHeader.tsx +37 -0
- package/components/Settings/SettingsListItem.tsx +142 -0
- package/components/Settings/SettingsManageAccountButton.tsx +58 -0
- package/components/Settings/SettingsOptionList.tsx +82 -0
- package/components/Settings/SettingsProfileCard.tsx +58 -0
- package/components/Settings/SettingsScreen.stories.tsx +66 -0
- package/components/Settings/SettingsScreen.tsx +129 -0
- package/components/Settings/SettingsSection.tsx +56 -0
- package/components/Settings/SettingsToggleItem.tsx +70 -0
- package/components/Settings/index.ts +23 -0
- package/components/Specialist/Hero.stories.tsx +1 -1
- package/components/Specialist/PortfolioCarousel.stories.tsx +1 -1
- package/components/Specialist/ServicesList.stories.tsx +1 -1
- package/components/Tooltip/DangerTooltip.stories.tsx +1 -1
- package/components/Tooltip/InfoTooltip.stories.tsx +1 -1
- package/components/Tooltip/InfoTooltipBase.stories.tsx +1 -1
- package/components/Tooltip/SucceedTooltip.stories.tsx +1 -1
- package/components/Tooltip/WarningTooltip.stories.tsx +1 -1
- package/components/UI/HorizontalCardSection.stories.tsx +85 -0
- package/components/UI/HorizontalCardSection.tsx +199 -0
- package/components/UI/TabBar/BottomTabBar.stories.tsx +87 -0
- package/components/UI/TabBar/BottomTabBar.tsx +128 -0
- package/components/UI/index.ts +4 -0
- package/components/UserCards/AiAgentHeroCard.stories.tsx +66 -0
- package/components/UserCards/AiAgentHeroCard.tsx +242 -0
- package/components/UserCards/Organizer.stories.tsx +1 -1
- package/components/UserCards/SpecialistCard.stories.tsx +1 -1
- package/components/UserCards/StoryCard.stories.tsx +1 -1
- package/components/UserCards/UserProfileCard.stories.tsx +1 -1
- package/components/UserCards/UserRow.stories.tsx +1 -1
- package/components/UserCards/index.ts +2 -0
- package/components/index.ts +2 -0
- package/index.ts +1 -0
- package/package.json +3 -1
- package/theme/index.ts +2 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useMemo, useState, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Image,
|
|
4
|
+
Pressable,
|
|
5
|
+
ScrollView,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
View,
|
|
9
|
+
useWindowDimensions,
|
|
10
|
+
} from "react-native";
|
|
11
|
+
import { ThemeType, SizesType, TypographytType, useTheme } from "../../theme";
|
|
12
|
+
import { GalleryModal } from "../Modals";
|
|
13
|
+
import { ProfileMediaNavigationItem, ProfileMediaNavigationItemProps } from "./ProfileMediaNavigationItem";
|
|
14
|
+
|
|
15
|
+
type TabItem = Omit<ProfileMediaNavigationItemProps, "isActive" | "onPress"> & {
|
|
16
|
+
key: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ProfileMediaGalleryProps = {
|
|
20
|
+
tabs: TabItem[];
|
|
21
|
+
activeTabKey?: string;
|
|
22
|
+
onTabPress?: (tab: TabItem, index: number) => void;
|
|
23
|
+
images: string[];
|
|
24
|
+
extraInfoLabel?: string;
|
|
25
|
+
extraInfoActionLabel?: string;
|
|
26
|
+
onExtraInfoPress?: () => void;
|
|
27
|
+
uploadButtonLabel?: string;
|
|
28
|
+
showAllButtonLabel?: string;
|
|
29
|
+
onUploadPress?: () => void;
|
|
30
|
+
onShowAllPress?: () => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const FALLBACK_IMAGES = [
|
|
34
|
+
"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=600&q=60",
|
|
35
|
+
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?auto=format&fit=crop&w=600&q=60",
|
|
36
|
+
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=600&q=60",
|
|
37
|
+
"https://images.unsplash.com/photo-1557862921-37829c790f19?auto=format&fit=crop&w=600&q=60",
|
|
38
|
+
"https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=600&q=60",
|
|
39
|
+
"https://images.unsplash.com/photo-1520813792240-56fc4a3765a7?auto=format&fit=crop&w=600&q=60",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export const ProfileMediaGallery: React.FC<ProfileMediaGalleryProps> = ({
|
|
43
|
+
tabs = [],
|
|
44
|
+
activeTabKey,
|
|
45
|
+
onTabPress,
|
|
46
|
+
images = [],
|
|
47
|
+
extraInfoLabel = "+14 photo tags",
|
|
48
|
+
extraInfoActionLabel = "View",
|
|
49
|
+
onExtraInfoPress,
|
|
50
|
+
uploadButtonLabel = "Upload photo",
|
|
51
|
+
showAllButtonLabel = "Show all",
|
|
52
|
+
onUploadPress,
|
|
53
|
+
onShowAllPress,
|
|
54
|
+
}) => {
|
|
55
|
+
const { theme, sizes, typography } = useTheme();
|
|
56
|
+
const { width } = useWindowDimensions();
|
|
57
|
+
const styles = useMemo(() => getStyles(theme, sizes, typography), [theme, sizes, typography]);
|
|
58
|
+
const [internalActiveKey, setInternalActiveKey] = useState<string>(
|
|
59
|
+
activeTabKey ?? tabs[0]?.key ?? ""
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (activeTabKey) {
|
|
64
|
+
setInternalActiveKey(activeTabKey);
|
|
65
|
+
}
|
|
66
|
+
}, [activeTabKey]);
|
|
67
|
+
|
|
68
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
69
|
+
const [modalInitialIndex, setModalInitialIndex] = useState(0);
|
|
70
|
+
|
|
71
|
+
const displayImages = images.length ? images.slice(0, 6) : FALLBACK_IMAGES;
|
|
72
|
+
|
|
73
|
+
const contentHorizontalPadding = (sizes.lg as number) * 2;
|
|
74
|
+
const gutter = (sizes.xs as number) * 2;
|
|
75
|
+
const imageSize = Math.max(
|
|
76
|
+
72,
|
|
77
|
+
Math.floor((width - contentHorizontalPadding - gutter * 2) / 3)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const handleTabPress = (tab: TabItem, index: number) => {
|
|
81
|
+
if (!activeTabKey) {
|
|
82
|
+
setInternalActiveKey(tab.key);
|
|
83
|
+
}
|
|
84
|
+
onTabPress?.(tab, index);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleOpenImage = (index: number) => {
|
|
88
|
+
setModalInitialIndex(index);
|
|
89
|
+
setModalVisible(true);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<View style={styles.wrapper}>
|
|
94
|
+
<ScrollView
|
|
95
|
+
horizontal
|
|
96
|
+
showsHorizontalScrollIndicator={false}
|
|
97
|
+
contentContainerStyle={styles.navigationContainer}
|
|
98
|
+
>
|
|
99
|
+
{tabs.map((tab, index) => (
|
|
100
|
+
<ProfileMediaNavigationItem
|
|
101
|
+
key={tab.key}
|
|
102
|
+
label={tab.label}
|
|
103
|
+
iconName={tab.iconName}
|
|
104
|
+
isActive={internalActiveKey === tab.key}
|
|
105
|
+
onPress={() => handleTabPress(tab, index)}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</ScrollView>
|
|
109
|
+
|
|
110
|
+
<View style={styles.galleryContainer}>
|
|
111
|
+
{displayImages.map((uri, index) => (
|
|
112
|
+
<Pressable
|
|
113
|
+
key={`${uri}-${index}`}
|
|
114
|
+
onPress={() => handleOpenImage(index)}
|
|
115
|
+
style={[styles.imageWrapper, { width: imageSize, height: imageSize }]}
|
|
116
|
+
android_ripple={{ color: "#00000022" }}
|
|
117
|
+
>
|
|
118
|
+
<Image
|
|
119
|
+
source={{ uri }}
|
|
120
|
+
style={styles.image}
|
|
121
|
+
resizeMode="cover"
|
|
122
|
+
/>
|
|
123
|
+
</Pressable>
|
|
124
|
+
))}
|
|
125
|
+
</View>
|
|
126
|
+
|
|
127
|
+
<View style={styles.infoRow}>
|
|
128
|
+
<Text style={styles.infoLabel}>{extraInfoLabel}</Text>
|
|
129
|
+
<Pressable onPress={onExtraInfoPress}>
|
|
130
|
+
<Text style={styles.infoLink}>{extraInfoActionLabel}</Text>
|
|
131
|
+
</Pressable>
|
|
132
|
+
</View>
|
|
133
|
+
|
|
134
|
+
<View style={styles.actionsRow}>
|
|
135
|
+
<Pressable
|
|
136
|
+
onPress={onUploadPress}
|
|
137
|
+
style={[styles.actionButton, styles.primaryActionButton]}
|
|
138
|
+
>
|
|
139
|
+
<Text style={[styles.actionButtonText, styles.primaryActionButtonText]}>
|
|
140
|
+
{uploadButtonLabel}
|
|
141
|
+
</Text>
|
|
142
|
+
</Pressable>
|
|
143
|
+
<Pressable
|
|
144
|
+
onPress={onShowAllPress}
|
|
145
|
+
style={[styles.actionButton, styles.secondaryActionButton]}
|
|
146
|
+
>
|
|
147
|
+
<Text style={[styles.actionButtonText, styles.secondaryActionButtonText]}>
|
|
148
|
+
{showAllButtonLabel}
|
|
149
|
+
</Text>
|
|
150
|
+
</Pressable>
|
|
151
|
+
</View>
|
|
152
|
+
|
|
153
|
+
<GalleryModal
|
|
154
|
+
visible={modalVisible}
|
|
155
|
+
images={displayImages}
|
|
156
|
+
initialIndex={modalInitialIndex}
|
|
157
|
+
onRequestClose={() => setModalVisible(false)}
|
|
158
|
+
/>
|
|
159
|
+
</View>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const getStyles = (theme: ThemeType, sizes: SizesType, typography: TypographytType) =>
|
|
164
|
+
StyleSheet.create({
|
|
165
|
+
wrapper: {
|
|
166
|
+
backgroundColor: theme.card,
|
|
167
|
+
borderRadius: sizes.radius_lg as number,
|
|
168
|
+
paddingVertical: sizes.lg as number,
|
|
169
|
+
paddingHorizontal: sizes.lg as number,
|
|
170
|
+
},
|
|
171
|
+
navigationContainer: {
|
|
172
|
+
paddingBottom: sizes.sm as number,
|
|
173
|
+
},
|
|
174
|
+
galleryContainer: {
|
|
175
|
+
flexDirection: "row",
|
|
176
|
+
flexWrap: "wrap",
|
|
177
|
+
justifyContent: "space-between",
|
|
178
|
+
},
|
|
179
|
+
imageWrapper: {
|
|
180
|
+
borderRadius: sizes.radius_sm as number,
|
|
181
|
+
overflow: "hidden",
|
|
182
|
+
marginBottom: sizes.sm as number,
|
|
183
|
+
backgroundColor: theme.backgroundSecond,
|
|
184
|
+
},
|
|
185
|
+
image: {
|
|
186
|
+
width: "100%",
|
|
187
|
+
height: "100%",
|
|
188
|
+
},
|
|
189
|
+
infoRow: {
|
|
190
|
+
flexDirection: "row",
|
|
191
|
+
alignItems: "center",
|
|
192
|
+
justifyContent: "space-between",
|
|
193
|
+
marginTop: sizes.xs as number,
|
|
194
|
+
marginBottom: sizes.md as number,
|
|
195
|
+
},
|
|
196
|
+
infoLabel: {
|
|
197
|
+
...(typography.bodySm as object),
|
|
198
|
+
color: theme.text,
|
|
199
|
+
fontWeight: "500",
|
|
200
|
+
},
|
|
201
|
+
infoLink: {
|
|
202
|
+
...(typography.textLink as object),
|
|
203
|
+
fontWeight: "600",
|
|
204
|
+
},
|
|
205
|
+
actionsRow: {
|
|
206
|
+
flexDirection: "row",
|
|
207
|
+
justifyContent: "space-between",
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
marginTop: sizes.xs as number,
|
|
210
|
+
},
|
|
211
|
+
actionButton: {
|
|
212
|
+
flex: 1,
|
|
213
|
+
borderRadius: sizes.radius_sm as number,
|
|
214
|
+
paddingVertical: sizes.sm as number,
|
|
215
|
+
alignItems: "center",
|
|
216
|
+
justifyContent: "center",
|
|
217
|
+
},
|
|
218
|
+
primaryActionButton: {
|
|
219
|
+
backgroundColor: theme.primary,
|
|
220
|
+
marginRight: sizes.sm as number,
|
|
221
|
+
},
|
|
222
|
+
secondaryActionButton: {
|
|
223
|
+
backgroundColor: theme.backgroundSecond,
|
|
224
|
+
borderWidth: 1,
|
|
225
|
+
borderColor: theme.border,
|
|
226
|
+
},
|
|
227
|
+
actionButtonText: {
|
|
228
|
+
...(typography.bodySm as object),
|
|
229
|
+
fontWeight: "600",
|
|
230
|
+
},
|
|
231
|
+
primaryActionButtonText: {
|
|
232
|
+
color: theme.white,
|
|
233
|
+
},
|
|
234
|
+
secondaryActionButtonText: {
|
|
235
|
+
color: theme.text,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
export default ProfileMediaGallery;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React, { memo, useMemo } from "react";
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { MaterialIcons } from "@expo/vector-icons";
|
|
4
|
+
import { ThemeType, SizesType, TypographytType, useTheme } from "../../theme";
|
|
5
|
+
|
|
6
|
+
type IconName = React.ComponentProps<typeof MaterialIcons>["name"];
|
|
7
|
+
|
|
8
|
+
export type ProfileMediaNavigationItemProps = {
|
|
9
|
+
label: string;
|
|
10
|
+
iconName?: IconName;
|
|
11
|
+
isActive?: boolean;
|
|
12
|
+
onPress?: () => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const ProfileMediaNavigationItem = memo(
|
|
16
|
+
({ label, iconName = "photo", isActive = false, onPress }: ProfileMediaNavigationItemProps) => {
|
|
17
|
+
const { theme, sizes, typography } = useTheme();
|
|
18
|
+
|
|
19
|
+
const styles = useMemo(() => getStyles(theme, sizes, typography), [theme, sizes, typography]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Pressable
|
|
23
|
+
onPress={onPress}
|
|
24
|
+
style={[styles.container, isActive ? styles.containerActive : styles.containerInactive]}
|
|
25
|
+
accessibilityRole="button"
|
|
26
|
+
accessibilityState={{ selected: isActive }}
|
|
27
|
+
>
|
|
28
|
+
{iconName ? (
|
|
29
|
+
<View style={styles.iconWrapper}>
|
|
30
|
+
<MaterialIcons
|
|
31
|
+
name={iconName}
|
|
32
|
+
size={20}
|
|
33
|
+
color={isActive ? theme.primary : theme.greyText}
|
|
34
|
+
/>
|
|
35
|
+
</View>
|
|
36
|
+
) : null}
|
|
37
|
+
<Text style={[styles.label, { color: isActive ? theme.primary : theme.greyText }]}>{label}</Text>
|
|
38
|
+
</Pressable>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
ProfileMediaNavigationItem.displayName = "ProfileMediaNavigationItem";
|
|
44
|
+
|
|
45
|
+
const getStyles = (theme: ThemeType, sizes: SizesType, typography: TypographytType) =>
|
|
46
|
+
StyleSheet.create({
|
|
47
|
+
container: {
|
|
48
|
+
flexDirection: "row",
|
|
49
|
+
alignItems: "center",
|
|
50
|
+
borderRadius: sizes.radius_sm as number,
|
|
51
|
+
paddingHorizontal: sizes.md as number,
|
|
52
|
+
paddingVertical: sizes.xs as number,
|
|
53
|
+
marginRight: sizes.sm as number,
|
|
54
|
+
borderWidth: 1,
|
|
55
|
+
},
|
|
56
|
+
containerActive: {
|
|
57
|
+
backgroundColor: theme.white,
|
|
58
|
+
borderColor: theme.primary,
|
|
59
|
+
},
|
|
60
|
+
containerInactive: {
|
|
61
|
+
backgroundColor: theme.backgroundSecond,
|
|
62
|
+
borderColor: theme.border,
|
|
63
|
+
},
|
|
64
|
+
iconWrapper: {
|
|
65
|
+
marginRight: sizes.xs as number,
|
|
66
|
+
},
|
|
67
|
+
label: {
|
|
68
|
+
...(typography.bodySm as object),
|
|
69
|
+
fontWeight: "500",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export default ProfileMediaNavigationItem;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React, { memo, useMemo } from "react";
|
|
2
|
+
import { Image, Pressable, StyleSheet, View, StyleProp, ViewStyle } from "react-native";
|
|
3
|
+
import { GalleryModal } from "../Modals";
|
|
4
|
+
import { ThemeType, SizesType, useTheme } from "../../theme";
|
|
5
|
+
|
|
6
|
+
type BaseProps = {
|
|
7
|
+
photos: string[];
|
|
8
|
+
columns?: number;
|
|
9
|
+
itemSize: number;
|
|
10
|
+
gap?: number;
|
|
11
|
+
visible: boolean;
|
|
12
|
+
initialIndex: number;
|
|
13
|
+
onOpenAt: (index: number) => void;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ProfileSelfiesGalleryViewProps = BaseProps & {
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const ProfileSelfiesGalleryView = memo(
|
|
22
|
+
({
|
|
23
|
+
photos,
|
|
24
|
+
columns = 2,
|
|
25
|
+
itemSize,
|
|
26
|
+
gap,
|
|
27
|
+
visible,
|
|
28
|
+
initialIndex,
|
|
29
|
+
onOpenAt,
|
|
30
|
+
onClose,
|
|
31
|
+
style,
|
|
32
|
+
}: ProfileSelfiesGalleryViewProps) => {
|
|
33
|
+
const { theme, sizes } = useTheme();
|
|
34
|
+
const spacing = gap ?? ((sizes.sm as number) * 1.25);
|
|
35
|
+
|
|
36
|
+
const styles = useMemo(() => getStyles(theme, sizes, spacing), [theme, sizes, spacing]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<View style={[styles.wrapper, style]}>
|
|
40
|
+
<View style={styles.grid}>
|
|
41
|
+
{photos.map((photo, index) => {
|
|
42
|
+
const isLastInRow = (index + 1) % columns === 0;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Pressable
|
|
46
|
+
key={`${photo}-${index}`}
|
|
47
|
+
onPress={() => onOpenAt(index)}
|
|
48
|
+
android_ripple={{ color: "#00000022" }}
|
|
49
|
+
style={[
|
|
50
|
+
styles.imageWrapper,
|
|
51
|
+
{ width: itemSize, height: itemSize, marginRight: isLastInRow ? 0 : spacing },
|
|
52
|
+
]}
|
|
53
|
+
>
|
|
54
|
+
<Image source={{ uri: photo }} style={styles.image} resizeMode="cover" />
|
|
55
|
+
</Pressable>
|
|
56
|
+
);
|
|
57
|
+
})}
|
|
58
|
+
</View>
|
|
59
|
+
|
|
60
|
+
<GalleryModal
|
|
61
|
+
visible={visible}
|
|
62
|
+
images={photos}
|
|
63
|
+
initialIndex={initialIndex}
|
|
64
|
+
onRequestClose={onClose}
|
|
65
|
+
/>
|
|
66
|
+
</View>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
ProfileSelfiesGalleryView.displayName = "ProfileSelfiesGalleryView";
|
|
72
|
+
|
|
73
|
+
const getStyles = (theme: ThemeType, sizes: SizesType, spacing: number) =>
|
|
74
|
+
StyleSheet.create({
|
|
75
|
+
wrapper: {
|
|
76
|
+
backgroundColor: theme.card,
|
|
77
|
+
borderRadius: (sizes.radius_lg as number) + 4,
|
|
78
|
+
padding: sizes.lg as number,
|
|
79
|
+
},
|
|
80
|
+
grid: {
|
|
81
|
+
flexDirection: "row",
|
|
82
|
+
flexWrap: "wrap",
|
|
83
|
+
justifyContent: "flex-start",
|
|
84
|
+
},
|
|
85
|
+
imageWrapper: {
|
|
86
|
+
borderRadius: (sizes.radius_lg as number) + 2,
|
|
87
|
+
overflow: "hidden",
|
|
88
|
+
marginBottom: spacing,
|
|
89
|
+
backgroundColor: theme.backgroundSecond,
|
|
90
|
+
},
|
|
91
|
+
image: {
|
|
92
|
+
width: "100%",
|
|
93
|
+
height: "100%",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export default ProfileSelfiesGalleryView;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import { Meta, StoryFn } from '@storybook/react';
|
|
3
|
+
import { Dimensions, View } from 'react-native';
|
|
4
|
+
import { ThemeProvider } from '../../theme';
|
|
5
|
+
import { ProfileSelfiesGalleryView } from './ProfileSelfiesGallery.pure';
|
|
6
|
+
|
|
7
|
+
type Props = React.ComponentProps<typeof ProfileSelfiesGalleryView>;
|
|
8
|
+
|
|
9
|
+
const WINDOW_WIDTH = Dimensions.get('window').width;
|
|
10
|
+
|
|
11
|
+
const meta: Meta<Props> = {
|
|
12
|
+
title: 'Features/Gallery/ProfileSelfiesGallery',
|
|
13
|
+
component: ProfileSelfiesGalleryView,
|
|
14
|
+
decorators: [
|
|
15
|
+
(Story) => (
|
|
16
|
+
<ThemeProvider>
|
|
17
|
+
<View style={{ paddingVertical: 24, paddingHorizontal: 16 }}>
|
|
18
|
+
<Story />
|
|
19
|
+
</View>
|
|
20
|
+
</ThemeProvider>
|
|
21
|
+
),
|
|
22
|
+
],
|
|
23
|
+
args: {
|
|
24
|
+
columns: 2,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
|
|
30
|
+
const Template: StoryFn<Props> = ({
|
|
31
|
+
visible: _visible,
|
|
32
|
+
initialIndex: _initialIndex,
|
|
33
|
+
onOpenAt: _onOpenAt,
|
|
34
|
+
onClose: _onClose,
|
|
35
|
+
itemSize: _itemSize,
|
|
36
|
+
...rest
|
|
37
|
+
}) => {
|
|
38
|
+
const [visible, setVisible] = useState(false);
|
|
39
|
+
const [initialIndex, setInitialIndex] = useState(0);
|
|
40
|
+
|
|
41
|
+
const itemSize = useMemo(() => {
|
|
42
|
+
const decoratorPadding = 32; // paddingHorizontal from decorator View (16 * 2)
|
|
43
|
+
const wrapperPadding = 40; // padding from component wrapper (20 * 2)
|
|
44
|
+
const gap = rest.gap ?? 15; // default spacing from component
|
|
45
|
+
const columns = rest.columns ?? 2;
|
|
46
|
+
|
|
47
|
+
return Math.floor((WINDOW_WIDTH - decoratorPadding - wrapperPadding - gap * (columns - 1)) / columns);
|
|
48
|
+
}, [rest.columns, rest.gap]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ProfileSelfiesGalleryView
|
|
52
|
+
{...rest}
|
|
53
|
+
itemSize={itemSize}
|
|
54
|
+
visible={visible}
|
|
55
|
+
initialIndex={initialIndex}
|
|
56
|
+
onOpenAt={(index) => {
|
|
57
|
+
setInitialIndex(index);
|
|
58
|
+
setVisible(true);
|
|
59
|
+
}}
|
|
60
|
+
onClose={() => setVisible(false)}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const Default = Template.bind({});
|
|
66
|
+
Default.args = {
|
|
67
|
+
photos: [
|
|
68
|
+
'https://images.unsplash.com/photo-1690733583113-10a2dbd4c3fb?auto=format&fit=crop&w=800&q=80',
|
|
69
|
+
'https://images.unsplash.com/photo-1633332755192-727a05c4013d?auto=format&fit=crop&w=800&q=80',
|
|
70
|
+
'https://images.unsplash.com/photo-1544723795-3fb6469f5b39?auto=format&fit=crop&w=800&q=80',
|
|
71
|
+
'https://images.unsplash.com/photo-1517841905240-472988babdf9?auto=format&fit=crop&w=800&q=80',
|
|
72
|
+
'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
|
|
73
|
+
'https://images.unsplash.com/photo-1520813792240-56fc4a3765a7?auto=format&fit=crop&w=800&q=80',
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const WithCustomGap = Template.bind({});
|
|
78
|
+
WithCustomGap.args = {
|
|
79
|
+
gap: 12,
|
|
80
|
+
photos: [
|
|
81
|
+
'https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=800&q=80',
|
|
82
|
+
'https://images.unsplash.com/photo-1531256456869-ce942a665e80?auto=format&fit=crop&w=800&q=80',
|
|
83
|
+
'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
|
|
84
|
+
'https://images.unsplash.com/photo-1531891437562-4301cf35b7e4?auto=format&fit=crop&w=800&q=80',
|
|
85
|
+
],
|
|
86
|
+
};
|
|
@@ -4,7 +4,7 @@ import { Text, TouchableOpacity, View } from 'react-native';
|
|
|
4
4
|
import HeaderDefault, { AccessType } from './HeaderDefault';
|
|
5
5
|
|
|
6
6
|
const meta: Meta = {
|
|
7
|
-
title: 'Header/HeaderDefault',
|
|
7
|
+
title: 'Layout/Header/HeaderDefault',
|
|
8
8
|
component: HeaderDefault,
|
|
9
9
|
args: {
|
|
10
10
|
title: 'Community event',
|
|
@@ -4,7 +4,7 @@ import { Text, View } from 'react-native';
|
|
|
4
4
|
import { HeaderHome } from './HeaderHome';
|
|
5
5
|
|
|
6
6
|
const meta: Meta<typeof HeaderHome> = {
|
|
7
|
-
title: 'Header/HeaderHome',
|
|
7
|
+
title: 'Layout/Header/HeaderHome',
|
|
8
8
|
component: HeaderHome,
|
|
9
9
|
argTypes: {
|
|
10
10
|
onPress: { action: 'header press' },
|
|
@@ -4,7 +4,7 @@ import { View, Text } from 'react-native';
|
|
|
4
4
|
import { HeaderSwitcher } from './HeaderSwitcher';
|
|
5
5
|
|
|
6
6
|
const meta: Meta<typeof HeaderSwitcher> = {
|
|
7
|
-
title: 'Header/HeaderSwitcher',
|
|
7
|
+
title: 'Layout/Header/HeaderSwitcher',
|
|
8
8
|
component: HeaderSwitcher,
|
|
9
9
|
args: {
|
|
10
10
|
isFirst: true,
|
|
@@ -10,7 +10,7 @@ const IMAGES = [
|
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
const meta = {
|
|
13
|
-
title: 'Modals/GalleryModal',
|
|
13
|
+
title: 'Features/Modals/GalleryModal',
|
|
14
14
|
component: GalleryModal,
|
|
15
15
|
decorators: [(Story) => <View style={{ flex: 1 }}><Story /></View>],
|
|
16
16
|
argTypes: { onRequestClose: { action: 'onRequestClose' } },
|
|
@@ -24,7 +24,7 @@ const sampleMessages: PureChatMessage[] = [
|
|
|
24
24
|
const defaultLimitLabel = (remaining: number, limit: number) => `Осталось сообщений: ${remaining} / ${limit}`;
|
|
25
25
|
|
|
26
26
|
const meta = {
|
|
27
|
-
title: 'Modals/GuestAiChatModal',
|
|
27
|
+
title: 'Features/Modals/GuestAiChatModal',
|
|
28
28
|
component: GuestAiChatModalView,
|
|
29
29
|
argTypes: {
|
|
30
30
|
onClose: { action: 'close modal' },
|
|
@@ -62,7 +62,7 @@ const ReportModal: React.FC<Props> = ({
|
|
|
62
62
|
<View style={styles.container}>
|
|
63
63
|
<Text style={styles.title}>{title}</Text>
|
|
64
64
|
<ScrollView style={styles.reasonsContainer}>
|
|
65
|
-
{reasons
|
|
65
|
+
{reasons?.map((reason) => (
|
|
66
66
|
<TouchableOpacity
|
|
67
67
|
key={reason}
|
|
68
68
|
style={[
|
|
@@ -4,7 +4,7 @@ import { View } from 'react-native';
|
|
|
4
4
|
import PollCardList from './PollCardList';
|
|
5
5
|
|
|
6
6
|
const meta: Meta<React.ComponentProps<typeof PollCardList>> = {
|
|
7
|
-
title: 'Poll/PollCardList',
|
|
7
|
+
title: 'Features/Poll/PollCardList',
|
|
8
8
|
component: PollCardList,
|
|
9
9
|
decorators: [
|
|
10
10
|
(Story) => (
|
|
@@ -7,7 +7,7 @@ import EventCardList from './EventCardList';
|
|
|
7
7
|
type Props = React.ComponentProps<typeof EventCardList>;
|
|
8
8
|
|
|
9
9
|
const meta: Meta<Props> = {
|
|
10
|
-
title: 'Posts/EventCardList',
|
|
10
|
+
title: 'Features/Posts/EventCardList',
|
|
11
11
|
component: EventCardList,
|
|
12
12
|
decorators: [
|
|
13
13
|
(Story) => (
|
|
@@ -4,7 +4,7 @@ import { View } from 'react-native';
|
|
|
4
4
|
import PlaceCardList from './PlaceCardList';
|
|
5
5
|
|
|
6
6
|
const meta: Meta<React.ComponentProps<typeof PlaceCardList>> = {
|
|
7
|
-
title: 'Posts/PlaceCardList',
|
|
7
|
+
title: 'Features/Posts/PlaceCardList',
|
|
8
8
|
component: PlaceCardList,
|
|
9
9
|
decorators: [
|
|
10
10
|
(Story) => (
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { ThemeProvider } from '../../theme';
|
|
6
|
+
import HeroPrankCard from './HeroPrankCard';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof HeroPrankCard> = {
|
|
9
|
+
title: 'Features/Prank/HeroPrankCard',
|
|
10
|
+
component: HeroPrankCard,
|
|
11
|
+
decorators: [
|
|
12
|
+
(Story) => (
|
|
13
|
+
<ThemeProvider>
|
|
14
|
+
<View style={{ padding: 24, backgroundColor: '#070C1F', flex: 1 }}>
|
|
15
|
+
<Story />
|
|
16
|
+
</View>
|
|
17
|
+
</ThemeProvider>
|
|
18
|
+
),
|
|
19
|
+
],
|
|
20
|
+
args: {
|
|
21
|
+
imageUri:
|
|
22
|
+
'https://images.unsplash.com/photo-1621447462558-42b4d8bc5d9b?auto=format&fit=crop&w=900&q=80',
|
|
23
|
+
title: 'Homeless Prank',
|
|
24
|
+
description: 'Prank your loved ones with a unknown guest in your home!',
|
|
25
|
+
},
|
|
26
|
+
parameters: {
|
|
27
|
+
backgrounds: {
|
|
28
|
+
default: 'dark',
|
|
29
|
+
values: [
|
|
30
|
+
{ name: 'dark', value: '#070C1F' },
|
|
31
|
+
{ name: 'light', value: '#f5f5f5' },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
|
|
39
|
+
type Story = StoryObj<typeof HeroPrankCard>;
|
|
40
|
+
|
|
41
|
+
export const Default: Story = {};
|
|
42
|
+
|
|
43
|
+
export const WithAction: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
onPress: () => console.log('Hero card pressed'),
|
|
46
|
+
},
|
|
47
|
+
};
|