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.
- package/components/Chat/ChatItem.stories.tsx +7 -5
- package/components/Chat/ChatItem.styles.ts +59 -26
- package/components/Chat/ChatItem.tsx +25 -14
- package/components/Chat/InputMessage.tsx +2 -2
- package/components/Profile/ProfileCard/ProfileCard.stories.tsx +98 -20
- package/components/Profile/ProfileCard/ProfileCard.tsx +49 -115
- package/components/Profile/ProfileCard/useProfileCardStyles.ts +12 -1
- package/components/Profile/index.ts +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
paddingVertical: 8,
|
|
7
|
+
paddingHorizontal: 12,
|
|
8
|
+
borderRadius: 16,
|
|
9
|
+
backgroundColor: theme.white,
|
|
10
10
|
},
|
|
11
11
|
profileSection: {
|
|
12
12
|
flexDirection: 'row',
|
|
13
|
-
alignItems: '
|
|
13
|
+
alignItems: 'flex-start',
|
|
14
14
|
flex: 1,
|
|
15
15
|
},
|
|
16
16
|
avatar: {
|
|
17
|
-
width:
|
|
18
|
-
height:
|
|
19
|
-
borderRadius:
|
|
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
|
-
|
|
33
|
+
headerRight: {
|
|
26
34
|
flexDirection: 'row',
|
|
27
|
-
alignItems:
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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={
|
|
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={
|
|
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={
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
51
|
-
|
|
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
|
-
{
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
<TouchableOpacity onPress={
|
|
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
|
-
|
|
72
|
+
{action.icon}
|
|
114
73
|
</View>
|
|
115
74
|
</TouchableOpacity>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
{
|
|
110
|
+
{visibleCtaActions.length > 0 && (
|
|
165
111
|
<View style={styles.buttonsRow}>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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';
|