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.
Files changed (113) hide show
  1. package/components/Button/Button.stories.tsx +1 -1
  2. package/components/Button/DeleteAccountButton.stories.tsx +13 -1
  3. package/components/Button/DeleteAccountButton.tsx +28 -8
  4. package/components/Button/PostButton.stories.tsx +1 -1
  5. package/components/Button/TelegramFeedbackLink.stories.tsx +7 -1
  6. package/components/Button/TelegramFeedbackLink.tsx +12 -4
  7. package/components/Cards/BusinessIdeaCard.stories.tsx +73 -0
  8. package/components/Cards/BusinessIdeaCard.tsx +251 -0
  9. package/components/Cards/EventCard.stories.tsx +4 -1
  10. package/components/Cards/EventCard.tsx +9 -2
  11. package/components/Cards/PlaceCard.stories.tsx +1 -1
  12. package/components/Cards/index.tsx +1 -0
  13. package/components/Chat/ChatItem.stories.tsx +1 -1
  14. package/components/Chat/ChatTab/ChatTabs.stories.tsx +1 -1
  15. package/components/Chat/ChatTab/SubTabButton.stories.tsx +1 -1
  16. package/components/Chat/InputMessage.stories.tsx +12 -1
  17. package/components/Chat/InputMessage.tsx +5 -3
  18. package/components/Chat/MessageItem/MessageItem.stories.tsx +1 -1
  19. package/components/Chat/PinnedMessagesBar/PinnedMessagesBar.stories.tsx +1 -1
  20. package/components/Gallery/AiAgentGallery.stories.tsx +1 -1
  21. package/components/Gallery/ProfileMediaGallery.stories.tsx +66 -0
  22. package/components/Gallery/ProfileMediaGallery.tsx +239 -0
  23. package/components/Gallery/ProfileMediaNavigationItem.tsx +73 -0
  24. package/components/Gallery/ProfileSelfiesGallery.pure.tsx +97 -0
  25. package/components/Gallery/ProfileSelfiesGallery.stories.tsx +86 -0
  26. package/components/Gallery/index.ts +4 -1
  27. package/components/Header/HeaderDefault.stories.tsx +1 -1
  28. package/components/Header/HeaderEdit.stories.tsx +1 -1
  29. package/components/Header/HeaderHome.stories.tsx +1 -1
  30. package/components/Header/HeaderSwitcher.stories.tsx +1 -1
  31. package/components/Header/HeaderWithImg.stories.tsx +10 -1
  32. package/components/Header/HeaderWithImg.tsx +27 -7
  33. package/components/Modals/GalleryModal.stories.tsx +1 -1
  34. package/components/Modals/GuestAiChatModal.stories.tsx +12 -4
  35. package/components/Modals/GuestAiChatModal.tsx +9 -3
  36. package/components/Modals/ReportModal.stories.tsx +35 -2
  37. package/components/Modals/ReportModal.tsx +23 -24
  38. package/components/Poll/CommentItem.stories.tsx +1 -1
  39. package/components/Poll/PollCardList.stories.tsx +5 -1
  40. package/components/Poll/PollCardList.tsx +4 -2
  41. package/components/Posts/EventCardList.stories.tsx +1 -1
  42. package/components/Posts/PlaceCardList.stories.tsx +1 -1
  43. package/components/Prank/HeroPrankCard.stories.tsx +47 -0
  44. package/components/Prank/HeroPrankCard.tsx +114 -0
  45. package/components/Prank/HomelessPrankPage.stories.tsx +58 -0
  46. package/components/Prank/UploadPromptCard.stories.tsx +55 -0
  47. package/components/Prank/UploadPromptCard.tsx +130 -0
  48. package/components/Prank/index.ts +5 -0
  49. package/components/Profile/ModalProfilePhoto.stories.tsx +1 -1
  50. package/components/Profile/ProfileCard/ProfileCard.stories.tsx +1 -1
  51. package/components/Profile/ProfilePhotoBanner.stories.tsx +11 -2
  52. package/components/Profile/ProfilePhotoBanner.tsx +5 -3
  53. package/components/Profile/ProfilePhotoUpload/ProfilePhotoUpload.stories.tsx +1 -1
  54. package/components/Profile/ProfileSummary/ProfileSummary.stories.tsx +52 -0
  55. package/components/Profile/ProfileSummary/ProfileSummary.tsx +188 -0
  56. package/components/Profile/ProfileTabs/UserProfileTabs.stories.tsx +1 -1
  57. package/components/Profile/index.ts +2 -0
  58. package/components/Screens/AppScreens.stories.tsx +572 -0
  59. package/components/Screens/HomeScreen.stories.tsx +159 -0
  60. package/components/Screens/LibraryScreen.stories.tsx +97 -0
  61. package/components/Settings/AppSettingsScreen.stories.tsx +134 -0
  62. package/components/Settings/AppSettingsScreen.tsx +215 -0
  63. package/components/Settings/SettingsHeader.tsx +37 -0
  64. package/components/Settings/SettingsListItem.tsx +142 -0
  65. package/components/Settings/SettingsManageAccountButton.tsx +58 -0
  66. package/components/Settings/SettingsOptionList.tsx +82 -0
  67. package/components/Settings/SettingsProfileCard.tsx +58 -0
  68. package/components/Settings/SettingsScreen.stories.tsx +66 -0
  69. package/components/Settings/SettingsScreen.tsx +129 -0
  70. package/components/Settings/SettingsSection.tsx +56 -0
  71. package/components/Settings/SettingsToggleItem.tsx +70 -0
  72. package/components/Settings/index.ts +23 -0
  73. package/components/Specialist/Hero.stories.tsx +2 -1
  74. package/components/Specialist/Hero.tsx +3 -1
  75. package/components/Specialist/PortfolioCarousel.stories.tsx +1 -1
  76. package/components/Specialist/ServicesList.stories.tsx +2 -1
  77. package/components/Specialist/ServicesList.tsx +4 -3
  78. package/components/Tooltip/DangerTooltip.stories.tsx +1 -1
  79. package/components/Tooltip/InfoTooltip.stories.tsx +1 -1
  80. package/components/Tooltip/InfoTooltipBase.stories.tsx +1 -1
  81. package/components/Tooltip/SucceedTooltip.stories.tsx +1 -1
  82. package/components/Tooltip/WarningTooltip.stories.tsx +1 -1
  83. package/components/UI/DeletedState.stories.tsx +6 -1
  84. package/components/UI/DeletedState.tsx +12 -3
  85. package/components/UI/DescriptionMore.stories.tsx +2 -0
  86. package/components/UI/DescriptionMore.tsx +13 -3
  87. package/components/UI/EmptyState.stories.tsx +4 -1
  88. package/components/UI/EmptyState.tsx +3 -2
  89. package/components/UI/HorizontalCardSection.stories.tsx +85 -0
  90. package/components/UI/HorizontalCardSection.tsx +199 -0
  91. package/components/UI/NoAuth.stories.tsx +3 -0
  92. package/components/UI/NoAuth.tsx +7 -6
  93. package/components/UI/ParticipantItem.stories.tsx +21 -0
  94. package/components/UI/ParticipantItem.tsx +30 -6
  95. package/components/UI/TabBar/BottomTabBar.stories.tsx +87 -0
  96. package/components/UI/TabBar/BottomTabBar.tsx +128 -0
  97. package/components/UI/ThemeSwitcher.stories.tsx +5 -1
  98. package/components/UI/ThemeSwitcher.tsx +7 -2
  99. package/components/UI/UpdateRequiredView.stories.tsx +2 -0
  100. package/components/UI/UpdateRequiredView.tsx +3 -2
  101. package/components/UI/index.ts +4 -0
  102. package/components/UserCards/AiAgentHeroCard.stories.tsx +66 -0
  103. package/components/UserCards/AiAgentHeroCard.tsx +242 -0
  104. package/components/UserCards/Organazer.tsx +3 -2
  105. package/components/UserCards/Organizer.stories.tsx +5 -1
  106. package/components/UserCards/SpecialistCard.stories.tsx +3 -1
  107. package/components/UserCards/SpecialistCard.tsx +3 -2
  108. package/components/UserCards/StoryCard.stories.tsx +1 -1
  109. package/components/UserCards/UserProfileCard.stories.tsx +1 -1
  110. package/components/UserCards/UserRow.stories.tsx +1 -1
  111. package/components/UserCards/index.ts +2 -0
  112. package/components/index.ts +2 -0
  113. 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;
@@ -0,0 +1,5 @@
1
+ export { default as HeroPrankCard } from './HeroPrankCard';
2
+ export type { HeroPrankCardProps } from './HeroPrankCard';
3
+
4
+ export { default as UploadPromptCard } from './UploadPromptCard';
5
+ export type { UploadPromptCardProps } from './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' },
@@ -5,7 +5,7 @@ import { View } from 'react-native';
5
5
  import PureProfileCard from "./ProfileCard";
6
6
 
7
7
  const meta = {
8
- title: 'Profile/ProfileCard',
8
+ title: 'Features/Profile/ProfileCard',
9
9
  component: PureProfileCard,
10
10
  argTypes: {
11
11
  onBack: { action: 'onBack' },
@@ -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 onAddPhoto={addPhotoHandler} onClose={closeHandler} />
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]}>Upload a photo — it will help others recognize and trust you.</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="Add Photo" onPress={onAddPhoto} />
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';