rn-vs-lb 1.0.68 → 1.0.70

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.
@@ -28,6 +28,8 @@ const meta: Meta<ChatItemProps> = {
28
28
  export default meta;
29
29
 
30
30
  const Template: StoryFn<ChatItemProps> = (args) => <ChatItem {...args} />;
31
+ const getItemLabel = (item: ChatItemProps, idx: number) =>
32
+ item.variant === 'person' ? item.chatName ?? item.senderFullName : item.chatName ?? `chat-${idx}`;
31
33
 
32
34
  export const PersonOnline = Template.bind({});
33
35
  PersonOnline.args = {
@@ -38,7 +40,7 @@ PersonOnline.args = {
38
40
  isUserOnline: true,
39
41
  lastMessage: 'See you at 6pm near the station',
40
42
  createdAt: '10:42',
41
- unread: '+',
43
+ unread: 'new',
42
44
  };
43
45
 
44
46
  export const PersonOffline = Template.bind({});
@@ -61,7 +63,7 @@ GroupWithSender.args = {
61
63
  'https://images.unsplash.com/photo-1519340241574-2cec6aef0c01?auto=format&fit=crop&w=200&q=60',
62
64
  lastMessage: 'Slides are uploaded to Drive, check the link above.',
63
65
  createdAt: 'Yesterday',
64
- unread: '+',
66
+ unread: 'new',
65
67
  };
66
68
 
67
69
  export const Bot = Template.bind({});
@@ -94,7 +96,7 @@ export const ListOfItems: StoryFn = () => {
94
96
  isUserOnline: true,
95
97
  lastMessage: 'See you soon!',
96
98
  createdAt: '10:42',
97
- unread: '+',
99
+ unread: 'new',
98
100
  },
99
101
  {
100
102
  variant: 'person',
@@ -113,7 +115,7 @@ export const ListOfItems: StoryFn = () => {
113
115
  'https://images.unsplash.com/photo-1519340241574-2cec6aef0c01?auto=format&fit=crop&w=200&q=60',
114
116
  lastMessage: 'Standup in 5 minutes.',
115
117
  createdAt: '08:55',
116
- unread: '+',
118
+ unread: 'new',
117
119
  },
118
120
  {
119
121
  variant: 'group',
@@ -166,7 +168,7 @@ export const ListOfItems: StoryFn = () => {
166
168
  <ChatItem
167
169
  key={idx}
168
170
  {...item}
169
- onPress={createPressHandler(item.chatName ?? item.senderFullName ?? `chat-${idx}`)}
171
+ onPress={createPressHandler(getItemLabel(item, idx))}
170
172
  />
171
173
  ))}
172
174
  </ScrollView>
@@ -3,65 +3,98 @@ import { ThemeType } from '../../theme';
3
3
 
4
4
  export const getStyles = (theme: ThemeType) => StyleSheet.create({
5
5
  userContainer: {
6
- flexDirection: 'row',
7
- alignItems: 'center',
8
- paddingVertical: 12,
9
- paddingHorizontal: 8,
6
+ paddingVertical: 8,
7
+ paddingHorizontal: 12,
8
+ borderRadius: 16,
9
+ backgroundColor: theme.white,
10
10
  },
11
11
  profileSection: {
12
12
  flexDirection: 'row',
13
- alignItems: 'center',
13
+ alignItems: 'flex-start',
14
14
  flex: 1,
15
15
  },
16
16
  avatar: {
17
- width: 60,
18
- height: 60,
19
- borderRadius: 40,
17
+ width: 56,
18
+ height: 56,
19
+ borderRadius: 28,
20
20
  },
21
21
  userInfo: {
22
22
  flex: 1,
23
23
  marginLeft: 12,
24
+ minHeight: 56,
25
+ justifyContent: 'center',
26
+ },
27
+ headerRow: {
28
+ flexDirection: 'row',
29
+ alignItems: 'center',
30
+ justifyContent: 'space-between',
31
+ gap: 8,
24
32
  },
25
- senderContainer: {
33
+ headerRight: {
26
34
  flexDirection: 'row',
27
- alignItems: "center",
28
- // justifyContent: "space-between",
29
- },
30
- unread: {
31
- // top:10,
32
- marginLeft: 8,
33
- width:18,
34
- height: 18,
35
- textAlign:"center",
35
+ alignItems: 'center',
36
+ gap: 8,
37
+ },
38
+ chatTitle: {
39
+ flex: 1,
40
+ },
41
+ unreadBadge: {
42
+ minHeight: 20,
43
+ paddingHorizontal: 8,
44
+ borderRadius: 999,
36
45
  backgroundColor: theme.success,
46
+ flexDirection: 'row',
47
+ alignItems: 'center',
48
+ gap: 5,
49
+ },
50
+ unreadDot: {
51
+ width: 6,
52
+ height: 6,
53
+ borderRadius: 3,
54
+ backgroundColor: theme.white,
55
+ },
56
+ unreadText: {
37
57
  color: theme.white,
38
- borderRadius: 20,
39
- fontSize: 12,
40
- lineHeight: 16,
58
+ fontSize: 10,
59
+ lineHeight: 12,
60
+ fontWeight: '700',
61
+ letterSpacing: 0.3,
62
+ textTransform: 'uppercase',
63
+ },
64
+ messageRow: {
65
+ marginTop: 4,
41
66
  },
42
67
  senderName: {
43
68
  fontSize: 14,
44
- marginRight: 4,
69
+ color: theme.placeholder,
70
+ },
71
+ senderNameUnread: {
45
72
  color: theme.text,
73
+ fontWeight: '500',
46
74
  },
47
75
  senderMessage: {
48
76
  fontSize: 13,
49
77
  fontStyle: 'italic',
78
+ color: theme.placeholder,
50
79
  },
51
80
  timeAgo: {
52
- fontSize: 13,
81
+ fontSize: 12,
53
82
  color: theme.placeholder,
83
+ fontWeight: '500',
84
+ },
85
+ timeAgoUnread: {
86
+ color: theme.text,
54
87
  },
55
88
 
56
89
  status: {
57
- position: "absolute",
58
- width:12,
90
+ position: 'absolute',
91
+ width: 12,
59
92
  height: 12,
60
93
  borderWidth: 2,
61
94
  borderColor: theme.white,
62
95
  borderRadius: 10,
63
96
  bottom: 0,
64
- right:1,
97
+ right: 1,
65
98
  },
66
99
  online: {
67
100
  backgroundColor: theme.success,
@@ -6,7 +6,7 @@ import { useTheme } from '../../theme';
6
6
  import { getStyles } from './ChatItem.styles';
7
7
 
8
8
  type BaseProps = {
9
- unread?: string;
9
+ unread?: string | boolean;
10
10
  onPress?: () => void;
11
11
  createdAt?: string;
12
12
  lastMessage?: string;
@@ -40,26 +40,42 @@ export type ChatItemProps = PersonProps | GroupProps | BotProps;
40
40
  export const ChatItem: FC<ChatItemProps> = (props) => {
41
41
  const { theme, typography } = useTheme();
42
42
  const styles = getStyles(theme);
43
+ const hasUnread = Boolean(props.unread);
44
+ const unreadLabel =
45
+ typeof props.unread === 'string' && props.unread.trim().length > 0 && props.unread.trim() !== '+'
46
+ ? props.unread.trim().toUpperCase()
47
+ : 'NEW';
43
48
 
44
49
  const handlePress = () => props.onPress?.();
45
50
 
46
- // Общий заголовок (верхняя строка) + бейдж непрочитанного
51
+ // Общий заголовок (верхняя строка) + время + бейдж непрочитанного
47
52
  const renderHeader = (title: string) => (
48
- <View style={styles.senderContainer}>
49
- <Text numberOfLines={1} ellipsizeMode="tail" style={typography.titleH6}>
53
+ <View style={styles.headerRow}>
54
+ <Text numberOfLines={1} ellipsizeMode="tail" style={[typography.titleH6, styles.chatTitle]}>
50
55
  {title}
51
56
  </Text>
52
- {props.unread ? <Text style={styles.unread}>+</Text> : null}
57
+ <View style={styles.headerRight}>
58
+ {props.createdAt ? (
59
+ <Text style={[styles.timeAgo, hasUnread && styles.timeAgoUnread]}>{props.createdAt}</Text>
60
+ ) : null}
61
+ {hasUnread ? (
62
+ <View style={styles.unreadBadge}>
63
+ <View style={styles.unreadDot} />
64
+ <Text style={styles.unreadText}>{unreadLabel}</Text>
65
+ </View>
66
+ ) : null}
67
+ </View>
53
68
  </View>
54
69
  );
55
70
 
56
71
  // Превью последнего сообщения (нижний блок)
57
72
  const renderLastMessage = () => {
58
73
  if (!props.lastMessage) return null;
74
+ const previewStyle = [styles.senderName, hasUnread && styles.senderNameUnread];
59
75
 
60
76
  if (props.variant === 'person') {
61
77
  return (
62
- <Text numberOfLines={2} ellipsizeMode="tail" style={styles.senderName}>
78
+ <Text numberOfLines={2} ellipsizeMode="tail" style={previewStyle}>
63
79
  {props.senderFullName}: <Text style={styles.senderMessage}>{props.lastMessage}</Text>
64
80
  </Text>
65
81
  );
@@ -67,7 +83,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
67
83
 
68
84
  if (props.variant === 'group' && props.senderFullName) {
69
85
  return (
70
- <Text numberOfLines={2} ellipsizeMode="tail" style={styles.senderName}>
86
+ <Text numberOfLines={2} ellipsizeMode="tail" style={previewStyle}>
71
87
  {props.senderFullName}: <Text style={styles.senderMessage}>{props.lastMessage}</Text>
72
88
  </Text>
73
89
  );
@@ -75,7 +91,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
75
91
 
76
92
  // bot или group без senderFullName
77
93
  return (
78
- <Text numberOfLines={2} ellipsizeMode="tail" style={styles.senderName}>
94
+ <Text numberOfLines={2} ellipsizeMode="tail" style={previewStyle}>
79
95
  {props.lastMessage}
80
96
  </Text>
81
97
  );
@@ -118,12 +134,7 @@ export const ChatItem: FC<ChatItemProps> = (props) => {
118
134
  {renderAvatar()}
119
135
  <View style={styles.userInfo}>
120
136
  {renderHeader(titleText)}
121
-
122
- <View style={styles.senderContainer}>{renderLastMessage()}</View>
123
-
124
- {props.lastMessage ? (
125
- <Text style={styles.timeAgo}>{props.createdAt}</Text>
126
- ) : null}
137
+ <View style={styles.messageRow}>{renderLastMessage()}</View>
127
138
  </View>
128
139
  </TouchableOpacity>
129
140
  </View>
@@ -1,5 +1,5 @@
1
1
  import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
2
- import { View, StyleSheet, TextInput, TouchableOpacity, Text, Image, ScrollView, ActivityIndicator } from 'react-native';
2
+ import { View, StyleSheet, TextInput, TouchableOpacity, Text, Image, ScrollView, ActivityIndicator, Platform } from 'react-native';
3
3
  import { Ionicons } from '@expo/vector-icons';
4
4
  import { ThemeType, useTheme } from '../../theme';
5
5
 
@@ -265,7 +265,7 @@ export const getStyles = (theme: ThemeType) =>
265
265
  paddingHorizontal: 10,
266
266
  paddingVertical: 6,
267
267
  borderRadius: 4,
268
- top:4,
268
+ top: Platform.OS === 'ios' ? 4 : undefined,
269
269
  borderColor: theme.white,
270
270
  },
271
271
  sendButton: {
@@ -2,6 +2,7 @@
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
3
  import React from 'react';
4
4
  import { View } from 'react-native';
5
+ import { FontAwesome, FontAwesome5, FontAwesome6, Ionicons, MaterialIcons, Octicons } from '@expo/vector-icons';
5
6
  import PureProfileCard from "./ProfileCard";
6
7
 
7
8
  const meta = {
@@ -9,17 +10,7 @@ const meta = {
9
10
  component: PureProfileCard,
10
11
  argTypes: {
11
12
  onBack: { action: 'onBack' },
12
- onOpenSettings: { action: 'onOpenSettings' },
13
- onOpenActivity: { action: 'onOpenActivity' },
14
- onOpenSpecialist: { action: 'onOpenSpecialist' },
15
- onOpenCreatePoll: { action: 'onOpenCreatePoll' },
16
- onOpenCreateEvent: { action: 'onOpenCreateEvent' },
17
- onOpenAiBots: { action: 'onOpenAiBots' },
18
- onOpenBots: { action: 'onOpenBots' },
19
- onOpenUserSheet: { action: 'onOpenUserSheet' },
20
13
  onLearnMorePress: { action: 'onLearnMorePress' },
21
- onMessage: { action: 'onMessage' },
22
- onFollowToggle: { action: 'onFollowToggle' },
23
14
  },
24
15
  decorators: [
25
16
  // если у тебя есть ThemeProvider — оберни им здесь
@@ -32,10 +23,18 @@ const meta = {
32
23
  isMe: false,
33
24
  isOnline: false,
34
25
  lastSeenText: 'Last seen: 2 hours ago',
35
- specialistEnabled: true,
36
- hideFollowBtn: false,
37
- isFollowing: false,
38
- hasNotifications: true,
26
+ headerActions: [
27
+ {
28
+ id: 'specialist',
29
+ icon: <FontAwesome5 size={20} name="id-card" color="#111" />,
30
+ onPress: () => {},
31
+ },
32
+ {
33
+ id: 'menu',
34
+ icon: <Ionicons name="ellipsis-horizontal-sharp" size={20} color="#2f7fff" />,
35
+ onPress: () => {},
36
+ },
37
+ ],
39
38
  },
40
39
  } satisfies Meta<typeof PureProfileCard>;
41
40
 
@@ -45,16 +44,95 @@ type S = StoryObj<typeof PureProfileCard>;
45
44
  export const Default: S = {};
46
45
  export const MeOwner: S = { args: { isMe: true, isOnline: undefined, lastSeenText: undefined } };
47
46
  export const OnlineUser: S = { args: { isMe: false, isOnline: true, lastSeenText: undefined } };
48
- export const Following: S = { args: { isFollowing: true } };
49
- export const WithoutFollowBtn: S = { args: { hideFollowBtn: true } };
50
- export const MeOwnerWithCreateActions: S = {
47
+ export const TwoActions: S = {
48
+ args: {
49
+ ctaActions: [
50
+ {
51
+ label: 'Message',
52
+ variant: 'primary',
53
+ icon: <FontAwesome name="send" size={14} color="#fff" />,
54
+ onPress: () => {},
55
+ },
56
+ {
57
+ label: 'Follow',
58
+ variant: 'outline',
59
+ icon: <FontAwesome5 name="user-plus" size={14} color="#2f7fff" />,
60
+ onPress: () => {},
61
+ },
62
+ ],
63
+ },
64
+ };
65
+
66
+ export const OneActionFullWidth: S = {
67
+ args: {
68
+ ctaActions: [
69
+ {
70
+ label: 'Create Poll',
71
+ icon: <Ionicons name="stats-chart-outline" size={20} color="#2f7fff" />,
72
+ onPress: () => {},
73
+ },
74
+ ],
75
+ },
76
+ };
77
+
78
+ export const OwnerTwoActions: S = {
51
79
  args: {
52
80
  isMe: true,
53
81
  isAuth: true,
54
82
  isOnline: undefined,
55
83
  lastSeenText: undefined,
56
- // наличие коллбеков включает сами кнопки
57
- onOpenCreatePoll: () => {},
58
- onOpenCreateEvent: () => {},
84
+ headerActions: [
85
+ {
86
+ id: 'activity',
87
+ icon: <FontAwesome name="bell-o" size={21} color="#111" />,
88
+ onPress: () => {},
89
+ showDot: true,
90
+ },
91
+ {
92
+ id: 'specialist',
93
+ icon: <FontAwesome5 size={20} name="id-card" color="#111" />,
94
+ onPress: () => {},
95
+ },
96
+ {
97
+ id: 'ai-bots',
98
+ icon: <MaterialIcons name="smart-toy" size={25} color="#111" />,
99
+ onPress: () => {},
100
+ },
101
+ {
102
+ id: 'bots',
103
+ icon: <FontAwesome6 style={{ top: -1 }} size={20} name="robot" color="#111" />,
104
+ onPress: () => {},
105
+ },
106
+ {
107
+ id: 'settings',
108
+ icon: <Ionicons size={24} name="settings-outline" color="#111" />,
109
+ onPress: () => {},
110
+ },
111
+ ],
112
+ ctaActions: [
113
+ {
114
+ label: 'Create Poll',
115
+ icon: <Ionicons name="stats-chart-outline" size={20} color="#2f7fff" />,
116
+ onPress: () => {},
117
+ },
118
+ {
119
+ label: 'Create Event',
120
+ icon: <Octicons name="diff-added" size={20} color="#2f7fff" />,
121
+ onPress: () => {},
122
+ },
123
+ ],
124
+ },
125
+ };
126
+
127
+ export const SixHeaderActions: S = {
128
+ args: {
129
+ headerActions: [
130
+ { id: 'a1', icon: <FontAwesome name="bell-o" size={21} color="#111" />, onPress: () => {}, showDot: true },
131
+ { id: 'a2', icon: <FontAwesome5 size={20} name="id-card" color="#111" />, onPress: () => {} },
132
+ { id: 'a3', icon: <MaterialIcons name="smart-toy" size={25} color="#111" />, onPress: () => {} },
133
+ { id: 'a4', icon: <FontAwesome6 style={{ top: -1 }} size={20} name="robot" color="#111" />, onPress: () => {} },
134
+ { id: 'a5', icon: <Ionicons size={24} name="settings-outline" color="#111" />, onPress: () => {} },
135
+ { id: 'a6', icon: <Ionicons name="ellipsis-horizontal-sharp" size={20} color="#2f7fff" />, onPress: () => {} },
136
+ ],
59
137
  },
60
138
  };
@@ -7,9 +7,23 @@ import {
7
7
  TouchableOpacity,
8
8
  View,
9
9
  } from 'react-native';
10
- import { Ionicons, FontAwesome, FontAwesome5, Octicons, MaterialIcons, FontAwesome6 } from '@expo/vector-icons';
10
+ import { Ionicons, MaterialIcons } from '@expo/vector-icons';
11
11
  import { useProfileCardStyles } from './useProfileCardStyles';
12
12
 
13
+ export type PureProfileCardHeaderAction = {
14
+ id?: string;
15
+ onPress: () => Promise<void> | void;
16
+ icon: React.ReactNode;
17
+ showDot?: boolean;
18
+ };
19
+
20
+ export type PureProfileCardCtaAction = {
21
+ label: string;
22
+ onPress: () => Promise<void> | void;
23
+ icon?: React.ReactNode;
24
+ variant?: 'primary' | 'outline';
25
+ };
26
+
13
27
  export type PureProfileCardProps = {
14
28
  name: string;
15
29
  imageUri: string;
@@ -22,39 +36,25 @@ export type PureProfileCardProps = {
22
36
 
23
37
  /** действия/навигация */
24
38
  onBack?: () => void;
25
- onOpenSettings?: () => void;
26
- onOpenActivity?: () => void;
27
- onOpenSpecialist?: () => void;
28
- onOpenCreatePoll?: () => void;
29
- onOpenCreateEvent?: () => void;
30
- onOpenAiBots?: () => void;
31
- onOpenBots?: () => void;
32
- onOpenUserSheet?: () => void;
39
+ headerActions?: PureProfileCardHeaderAction[];
33
40
 
34
41
  /** CTA */
35
42
  onLearnMorePress?: () => void;
36
- onMessage?: () => Promise<void> | void;
37
- onFollowToggle?: () => void;
38
-
39
- /** UI-флаги */
40
- specialistEnabled?: boolean;
41
- hideFollowBtn?: boolean;
42
- isFollowing?: boolean;
43
- hasNotifications?: boolean;
43
+ ctaActions?: PureProfileCardCtaAction[];
44
44
  };
45
45
 
46
46
  export default function PureProfileCard(props: PureProfileCardProps) {
47
47
  const {
48
48
  name, imageUri,
49
49
  isAuth, isMe, isOnline, lastSeenText,
50
- onBack, onOpenSettings, onOpenActivity, onOpenSpecialist,
51
- onOpenCreatePoll, onOpenCreateEvent, onOpenAiBots, onOpenBots, onOpenUserSheet,
52
- onLearnMorePress, onMessage, onFollowToggle,
53
- specialistEnabled, hideFollowBtn, isFollowing, hasNotifications,
50
+ onBack, headerActions,
51
+ onLearnMorePress, ctaActions,
54
52
  } = props;
55
53
 
56
54
  const { theme, globalStyleSheet, typography, styles } = useProfileCardStyles();
57
55
  const [previewVisible, setPreviewVisible] = useState(false);
56
+ const visibleHeaderActions = (headerActions ?? []).slice(0, 6);
57
+ const visibleCtaActions = (ctaActions ?? []).slice(0, 2);
58
58
 
59
59
  return (
60
60
  <View style={styles.cardContainer}>
@@ -64,74 +64,20 @@ export default function PureProfileCard(props: PureProfileCardProps) {
64
64
  <Ionicons size={24} name="arrow-back" color={theme.text} />
65
65
  </TouchableOpacity>
66
66
 
67
- {!isMe ? (
68
- <View style={styles.settingSection}>
69
- {specialistEnabled && onOpenSpecialist && (
70
- <TouchableOpacity onPress={onOpenSpecialist}>
71
- <View style={styles.iconBackground}>
72
- <FontAwesome5 size={20} name="id-card" color={theme.text} />
73
- </View>
74
- </TouchableOpacity>
75
- )}
76
- {onOpenUserSheet && (
77
- <TouchableOpacity onPress={() => onOpenUserSheet()}>
78
- <View style={styles.iconBackground}>
79
- <Ionicons name="ellipsis-horizontal-sharp" size={20} color={theme.primary} />
80
- </View>
81
- </TouchableOpacity>
82
- )}
83
- </View>
84
- ) : (
85
- <View style={styles.settingSection}>
86
- {onOpenActivity && (
87
- <View>
88
- <TouchableOpacity onPress={onOpenActivity}>
89
- <View style={styles.iconBackground}>
90
- <FontAwesome size={21} name="bell-o" color={theme.text} />
91
- </View>
92
- </TouchableOpacity>
93
- {hasNotifications && (
94
- <View style={{
95
- position: 'absolute', right: 5, top: 2,
96
- width: 8, height: 8, borderRadius: 4, backgroundColor: theme.danger,
97
- }} />
98
- )}
99
- </View>
100
- )}
101
-
102
- {onOpenSpecialist && (
103
- <TouchableOpacity onPress={onOpenSpecialist}>
104
- <View style={styles.iconBackground}>
105
- <FontAwesome5 size={20} name="id-card" color={theme.text} />
106
- </View>
107
- </TouchableOpacity>
108
- )}
109
-
110
- {onOpenAiBots && (
111
- <TouchableOpacity onPress={onOpenAiBots}>
67
+ <View style={styles.settingSection}>
68
+ {visibleHeaderActions.map((action, index) => (
69
+ <View key={action.id ?? `header-action-${index}`} style={styles.headerActionItem}>
70
+ <TouchableOpacity onPress={action.onPress}>
112
71
  <View style={styles.iconBackground}>
113
- <MaterialIcons name="smart-toy" size={25} color={theme.text} />
72
+ {action.icon}
114
73
  </View>
115
74
  </TouchableOpacity>
116
- )}
117
-
118
- {onOpenBots && (
119
- <TouchableOpacity onPress={onOpenBots}>
120
- <View style={styles.iconBackground}>
121
- <FontAwesome6 style={{ top: -1 }} size={20} name="robot" color={theme.text} />
122
- </View>
123
- </TouchableOpacity>
124
- )}
125
-
126
- {onOpenSettings && (
127
- <TouchableOpacity onPress={onOpenSettings}>
128
- <View style={styles.iconBackground}>
129
- <Ionicons size={24} name="settings-outline" color={theme.text} />
130
- </View>
131
- </TouchableOpacity>
132
- )}
133
- </View>
134
- )}
75
+ {action.showDot && (
76
+ <View style={styles.headerActionDot} />
77
+ )}
78
+ </View>
79
+ ))}
80
+ </View>
135
81
  </View>
136
82
 
137
83
  {/* аватар */}
@@ -161,36 +107,24 @@ export default function PureProfileCard(props: PureProfileCardProps) {
161
107
  </TouchableOpacity>}
162
108
 
163
109
  {/* CTA */}
164
- {isAuth && !isMe && (
110
+ {visibleCtaActions.length > 0 && (
165
111
  <View style={styles.buttonsRow}>
166
- <TouchableOpacity style={[styles.btnPrimary, hideFollowBtn && { minWidth: '98%' }]} onPress={onMessage}>
167
- <FontAwesome name="send" size={14} color={theme.white} />
168
- <Text style={styles.btnPrimaryText}>Message</Text>
169
- </TouchableOpacity>
170
-
171
- {!hideFollowBtn && (
172
- <TouchableOpacity style={styles.btnOutline} onPress={onFollowToggle}>
173
- <FontAwesome5 name={isFollowing ? 'user-minus' : 'user-plus'} size={14} color={theme.primary} />
174
- <Text style={styles.btnOutlineText}>{isFollowing ? 'Unfollow' : 'Follow'}</Text>
175
- </TouchableOpacity>
176
- )}
177
- </View>
178
- )}
179
-
180
- {(isMe && onOpenCreatePoll || isMe && onOpenCreateEvent) &&(
181
- <View style={styles.buttonsRow}>
182
- {onOpenCreatePoll && (
183
- <TouchableOpacity style={styles.btnOutline} onPress={onOpenCreatePoll}>
184
- <Ionicons name="stats-chart-outline" size={20} color={theme.primary} />
185
- <Text style={styles.btnOutlineText}>Create Poll</Text>
186
- </TouchableOpacity>
187
- )}
188
- {onOpenCreateEvent && (
189
- <TouchableOpacity style={styles.btnOutline} onPress={onOpenCreateEvent}>
190
- <Octicons name="diff-added" size={20} color={theme.primary} />
191
- <Text style={styles.btnOutlineText}>Create Event</Text>
192
- </TouchableOpacity>
193
- )}
112
+ {visibleCtaActions.map((action, index) => {
113
+ const isPrimary = (action.variant ?? 'outline') === 'primary';
114
+ return (
115
+ <TouchableOpacity
116
+ key={`${action.label}-${index}`}
117
+ style={[
118
+ isPrimary ? styles.btnPrimary : styles.btnOutline,
119
+ visibleCtaActions.length === 1 && styles.btnWide,
120
+ ]}
121
+ onPress={action.onPress}
122
+ >
123
+ {action.icon}
124
+ <Text style={isPrimary ? styles.btnPrimaryText : styles.btnOutlineText}>{action.label}</Text>
125
+ </TouchableOpacity>
126
+ );
127
+ })}
194
128
  </View>
195
129
  )}
196
130
 
@@ -19,12 +19,23 @@ export const useProfileCardStyles = () => {
19
19
  },
20
20
  navigation: { width: '100%' },
21
21
  settingSection: { flexDirection: 'row' },
22
+ headerActionItem: {
23
+ position: 'relative',
24
+ },
22
25
  iconBackground: {
23
26
  backgroundColor: theme.backgroundLight,
24
27
  height: 40, width: 40, borderRadius: 50,
25
28
  alignItems: 'center', justifyContent: 'center',
26
29
  },
27
- dot: { backgroundColor: theme.danger, right: 5, top: 2 },
30
+ headerActionDot: {
31
+ position: 'absolute',
32
+ right: 5,
33
+ top: 2,
34
+ width: 8,
35
+ height: 8,
36
+ borderRadius: 4,
37
+ backgroundColor: theme.danger,
38
+ },
28
39
  learnMoreText: { color: theme.placeholder, marginLeft: 5 },
29
40
 
30
41
  buttonsRow: {
@@ -1,6 +1,6 @@
1
1
  export { ModalProfilePhoto } from './ModalProfilePhoto';
2
2
  export { default as ProfileCard } from './ProfileCard/ProfileCard';
3
- export type { PureProfileCardProps } from './ProfileCard/ProfileCard';
3
+ export type { PureProfileCardProps, PureProfileCardCtaAction, PureProfileCardHeaderAction } from './ProfileCard/ProfileCard';
4
4
  export { default as ProfilePhotoBanner } from './ProfilePhotoBanner';
5
5
  export { default as ProfilePhotoUpload } from './ProfilePhotoUpload/ProfilePhotoUpload';
6
6
  export type { ProfilePhotoUploadProps } from './ProfilePhotoUpload/ProfilePhotoUpload';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-vs-lb",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
4
4
  "description": "Expo Router + Storybook template ready for npm distribution.",
5
5
  "keywords": [
6
6
  "expo",