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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ScrollView, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { ThemeProvider, useTheme } from '../../theme';
|
|
6
|
+
import Spacer from '../UI/Spacer';
|
|
7
|
+
import HeroPrankCard from './HeroPrankCard';
|
|
8
|
+
import UploadPromptCard from './UploadPromptCard';
|
|
9
|
+
|
|
10
|
+
const PagePreview: React.FC = () => {
|
|
11
|
+
const { sizes } = useTheme();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<ScrollView
|
|
15
|
+
style={{ flex: 1, backgroundColor: '#070C1F' }}
|
|
16
|
+
contentContainerStyle={{ padding: sizes.lg }}
|
|
17
|
+
>
|
|
18
|
+
<View>
|
|
19
|
+
<HeroPrankCard
|
|
20
|
+
imageUri="https://images.unsplash.com/photo-1621447462558-42b4d8bc5d9b?auto=format&fit=crop&w=900&q=80"
|
|
21
|
+
title="Homeless Prank"
|
|
22
|
+
description="Prank your loved ones with a unknown guest in your home!"
|
|
23
|
+
/>
|
|
24
|
+
<Spacer size="lg" />
|
|
25
|
+
<UploadPromptCard onPress={() => console.log('Upload pressed')} />
|
|
26
|
+
</View>
|
|
27
|
+
<Spacer size="xl" />
|
|
28
|
+
</ScrollView>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const meta: Meta<typeof PagePreview> = {
|
|
33
|
+
title: 'Features/Prank/HomelessPrankPage',
|
|
34
|
+
component: PagePreview,
|
|
35
|
+
decorators: [
|
|
36
|
+
(Story) => (
|
|
37
|
+
<ThemeProvider>
|
|
38
|
+
<View style={{ flex: 1 }}>
|
|
39
|
+
<Story />
|
|
40
|
+
</View>
|
|
41
|
+
</ThemeProvider>
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
parameters: {
|
|
45
|
+
layout: 'fullscreen',
|
|
46
|
+
backgrounds: {
|
|
47
|
+
default: 'dark',
|
|
48
|
+
values: [
|
|
49
|
+
{ name: 'dark', value: '#070C1F' },
|
|
50
|
+
{ name: 'light', value: '#f5f5f5' },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export default meta;
|
|
57
|
+
|
|
58
|
+
export const Default: StoryFn<typeof PagePreview> = () => <PagePreview />;
|
|
@@ -0,0 +1,55 @@
|
|
|
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 UploadPromptCard from './UploadPromptCard';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof UploadPromptCard> = {
|
|
9
|
+
title: 'Features/Prank/UploadPromptCard',
|
|
10
|
+
component: UploadPromptCard,
|
|
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
|
+
message: 'Tap here to upload the photo you want to bring to life!'
|
|
22
|
+
},
|
|
23
|
+
parameters: {
|
|
24
|
+
backgrounds: {
|
|
25
|
+
default: 'dark',
|
|
26
|
+
values: [
|
|
27
|
+
{ name: 'dark', value: '#070C1F' },
|
|
28
|
+
{ name: 'light', value: '#f5f5f5' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default meta;
|
|
35
|
+
|
|
36
|
+
type Story = StoryObj<typeof UploadPromptCard>;
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
onPress: () => console.log('Upload pressed'),
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const Uploading: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
isUploading: true,
|
|
47
|
+
progress: 0.45,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Disabled: Story = {
|
|
52
|
+
args: {
|
|
53
|
+
disabled: true,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
StyleProp,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
View,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
12
|
+
|
|
13
|
+
import Spacer from '../UI/Spacer';
|
|
14
|
+
import { ThemeType, SizesType, useTheme } from '../../theme';
|
|
15
|
+
|
|
16
|
+
export interface UploadPromptCardProps {
|
|
17
|
+
message?: string;
|
|
18
|
+
onPress?: () => void;
|
|
19
|
+
isUploading?: boolean;
|
|
20
|
+
progress?: number;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
style?: StyleProp<ViewStyle>;
|
|
23
|
+
testID?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const UploadPromptCard: React.FC<UploadPromptCardProps> = ({
|
|
27
|
+
message = 'Tap here to upload the photo you want to bring to life!',
|
|
28
|
+
onPress,
|
|
29
|
+
isUploading = false,
|
|
30
|
+
progress,
|
|
31
|
+
disabled = false,
|
|
32
|
+
style,
|
|
33
|
+
testID,
|
|
34
|
+
}) => {
|
|
35
|
+
const { theme, sizes } = useTheme();
|
|
36
|
+
const styles = React.useMemo(() => getStyles({ theme, sizes }), [theme, sizes]);
|
|
37
|
+
|
|
38
|
+
const isDisabled = disabled || isUploading;
|
|
39
|
+
|
|
40
|
+
const renderUploading = () => {
|
|
41
|
+
const normalizedProgress =
|
|
42
|
+
typeof progress === 'number'
|
|
43
|
+
? Math.min(1, Math.max(0, progress))
|
|
44
|
+
: undefined;
|
|
45
|
+
const percentage =
|
|
46
|
+
normalizedProgress !== undefined
|
|
47
|
+
? Math.round(normalizedProgress * 100)
|
|
48
|
+
: undefined;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<ActivityIndicator color={theme.white} size="large" />
|
|
53
|
+
<Spacer size="sm" />
|
|
54
|
+
<Text style={styles.uploadingLabel}>
|
|
55
|
+
{percentage !== undefined ? `Uploading ${percentage}%` : 'Uploading...'}
|
|
56
|
+
</Text>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const renderIdle = () => (
|
|
62
|
+
<>
|
|
63
|
+
<View style={styles.iconContainer}>
|
|
64
|
+
<Ionicons name="image-outline" size={36} color={theme.white} />
|
|
65
|
+
</View>
|
|
66
|
+
<Spacer size="sm" />
|
|
67
|
+
<Text style={styles.message} numberOfLines={3}>
|
|
68
|
+
{message}
|
|
69
|
+
</Text>
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<TouchableOpacity
|
|
75
|
+
activeOpacity={0.85}
|
|
76
|
+
onPress={onPress}
|
|
77
|
+
disabled={isDisabled || !onPress}
|
|
78
|
+
style={[styles.container, isDisabled && styles.disabled, style]}
|
|
79
|
+
testID={testID}
|
|
80
|
+
>
|
|
81
|
+
{isUploading ? renderUploading() : renderIdle()}
|
|
82
|
+
</TouchableOpacity>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
interface StyleParams {
|
|
87
|
+
theme: ThemeType;
|
|
88
|
+
sizes: SizesType;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getStyles = ({ theme, sizes }: StyleParams) =>
|
|
92
|
+
StyleSheet.create({
|
|
93
|
+
container: {
|
|
94
|
+
borderRadius: sizes.radius_lg,
|
|
95
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
96
|
+
borderWidth: 1,
|
|
97
|
+
borderColor: 'rgba(255,255,255,0.12)',
|
|
98
|
+
paddingHorizontal: sizes.lg,
|
|
99
|
+
paddingVertical: sizes.xl,
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
minHeight: 180,
|
|
103
|
+
},
|
|
104
|
+
disabled: {
|
|
105
|
+
opacity: 0.6,
|
|
106
|
+
},
|
|
107
|
+
iconContainer: {
|
|
108
|
+
width: 64,
|
|
109
|
+
height: 64,
|
|
110
|
+
borderRadius: 20,
|
|
111
|
+
backgroundColor: 'rgba(255,255,255,0.08)',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
justifyContent: 'center',
|
|
114
|
+
},
|
|
115
|
+
message: {
|
|
116
|
+
fontSize: sizes.font,
|
|
117
|
+
lineHeight: 22,
|
|
118
|
+
textAlign: 'center',
|
|
119
|
+
color: 'rgba(255,255,255,0.85)',
|
|
120
|
+
fontWeight: '500',
|
|
121
|
+
},
|
|
122
|
+
uploadingLabel: {
|
|
123
|
+
fontSize: sizes.font,
|
|
124
|
+
lineHeight: 22,
|
|
125
|
+
color: 'rgba(255,255,255,0.85)',
|
|
126
|
+
fontWeight: '500',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export default UploadPromptCard;
|
|
@@ -5,7 +5,7 @@ import { View, Button } from 'react-native';
|
|
|
5
5
|
import { ModalProfilePhoto } from './ModalProfilePhoto'; // <-- проверь путь!
|
|
6
6
|
|
|
7
7
|
const meta = {
|
|
8
|
-
title: 'Profile/ModalProfilePhoto',
|
|
8
|
+
title: 'Features/Profile/ModalProfilePhoto',
|
|
9
9
|
component: ModalProfilePhoto,
|
|
10
10
|
argTypes: {
|
|
11
11
|
handleClosePreview: { action: 'handleClosePreview' },
|
|
@@ -4,7 +4,7 @@ import { View, ScrollView } from 'react-native';
|
|
|
4
4
|
import ProfilePhotoBanner from './ProfilePhotoBanner';
|
|
5
5
|
|
|
6
6
|
const meta: Meta<React.ComponentProps<typeof ProfilePhotoBanner>> = {
|
|
7
|
-
title: 'Profile/ProfilePhotoBanner',
|
|
7
|
+
title: 'Features/Profile/ProfilePhotoBanner',
|
|
8
8
|
component: ProfilePhotoBanner,
|
|
9
9
|
decorators: [
|
|
10
10
|
(Story) => (
|
|
@@ -34,11 +34,18 @@ const closeHandler = () => {
|
|
|
34
34
|
|
|
35
35
|
export const Default = Template.bind({});
|
|
36
36
|
Default.args = {
|
|
37
|
+
message: 'Upload a photo — it will help others recognize and trust you.',
|
|
38
|
+
buttonText: 'Add Photo',
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
export const InsideScrollableList: StoryFn = () => (
|
|
40
42
|
<ScrollView contentContainerStyle={{ gap: 12, padding: 16 }}>
|
|
41
|
-
<ProfilePhotoBanner
|
|
43
|
+
<ProfilePhotoBanner
|
|
44
|
+
onAddPhoto={addPhotoHandler}
|
|
45
|
+
onClose={closeHandler}
|
|
46
|
+
message="Upload a photo — it will help others recognize and trust you."
|
|
47
|
+
buttonText="Add Photo"
|
|
48
|
+
/>
|
|
42
49
|
<View style={{ height: 150, backgroundColor: '#f0f0f0', borderRadius: 12 }} />
|
|
43
50
|
<View style={{ height: 150, backgroundColor: '#f5f5f5', borderRadius: 12 }} />
|
|
44
51
|
<View style={{ height: 150, backgroundColor: '#fafafa', borderRadius: 12 }} />
|
|
@@ -61,6 +68,8 @@ export const DismissibleBehaviour: StoryFn = () => {
|
|
|
61
68
|
closeHandler();
|
|
62
69
|
setVisible(false);
|
|
63
70
|
}}
|
|
71
|
+
message="Upload a photo — it will help others recognize and trust you."
|
|
72
|
+
buttonText="Add Photo"
|
|
64
73
|
/>
|
|
65
74
|
)}
|
|
66
75
|
<View style={{ height: 160, backgroundColor: '#d9ecff', borderRadius: 16 }} />
|
|
@@ -7,21 +7,23 @@ import Button from '../Button/Button';
|
|
|
7
7
|
interface Props {
|
|
8
8
|
onAddPhoto: () => void;
|
|
9
9
|
onClose: () => void;
|
|
10
|
+
message: string;
|
|
11
|
+
buttonText: string;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const ProfilePhotoBanner: React.FC<Props> = ({ onAddPhoto, onClose }) => {
|
|
14
|
+
const ProfilePhotoBanner: React.FC<Props> = ({ onAddPhoto, onClose, message, buttonText }) => {
|
|
13
15
|
const { theme, typography } = useTheme();
|
|
14
16
|
const styles = getStyles({ theme });
|
|
15
17
|
|
|
16
18
|
return (
|
|
17
19
|
<View style={styles.container}>
|
|
18
20
|
<View style={styles.row}>
|
|
19
|
-
<Text style={[typography.body, styles.text]}>
|
|
21
|
+
<Text style={[typography.body, styles.text]}>{message}</Text>
|
|
20
22
|
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
|
|
21
23
|
<Ionicons name="close" size={20} color={theme.text} />
|
|
22
24
|
</TouchableOpacity>
|
|
23
25
|
</View>
|
|
24
|
-
<Button title=
|
|
26
|
+
<Button title={buttonText} onPress={onAddPhoto} />
|
|
25
27
|
</View>
|
|
26
28
|
);
|
|
27
29
|
};
|
|
@@ -5,7 +5,7 @@ import { View, Button } from 'react-native';
|
|
|
5
5
|
import PureProfilePhotoUpload from './ProfilePhotoUpload';
|
|
6
6
|
|
|
7
7
|
const meta = {
|
|
8
|
-
title: 'Profile/PureProfilePhotoUpload',
|
|
8
|
+
title: 'Features/Profile/PureProfilePhotoUpload',
|
|
9
9
|
component: PureProfilePhotoUpload,
|
|
10
10
|
argTypes: {
|
|
11
11
|
onPressSelect: { action: 'onPressSelect' },
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Meta, StoryFn } from '@storybook/react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import ProfileSummary, { ProfileSummaryProps } from './ProfileSummary';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<ProfileSummaryProps> = {
|
|
7
|
+
title: 'Features/Profile/ProfileSummary',
|
|
8
|
+
component: ProfileSummary,
|
|
9
|
+
decorators: [
|
|
10
|
+
(Story) => (
|
|
11
|
+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', padding: 16 }}>
|
|
12
|
+
<Story />
|
|
13
|
+
</View>
|
|
14
|
+
),
|
|
15
|
+
],
|
|
16
|
+
argTypes: {
|
|
17
|
+
onAvatarPress: { action: 'avatar press' },
|
|
18
|
+
onAddPress: { action: 'add press' },
|
|
19
|
+
onEditPress: { action: 'edit press' },
|
|
20
|
+
onWebsitePress: { action: 'website press' },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
|
|
26
|
+
const Template: StoryFn<ProfileSummaryProps> = (args) => <ProfileSummary {...args} />;
|
|
27
|
+
|
|
28
|
+
export const Default = Template.bind({});
|
|
29
|
+
Default.args = {
|
|
30
|
+
avatarUri:
|
|
31
|
+
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=200&q=80',
|
|
32
|
+
name: 'AiPair.pro',
|
|
33
|
+
username: '@ai_pair',
|
|
34
|
+
stats: [
|
|
35
|
+
{ label: 'Following', value: '2' },
|
|
36
|
+
{ label: 'Follower', value: '1' },
|
|
37
|
+
{ label: 'Likes', value: '165' },
|
|
38
|
+
],
|
|
39
|
+
website: 'https://aipair.pro',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const WithoutAddButton = Template.bind({});
|
|
43
|
+
WithoutAddButton.args = {
|
|
44
|
+
...Default.args,
|
|
45
|
+
showAddButton: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const WithLongName = Template.bind({});
|
|
49
|
+
WithLongName.args = {
|
|
50
|
+
...Default.args,
|
|
51
|
+
name: 'AiPair.pro — Personal AI assistant hub',
|
|
52
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
4
|
+
import Button from '../../Button/Button';
|
|
5
|
+
import Spacer from '../../UI/Spacer';
|
|
6
|
+
import TextWithLinks from '../../UI/TextWithLinks';
|
|
7
|
+
import { useTheme } from '../../../theme';
|
|
8
|
+
|
|
9
|
+
type ProfileStat = {
|
|
10
|
+
/** Title displayed under the numeric value. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Numeric or string value displayed above the label. */
|
|
13
|
+
value: string | number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface ProfileSummaryProps {
|
|
17
|
+
/** Remote uri for the avatar. */
|
|
18
|
+
avatarUri: string;
|
|
19
|
+
/** Main profile name. */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Secondary username, e.g. @handle. */
|
|
22
|
+
username: string;
|
|
23
|
+
/** Collection of statistics shown under the username. */
|
|
24
|
+
stats: ProfileStat[];
|
|
25
|
+
/** Optional website text shown as a link under the stats. */
|
|
26
|
+
website?: string;
|
|
27
|
+
/** Custom handler for link clicks. */
|
|
28
|
+
onWebsitePress?: (url: string) => void;
|
|
29
|
+
/** Called when avatar is pressed. */
|
|
30
|
+
onAvatarPress?: () => void;
|
|
31
|
+
/** Called when floating add button on avatar pressed. */
|
|
32
|
+
onAddPress?: () => void;
|
|
33
|
+
/** Called when edit button pressed. */
|
|
34
|
+
onEditPress?: () => void;
|
|
35
|
+
/** Controls visibility of floating add button. */
|
|
36
|
+
showAddButton?: boolean;
|
|
37
|
+
/** Custom label for edit button. */
|
|
38
|
+
editLabel?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ProfileSummary: React.FC<ProfileSummaryProps> = ({
|
|
42
|
+
avatarUri,
|
|
43
|
+
name,
|
|
44
|
+
username,
|
|
45
|
+
stats,
|
|
46
|
+
website,
|
|
47
|
+
onWebsitePress,
|
|
48
|
+
onAvatarPress,
|
|
49
|
+
onAddPress,
|
|
50
|
+
onEditPress,
|
|
51
|
+
showAddButton = true,
|
|
52
|
+
editLabel = 'Edit',
|
|
53
|
+
}) => {
|
|
54
|
+
const { theme, typography, sizes } = useTheme();
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View style={[styles.container, { paddingHorizontal: sizes.lg }]}>
|
|
58
|
+
<View style={styles.avatarWrapper}>
|
|
59
|
+
<TouchableOpacity onPress={onAvatarPress} activeOpacity={0.8}>
|
|
60
|
+
<Image source={{ uri: avatarUri }} style={styles.avatar} />
|
|
61
|
+
</TouchableOpacity>
|
|
62
|
+
|
|
63
|
+
{showAddButton && (
|
|
64
|
+
<TouchableOpacity
|
|
65
|
+
onPress={onAddPress}
|
|
66
|
+
activeOpacity={0.8}
|
|
67
|
+
style={[styles.addButton, { backgroundColor: theme.primary }]}
|
|
68
|
+
>
|
|
69
|
+
<Ionicons name="add" size={20} color={theme.white} />
|
|
70
|
+
</TouchableOpacity>
|
|
71
|
+
)}
|
|
72
|
+
</View>
|
|
73
|
+
|
|
74
|
+
<Spacer size="sm" />
|
|
75
|
+
|
|
76
|
+
<View style={styles.nameRow}>
|
|
77
|
+
<Text style={[typography.titleH4, styles.centerText]}>{name}</Text>
|
|
78
|
+
<Button
|
|
79
|
+
title={editLabel}
|
|
80
|
+
type="gray-outline"
|
|
81
|
+
onPress={onEditPress}
|
|
82
|
+
style={[styles.editButton, { marginLeft: sizes.sm }]}
|
|
83
|
+
textStyle={styles.editButtonText}
|
|
84
|
+
disabled={!onEditPress}
|
|
85
|
+
/>
|
|
86
|
+
</View>
|
|
87
|
+
|
|
88
|
+
<Text style={[typography.body, styles.username, { color: theme.description }]}>{username}</Text>
|
|
89
|
+
|
|
90
|
+
<Spacer size="md" />
|
|
91
|
+
|
|
92
|
+
<View style={styles.statsRow}>
|
|
93
|
+
{stats.map((stat) => (
|
|
94
|
+
<View key={stat.label} style={styles.statItem}>
|
|
95
|
+
<Text style={[typography.titleH6, styles.statValue]}>{stat.value}</Text>
|
|
96
|
+
<Text style={[typography.bodySm, styles.statLabel, { color: theme.description }]}>{stat.label}</Text>
|
|
97
|
+
</View>
|
|
98
|
+
))}
|
|
99
|
+
</View>
|
|
100
|
+
|
|
101
|
+
{website ? (
|
|
102
|
+
<>
|
|
103
|
+
<Spacer size="sm" />
|
|
104
|
+
<TextWithLinks
|
|
105
|
+
text={website}
|
|
106
|
+
style={[typography.body, styles.centerText]}
|
|
107
|
+
linkTextStyle={[styles.websiteLink, { color: theme.primary }]}
|
|
108
|
+
onLinkPress={onWebsitePress}
|
|
109
|
+
/>
|
|
110
|
+
</>
|
|
111
|
+
) : null}
|
|
112
|
+
</View>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const styles = StyleSheet.create({
|
|
117
|
+
container: {
|
|
118
|
+
alignItems: 'center',
|
|
119
|
+
paddingVertical: 24,
|
|
120
|
+
},
|
|
121
|
+
avatarWrapper: {
|
|
122
|
+
position: 'relative',
|
|
123
|
+
},
|
|
124
|
+
avatar: {
|
|
125
|
+
width: 112,
|
|
126
|
+
height: 112,
|
|
127
|
+
borderRadius: 56,
|
|
128
|
+
backgroundColor: '#2B2B2B',
|
|
129
|
+
},
|
|
130
|
+
addButton: {
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
bottom: 4,
|
|
133
|
+
right: -4,
|
|
134
|
+
width: 36,
|
|
135
|
+
height: 36,
|
|
136
|
+
borderRadius: 18,
|
|
137
|
+
alignItems: 'center',
|
|
138
|
+
justifyContent: 'center',
|
|
139
|
+
shadowColor: '#000',
|
|
140
|
+
shadowOpacity: 0.15,
|
|
141
|
+
shadowRadius: 4,
|
|
142
|
+
shadowOffset: { width: 0, height: 2 },
|
|
143
|
+
elevation: 4,
|
|
144
|
+
},
|
|
145
|
+
nameRow: {
|
|
146
|
+
flexDirection: 'row',
|
|
147
|
+
alignItems: 'center',
|
|
148
|
+
justifyContent: 'center',
|
|
149
|
+
},
|
|
150
|
+
centerText: {
|
|
151
|
+
textAlign: 'center',
|
|
152
|
+
},
|
|
153
|
+
editButton: {
|
|
154
|
+
minHeight: 32,
|
|
155
|
+
paddingHorizontal: 14,
|
|
156
|
+
borderRadius: 16,
|
|
157
|
+
width: 'auto',
|
|
158
|
+
alignSelf: 'center',
|
|
159
|
+
},
|
|
160
|
+
editButtonText: {
|
|
161
|
+
fontSize: 14,
|
|
162
|
+
},
|
|
163
|
+
username: {
|
|
164
|
+
marginTop: 4,
|
|
165
|
+
},
|
|
166
|
+
statsRow: {
|
|
167
|
+
flexDirection: 'row',
|
|
168
|
+
alignItems: 'center',
|
|
169
|
+
justifyContent: 'space-between',
|
|
170
|
+
width: '100%',
|
|
171
|
+
maxWidth: 280,
|
|
172
|
+
},
|
|
173
|
+
statItem: {
|
|
174
|
+
alignItems: 'center',
|
|
175
|
+
flex: 1,
|
|
176
|
+
},
|
|
177
|
+
statValue: {
|
|
178
|
+
fontWeight: '600',
|
|
179
|
+
},
|
|
180
|
+
statLabel: {
|
|
181
|
+
marginTop: 2,
|
|
182
|
+
},
|
|
183
|
+
websiteLink: {
|
|
184
|
+
fontWeight: '500',
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
export default ProfileSummary;
|
|
@@ -5,7 +5,7 @@ import { View } from 'react-native';
|
|
|
5
5
|
import UserProfileTabs, { type UserProfileTab } from './UserProfileTabs';
|
|
6
6
|
|
|
7
7
|
const meta = {
|
|
8
|
-
title: 'Profile/UserProfileTabs',
|
|
8
|
+
title: 'Features/Profile/UserProfileTabs',
|
|
9
9
|
component: UserProfileTabs,
|
|
10
10
|
decorators: [(Story) => <View style={{ padding: 12 }}><Story /></View>],
|
|
11
11
|
argTypes: {
|
|
@@ -6,3 +6,5 @@ export { default as ProfilePhotoUpload } from './ProfilePhotoUpload/ProfilePhoto
|
|
|
6
6
|
export type { ProfilePhotoUploadProps } from './ProfilePhotoUpload/ProfilePhotoUpload';
|
|
7
7
|
export { default as UserProfileTabs } from './ProfileTabs/UserProfileTabs';
|
|
8
8
|
export type { UserProfileTab } from './ProfileTabs/UserProfileTabs';
|
|
9
|
+
export { default as ProfileSummary } from './ProfileSummary/ProfileSummary';
|
|
10
|
+
export type { ProfileSummaryProps } from './ProfileSummary/ProfileSummary';
|