rn-vs-lb 1.0.64 → 1.0.66
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/components/Button/Button.stories.tsx +1 -1
- package/components/Button/DeleteAccountButton.stories.tsx +13 -1
- package/components/Button/DeleteAccountButton.tsx +28 -8
- package/components/Button/PostButton.stories.tsx +1 -1
- package/components/Button/TelegramFeedbackLink.stories.tsx +7 -1
- package/components/Button/TelegramFeedbackLink.tsx +12 -4
- package/components/Cards/BusinessIdeaCard.stories.tsx +73 -0
- package/components/Cards/BusinessIdeaCard.tsx +251 -0
- package/components/Cards/EventCard.stories.tsx +4 -1
- package/components/Cards/EventCard.tsx +9 -2
- 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 +12 -1
- package/components/Chat/InputMessage.tsx +5 -3
- package/components/Chat/MessageItem/MessageItem.stories.tsx +1 -1
- 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 +10 -1
- package/components/Header/HeaderWithImg.tsx +27 -7
- package/components/Modals/GalleryModal.stories.tsx +1 -1
- package/components/Modals/GuestAiChatModal.stories.tsx +12 -4
- package/components/Modals/GuestAiChatModal.tsx +9 -3
- package/components/Modals/ReportModal.stories.tsx +35 -2
- package/components/Modals/ReportModal.tsx +23 -24
- package/components/Poll/CommentItem.stories.tsx +1 -1
- package/components/Poll/PollCardList.stories.tsx +5 -1
- package/components/Poll/PollCardList.tsx +4 -2
- 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 +11 -2
- package/components/Profile/ProfilePhotoBanner.tsx +5 -3
- 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/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 +2 -1
- package/components/Specialist/Hero.tsx +3 -1
- package/components/Specialist/PortfolioCarousel.stories.tsx +1 -1
- package/components/Specialist/ServicesList.stories.tsx +2 -1
- package/components/Specialist/ServicesList.tsx +4 -3
- 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/DeletedState.stories.tsx +6 -1
- package/components/UI/DeletedState.tsx +12 -3
- package/components/UI/DescriptionMore.stories.tsx +2 -0
- package/components/UI/DescriptionMore.tsx +13 -3
- package/components/UI/EmptyState.stories.tsx +4 -1
- package/components/UI/EmptyState.tsx +3 -2
- package/components/UI/HorizontalCardSection.stories.tsx +85 -0
- package/components/UI/HorizontalCardSection.tsx +199 -0
- package/components/UI/NoAuth.stories.tsx +3 -0
- package/components/UI/NoAuth.tsx +7 -6
- package/components/UI/ParticipantItem.stories.tsx +21 -0
- package/components/UI/ParticipantItem.tsx +30 -6
- package/components/UI/TabBar/BottomTabBar.stories.tsx +87 -0
- package/components/UI/TabBar/BottomTabBar.tsx +128 -0
- package/components/UI/ThemeSwitcher.stories.tsx +5 -1
- package/components/UI/ThemeSwitcher.tsx +7 -2
- package/components/UI/UpdateRequiredView.stories.tsx +2 -0
- package/components/UI/UpdateRequiredView.tsx +3 -2
- 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/Organazer.tsx +3 -2
- package/components/UserCards/Organizer.stories.tsx +5 -1
- package/components/UserCards/SpecialistCard.stories.tsx +3 -1
- package/components/UserCards/SpecialistCard.tsx +3 -2
- 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/package.json +2 -1
|
@@ -8,7 +8,7 @@ import InputMessage, { ImageAsset } from './InputMessage';
|
|
|
8
8
|
type Props = React.ComponentProps<typeof InputMessage>;
|
|
9
9
|
|
|
10
10
|
const meta: Meta<Props> = {
|
|
11
|
-
title: 'Chat/InputMessage',
|
|
11
|
+
title: 'Features/Chat/InputMessage',
|
|
12
12
|
component: InputMessage,
|
|
13
13
|
decorators: [
|
|
14
14
|
(Story) => (
|
|
@@ -87,18 +87,21 @@ const Template: StoryFn<Props> = (args) => {
|
|
|
87
87
|
export const Default = Template.bind({});
|
|
88
88
|
Default.args = {
|
|
89
89
|
placeholder: 'Message',
|
|
90
|
+
editingLabel: 'Editing message',
|
|
90
91
|
maxImages: 1,
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
export const AttachmentsDisabled = Template.bind({});
|
|
94
95
|
AttachmentsDisabled.args = {
|
|
95
96
|
placeholder: 'No attachments allowed',
|
|
97
|
+
editingLabel: 'Editing message',
|
|
96
98
|
enableImageAttachment: false,
|
|
97
99
|
};
|
|
98
100
|
|
|
99
101
|
export const WithAttachments = Template.bind({});
|
|
100
102
|
WithAttachments.args = {
|
|
101
103
|
placeholder: 'Attach up to 2 images',
|
|
104
|
+
editingLabel: 'Editing message',
|
|
102
105
|
maxImages: 2,
|
|
103
106
|
};
|
|
104
107
|
WithAttachments.render = (args) => {
|
|
@@ -118,6 +121,7 @@ WithAttachments.render = (args) => {
|
|
|
118
121
|
return imgs;
|
|
119
122
|
}}
|
|
120
123
|
onMaxImagesExceeded={(max) => Alert.alert('Max images exceeded', `Allowed: ${max}`)}
|
|
124
|
+
editingLabel="Editing message"
|
|
121
125
|
/>
|
|
122
126
|
);
|
|
123
127
|
};
|
|
@@ -137,6 +141,8 @@ export const ReplyMode: StoryFn = () => {
|
|
|
137
141
|
onCancelReply={replyCancelHandler}
|
|
138
142
|
onTyping={typingHandler}
|
|
139
143
|
onStopTyping={stopTypingHandler}
|
|
144
|
+
placeholder="Message"
|
|
145
|
+
editingLabel="Editing message"
|
|
140
146
|
/>
|
|
141
147
|
);
|
|
142
148
|
};
|
|
@@ -152,6 +158,8 @@ export const EditMode: StoryFn = () => {
|
|
|
152
158
|
onCancelEdit={editCancelHandler}
|
|
153
159
|
onTyping={typingHandler}
|
|
154
160
|
onStopTyping={stopTypingHandler}
|
|
161
|
+
placeholder="Message"
|
|
162
|
+
editingLabel="Editing message"
|
|
155
163
|
/>
|
|
156
164
|
);
|
|
157
165
|
};
|
|
@@ -176,6 +184,8 @@ export const SendingControlled: StoryFn = () => {
|
|
|
176
184
|
maxImages={1}
|
|
177
185
|
onTyping={typingHandler}
|
|
178
186
|
onStopTyping={stopTypingHandler}
|
|
187
|
+
placeholder="Message"
|
|
188
|
+
editingLabel="Editing message"
|
|
179
189
|
/>
|
|
180
190
|
);
|
|
181
191
|
};
|
|
@@ -200,6 +210,7 @@ export const ManyInputsDemo: StoryFn = () => {
|
|
|
200
210
|
placeholder={`Message #${idx + 1}`}
|
|
201
211
|
onTyping={() => logEvent(`typing-${idx}`)}
|
|
202
212
|
onStopTyping={() => logEvent(`stop-typing-${idx}`)}
|
|
213
|
+
editingLabel="Editing message"
|
|
203
214
|
/>
|
|
204
215
|
))}
|
|
205
216
|
</ScrollView>
|
|
@@ -33,7 +33,8 @@ interface InputMessageProps {
|
|
|
33
33
|
onStopTyping?: () => void; // будет вызвано через ~2s тишины
|
|
34
34
|
|
|
35
35
|
// Placeholder
|
|
36
|
-
placeholder
|
|
36
|
+
placeholder: string;
|
|
37
|
+
editingLabel: string;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export const InputMessage: FC<InputMessageProps> = ({
|
|
@@ -56,7 +57,8 @@ export const InputMessage: FC<InputMessageProps> = ({
|
|
|
56
57
|
onTyping,
|
|
57
58
|
onStopTyping,
|
|
58
59
|
|
|
59
|
-
placeholder
|
|
60
|
+
placeholder,
|
|
61
|
+
editingLabel,
|
|
60
62
|
}) => {
|
|
61
63
|
const { theme, typography } = useTheme();
|
|
62
64
|
const styles = getStyles(theme);
|
|
@@ -119,7 +121,7 @@ export const InputMessage: FC<InputMessageProps> = ({
|
|
|
119
121
|
{/* Edit */}
|
|
120
122
|
{editMessage && (
|
|
121
123
|
<View style={[styles.replyContainer, { borderLeftColor: theme.primaryLight }]}>
|
|
122
|
-
<Text style={styles.replyLabel}>
|
|
124
|
+
<Text style={styles.replyLabel}>{editingLabel}</Text>
|
|
123
125
|
<View style={styles.replyContent}>
|
|
124
126
|
<Text numberOfLines={1} style={styles.replyText}>
|
|
125
127
|
{editMessage.content ?? ''}
|
|
@@ -7,7 +7,7 @@ import MessageItem from './MessageItem';
|
|
|
7
7
|
import { LinkPreviewData, MessageDTO, MessageItemProps } from './types';
|
|
8
8
|
|
|
9
9
|
const meta: Meta<MessageItemProps> = {
|
|
10
|
-
title: 'Chat/MessageItem',
|
|
10
|
+
title: 'Features/Chat/MessageItem',
|
|
11
11
|
component: MessageItem,
|
|
12
12
|
decorators: [
|
|
13
13
|
(Story) => (
|
|
@@ -8,7 +8,7 @@ import { MessageDTO } from '../../../types/message';
|
|
|
8
8
|
type Props = React.ComponentProps<typeof PinnedMessagesBar>;
|
|
9
9
|
|
|
10
10
|
const meta: Meta<Props> = {
|
|
11
|
-
title: 'Chat/PinnedMessagesBar',
|
|
11
|
+
title: 'Features/Chat/PinnedMessagesBar',
|
|
12
12
|
component: PinnedMessagesBar,
|
|
13
13
|
decorators: [
|
|
14
14
|
(Story) => (
|
|
@@ -7,7 +7,7 @@ import { AiAgentGalleryView } from './AiAgentGallery.pure';
|
|
|
7
7
|
type Props = React.ComponentProps<typeof AiAgentGalleryView>;
|
|
8
8
|
|
|
9
9
|
const meta: Meta<Props> = {
|
|
10
|
-
title: 'Gallery/AiAgentGallery',
|
|
10
|
+
title: 'Features/Gallery/AiAgentGallery',
|
|
11
11
|
component: AiAgentGalleryView,
|
|
12
12
|
decorators: [
|
|
13
13
|
(Story) => (
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Meta, StoryFn } from "@storybook/react";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { ThemeProvider } from "../../theme";
|
|
5
|
+
import { ProfileMediaGallery, ProfileMediaGalleryProps } from "./ProfileMediaGallery";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof ProfileMediaGallery> = {
|
|
8
|
+
title: 'Features/Gallery/ProfileMediaGallery',
|
|
9
|
+
component: ProfileMediaGallery,
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<ThemeProvider>
|
|
13
|
+
<View style={{ padding: 24 }}>
|
|
14
|
+
<Story />
|
|
15
|
+
</View>
|
|
16
|
+
</ThemeProvider>
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
const TABS: ProfileMediaGalleryProps["tabs"] = [
|
|
24
|
+
{ key: "photos", label: "Photos", iconName: "collections" },
|
|
25
|
+
{ key: "videos", label: "Videos", iconName: "videocam" },
|
|
26
|
+
{ key: "music", label: "Music", iconName: "music-note" },
|
|
27
|
+
{ key: "albums", label: "Albums", iconName: "photo-album" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const Template: StoryFn<ProfileMediaGalleryProps> = (args) => (
|
|
31
|
+
<ProfileMediaGallery {...args} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const Default = Template.bind({});
|
|
35
|
+
Default.args = {
|
|
36
|
+
tabs: TABS,
|
|
37
|
+
images: [
|
|
38
|
+
"https://images.unsplash.com/photo-1503023345310-bd7c1de61c7d?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
|
+
"https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=600&q=60",
|
|
41
|
+
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=600&q=60",
|
|
42
|
+
"https://images.unsplash.com/photo-1492562080023-ab3db95bfbce?auto=format&fit=crop&w=600&q=60",
|
|
43
|
+
"https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?auto=format&fit=crop&w=600&q=60",
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const ControlledTabs: StoryFn<ProfileMediaGalleryProps> = ({ ...rest }) => {
|
|
48
|
+
const [activeTabKey, setActiveTabKey] = useState(rest.activeTabKey ?? TABS[0].key);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ProfileMediaGallery
|
|
52
|
+
{...rest}
|
|
53
|
+
tabs={rest.tabs ?? TABS}
|
|
54
|
+
activeTabKey={activeTabKey}
|
|
55
|
+
onTabPress={(tab, index) => {
|
|
56
|
+
setActiveTabKey(tab.key);
|
|
57
|
+
rest.onTabPress?.(tab, index);
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
ControlledTabs.args = {
|
|
64
|
+
tabs: TABS,
|
|
65
|
+
images: Default.args?.images ?? [],
|
|
66
|
+
};
|
|
@@ -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
|
+
};
|