rn-vs-lb 1.0.68 → 1.0.69

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.
@@ -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.69",
4
4
  "description": "Expo Router + Storybook template ready for npm distribution.",
5
5
  "keywords": [
6
6
  "expo",