rn-vs-lb 1.0.63 → 1.0.65
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/DeleteAccountButton.stories.tsx +12 -0
- package/components/Button/DeleteAccountButton.tsx +28 -8
- package/components/Button/TelegramFeedbackLink.stories.tsx +6 -0
- package/components/Button/TelegramFeedbackLink.tsx +12 -4
- package/components/Cards/EventCard.stories.tsx +3 -0
- package/components/Cards/EventCard.tsx +9 -2
- package/components/Chat/InputMessage.stories.tsx +11 -0
- package/components/Chat/InputMessage.tsx +5 -3
- package/components/Header/HeaderWithImg.stories.tsx +9 -0
- package/components/Header/HeaderWithImg.tsx +27 -7
- package/components/Modals/GuestAiChatModal.stories.tsx +11 -3
- package/components/Modals/GuestAiChatModal.tsx +9 -3
- package/components/Modals/ReportModal.stories.tsx +34 -1
- package/components/Modals/ReportModal.tsx +23 -24
- package/components/Poll/PollCardList.stories.tsx +4 -0
- package/components/Poll/PollCardList.tsx +4 -2
- package/components/Profile/ProfilePhotoBanner.stories.tsx +10 -1
- package/components/Profile/ProfilePhotoBanner.tsx +5 -3
- package/components/Specialist/Hero.stories.tsx +1 -0
- package/components/Specialist/Hero.tsx +3 -1
- package/components/Specialist/ServicesList.stories.tsx +1 -0
- package/components/Specialist/ServicesList.tsx +4 -3
- 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/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/StepProgress.tsx +1 -1
- 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/UserCards/Organazer.tsx +3 -2
- package/components/UserCards/Organizer.stories.tsx +4 -0
- package/components/UserCards/SpecialistCard.stories.tsx +2 -0
- package/components/UserCards/SpecialistCard.tsx +3 -2
- package/package.json +1 -1
|
@@ -18,9 +18,21 @@ const createMockDelete = (delay = 800) => async () => {
|
|
|
18
18
|
export const Default = Template.bind({});
|
|
19
19
|
Default.args = {
|
|
20
20
|
deleteAccount: createMockDelete(),
|
|
21
|
+
triggerLabel: 'Delete account',
|
|
22
|
+
modalTitle: 'Confirm Deletion',
|
|
23
|
+
modalDescription:
|
|
24
|
+
'All your data, including profile, events, and chat history, will be permanently deleted. This process is irreversible and will be completed within 24 hours. Are you sure you want to proceed?',
|
|
25
|
+
cancelButtonLabel: 'Cancel',
|
|
26
|
+
confirmButtonLabel: 'Delete',
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
export const SlowNetwork = Template.bind({});
|
|
24
30
|
SlowNetwork.args = {
|
|
25
31
|
deleteAccount: createMockDelete(2000),
|
|
32
|
+
triggerLabel: 'Delete account',
|
|
33
|
+
modalTitle: 'Confirm Deletion',
|
|
34
|
+
modalDescription:
|
|
35
|
+
'All your data, including profile, events, and chat history, will be permanently deleted. This process is irreversible and will be completed within 24 hours. Are you sure you want to proceed?',
|
|
36
|
+
cancelButtonLabel: 'Cancel',
|
|
37
|
+
confirmButtonLabel: 'Delete',
|
|
26
38
|
};
|
|
@@ -6,9 +6,21 @@ import { ThemeType, useTheme } from '../../theme';
|
|
|
6
6
|
|
|
7
7
|
export type DeleteAccountButtonProps = {
|
|
8
8
|
deleteAccount: () => Promise<void>;
|
|
9
|
+
triggerLabel: string;
|
|
10
|
+
modalTitle: string;
|
|
11
|
+
modalDescription: string;
|
|
12
|
+
cancelButtonLabel: string;
|
|
13
|
+
confirmButtonLabel: string;
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
export const DeleteAccountButton: FC<DeleteAccountButtonProps> = ({
|
|
16
|
+
export const DeleteAccountButton: FC<DeleteAccountButtonProps> = ({
|
|
17
|
+
deleteAccount,
|
|
18
|
+
triggerLabel,
|
|
19
|
+
modalTitle,
|
|
20
|
+
modalDescription,
|
|
21
|
+
cancelButtonLabel,
|
|
22
|
+
confirmButtonLabel,
|
|
23
|
+
}) => {
|
|
12
24
|
const [modalVisible, setModalVisible] = useState(false);
|
|
13
25
|
const { theme, typography } = useTheme();
|
|
14
26
|
const styles = getStyles({ theme });
|
|
@@ -21,7 +33,7 @@ export const DeleteAccountButton: FC<DeleteAccountButtonProps> = ({ deleteAccoun
|
|
|
21
33
|
return (
|
|
22
34
|
<View style={styles.container}>
|
|
23
35
|
<TouchableOpacity style={styles.deleteButton} onPress={() => setModalVisible(true)}>
|
|
24
|
-
<Text style={[typography.titleH6, { color: theme.red }]}>
|
|
36
|
+
<Text style={[typography.titleH6, { color: theme.red }]}>{triggerLabel}</Text>
|
|
25
37
|
</TouchableOpacity>
|
|
26
38
|
|
|
27
39
|
<Modal
|
|
@@ -32,17 +44,25 @@ export const DeleteAccountButton: FC<DeleteAccountButtonProps> = ({ deleteAccoun
|
|
|
32
44
|
>
|
|
33
45
|
<View style={styles.modalOverlay}>
|
|
34
46
|
<View style={styles.modalContent}>
|
|
35
|
-
<Text style={typography.titleH5}>
|
|
47
|
+
<Text style={typography.titleH5}>{modalTitle}</Text>
|
|
36
48
|
<Spacer size="xxs" />
|
|
37
49
|
<Text style={[typography.body, { textAlign: "center" }]}>
|
|
38
|
-
|
|
39
|
-
This process is irreversible and will be completed within 24 hours.
|
|
40
|
-
Are you sure you want to proceed?
|
|
50
|
+
{modalDescription}
|
|
41
51
|
</Text>
|
|
42
52
|
<Spacer size="lg" />
|
|
43
53
|
<View style={styles.buttonRow}>
|
|
44
|
-
<Button
|
|
45
|
-
|
|
54
|
+
<Button
|
|
55
|
+
onPress={() => setModalVisible(false)}
|
|
56
|
+
style={styles.cancelButton}
|
|
57
|
+
type="gray-outline"
|
|
58
|
+
title={cancelButtonLabel}
|
|
59
|
+
/>
|
|
60
|
+
<Button
|
|
61
|
+
onPress={handleDeleteAccount}
|
|
62
|
+
type="report-outline"
|
|
63
|
+
style={styles.confirmButton}
|
|
64
|
+
title={confirmButtonLabel}
|
|
65
|
+
/>
|
|
46
66
|
</View>
|
|
47
67
|
</View>
|
|
48
68
|
</View>
|
|
@@ -20,9 +20,15 @@ const Template: StoryFn<TelegramFeedbackLinkProps> = (args) => <TelegramFeedback
|
|
|
20
20
|
export const Default = Template.bind({});
|
|
21
21
|
Default.args = {
|
|
22
22
|
link: 'https://t.me/volunteer_support_bot',
|
|
23
|
+
title: 'Feedback & Bugs',
|
|
24
|
+
subtitle: 'Tap to write us in Telegram',
|
|
25
|
+
unsupportedLinkMessage: "Can't open Telegram URL",
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
export const CustomRoom = Template.bind({});
|
|
26
29
|
CustomRoom.args = {
|
|
27
30
|
link: 'https://t.me/joinchat/ExampleRoom',
|
|
31
|
+
title: 'Report an Issue',
|
|
32
|
+
subtitle: 'Reach out to the team on Telegram',
|
|
33
|
+
unsupportedLinkMessage: "Can't open Telegram URL",
|
|
28
34
|
};
|
|
@@ -6,9 +6,17 @@ import { useTheme } from '../../theme';
|
|
|
6
6
|
|
|
7
7
|
export type TelegramFeedbackLinkProps = {
|
|
8
8
|
link: string;
|
|
9
|
+
title: string;
|
|
10
|
+
subtitle: string;
|
|
11
|
+
unsupportedLinkMessage: string;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
export const TelegramFeedbackLink: React.FC<TelegramFeedbackLinkProps> = ({
|
|
14
|
+
export const TelegramFeedbackLink: React.FC<TelegramFeedbackLinkProps> = ({
|
|
15
|
+
link,
|
|
16
|
+
title,
|
|
17
|
+
subtitle,
|
|
18
|
+
unsupportedLinkMessage,
|
|
19
|
+
}) => {
|
|
12
20
|
const { theme, typography } = useTheme();
|
|
13
21
|
|
|
14
22
|
const handlePress = async () => {
|
|
@@ -16,7 +24,7 @@ export const TelegramFeedbackLink: React.FC<TelegramFeedbackLinkProps> = ({ link
|
|
|
16
24
|
if (supported) {
|
|
17
25
|
await Linking.openURL(link);
|
|
18
26
|
} else {
|
|
19
|
-
console.warn(
|
|
27
|
+
console.warn(unsupportedLinkMessage);
|
|
20
28
|
}
|
|
21
29
|
};
|
|
22
30
|
|
|
@@ -24,8 +32,8 @@ export const TelegramFeedbackLink: React.FC<TelegramFeedbackLinkProps> = ({ link
|
|
|
24
32
|
<TouchableOpacity style={styles.container} onPress={handlePress}>
|
|
25
33
|
<FontAwesome name={'telegram'} size={26} color={theme.primaryLight} />
|
|
26
34
|
<View style={styles.textContainer}>
|
|
27
|
-
<Text style={[typography.titleH6, { color: theme.text }]}>
|
|
28
|
-
<Text style={typography.bodySm}>
|
|
35
|
+
<Text style={[typography.titleH6, { color: theme.text }]}>{title}</Text>
|
|
36
|
+
<Text style={typography.bodySm}>{subtitle}</Text>
|
|
29
37
|
</View>
|
|
30
38
|
</TouchableOpacity>
|
|
31
39
|
);
|
|
@@ -57,12 +57,15 @@ export const Default: Story = {
|
|
|
57
57
|
'Crash Drum Studio — пространство, где можно почувствовать силу ритма и научиться играть на ударных в любой форме.',
|
|
58
58
|
organizerAvatarUri: 'https://i.pravatar.cc/150?img=11',
|
|
59
59
|
organizerName: 'Admin Belgrade',
|
|
60
|
+
organizerRoleLabel: 'Organizer',
|
|
60
61
|
likes: 42,
|
|
61
62
|
views: 313,
|
|
62
63
|
hasLike: false,
|
|
63
64
|
isUserParticipantInPost: false,
|
|
64
65
|
participantsCount: 0,
|
|
65
66
|
visible: true,
|
|
67
|
+
participantLabel: 'You’re participating',
|
|
68
|
+
demoLabel: 'DEMO',
|
|
66
69
|
},
|
|
67
70
|
};
|
|
68
71
|
|
|
@@ -13,6 +13,7 @@ interface EventCardProps {
|
|
|
13
13
|
description: string;
|
|
14
14
|
organizerAvatarUri: string;
|
|
15
15
|
organizerName: string;
|
|
16
|
+
organizerRoleLabel: string;
|
|
16
17
|
onPress: () => void;
|
|
17
18
|
likes: number;
|
|
18
19
|
views: number;
|
|
@@ -24,6 +25,8 @@ interface EventCardProps {
|
|
|
24
25
|
maxParticipants?: number;
|
|
25
26
|
categories?: string[];
|
|
26
27
|
price?: string;
|
|
28
|
+
participantLabel: string;
|
|
29
|
+
demoLabel: string;
|
|
27
30
|
|
|
28
31
|
/** Новое: видимость карточки сообщает родитель (FlatList/ScrollView и т.д.) */
|
|
29
32
|
visible?: boolean;
|
|
@@ -39,6 +42,7 @@ const EventCard: React.FC<EventCardProps> = ({
|
|
|
39
42
|
description,
|
|
40
43
|
organizerAvatarUri,
|
|
41
44
|
organizerName,
|
|
45
|
+
organizerRoleLabel,
|
|
42
46
|
onPress,
|
|
43
47
|
likes,
|
|
44
48
|
views,
|
|
@@ -50,6 +54,8 @@ const EventCard: React.FC<EventCardProps> = ({
|
|
|
50
54
|
maxParticipants,
|
|
51
55
|
categories,
|
|
52
56
|
price,
|
|
57
|
+
participantLabel,
|
|
58
|
+
demoLabel,
|
|
53
59
|
visible = false,
|
|
54
60
|
triggerOnce = true,
|
|
55
61
|
}) => {
|
|
@@ -77,7 +83,7 @@ const EventCard: React.FC<EventCardProps> = ({
|
|
|
77
83
|
<Image source={{ uri: imageUri }} style={styles.image} resizeMode="cover" />
|
|
78
84
|
{isUserParticipantInPost && (
|
|
79
85
|
<View style={styles.participantOverlay}>
|
|
80
|
-
<Text style={styles.participantText}>
|
|
86
|
+
<Text style={styles.participantText}>{participantLabel}</Text>
|
|
81
87
|
</View>
|
|
82
88
|
)}
|
|
83
89
|
</View>
|
|
@@ -86,7 +92,7 @@ const EventCard: React.FC<EventCardProps> = ({
|
|
|
86
92
|
<View style={globalStyleSheet.flexRowCenterStart}>
|
|
87
93
|
{date && <Text style={[typography.body, styles.date]}>{date}</Text>}
|
|
88
94
|
{categories?.includes('bot') && (
|
|
89
|
-
<Text style={[typography.body, styles.demo]}>
|
|
95
|
+
<Text style={[typography.body, styles.demo]}>{demoLabel}</Text>
|
|
90
96
|
)}
|
|
91
97
|
{price && <Text style={[typography.body, styles.demo]}>{price}</Text>}
|
|
92
98
|
</View>
|
|
@@ -107,6 +113,7 @@ const EventCard: React.FC<EventCardProps> = ({
|
|
|
107
113
|
<Organizer
|
|
108
114
|
avatarUri={organizerAvatarUri}
|
|
109
115
|
organizerName={organizerName}
|
|
116
|
+
roleLabel={organizerRoleLabel}
|
|
110
117
|
onPress={onPress}
|
|
111
118
|
/>
|
|
112
119
|
<SocialStatsEvent onLike={onLike} hasLike={hasLike} likes={likes} views={views} />
|
|
@@ -87,18 +87,21 @@ const Template: StoryFn<Props> = (args) => {
|
|
|
87
87
|
export const Default = Template.bind({});
|
|
88
88
|
Default.args = {
|
|
89
89
|
placeholder: 'Message',
|
|
90
|
+
editingLabel: 'Editing message',
|
|
90
91
|
maxImages: 1,
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
export const AttachmentsDisabled = Template.bind({});
|
|
94
95
|
AttachmentsDisabled.args = {
|
|
95
96
|
placeholder: 'No attachments allowed',
|
|
97
|
+
editingLabel: 'Editing message',
|
|
96
98
|
enableImageAttachment: false,
|
|
97
99
|
};
|
|
98
100
|
|
|
99
101
|
export const WithAttachments = Template.bind({});
|
|
100
102
|
WithAttachments.args = {
|
|
101
103
|
placeholder: 'Attach up to 2 images',
|
|
104
|
+
editingLabel: 'Editing message',
|
|
102
105
|
maxImages: 2,
|
|
103
106
|
};
|
|
104
107
|
WithAttachments.render = (args) => {
|
|
@@ -118,6 +121,7 @@ WithAttachments.render = (args) => {
|
|
|
118
121
|
return imgs;
|
|
119
122
|
}}
|
|
120
123
|
onMaxImagesExceeded={(max) => Alert.alert('Max images exceeded', `Allowed: ${max}`)}
|
|
124
|
+
editingLabel="Editing message"
|
|
121
125
|
/>
|
|
122
126
|
);
|
|
123
127
|
};
|
|
@@ -137,6 +141,8 @@ export const ReplyMode: StoryFn = () => {
|
|
|
137
141
|
onCancelReply={replyCancelHandler}
|
|
138
142
|
onTyping={typingHandler}
|
|
139
143
|
onStopTyping={stopTypingHandler}
|
|
144
|
+
placeholder="Message"
|
|
145
|
+
editingLabel="Editing message"
|
|
140
146
|
/>
|
|
141
147
|
);
|
|
142
148
|
};
|
|
@@ -152,6 +158,8 @@ export const EditMode: StoryFn = () => {
|
|
|
152
158
|
onCancelEdit={editCancelHandler}
|
|
153
159
|
onTyping={typingHandler}
|
|
154
160
|
onStopTyping={stopTypingHandler}
|
|
161
|
+
placeholder="Message"
|
|
162
|
+
editingLabel="Editing message"
|
|
155
163
|
/>
|
|
156
164
|
);
|
|
157
165
|
};
|
|
@@ -176,6 +184,8 @@ export const SendingControlled: StoryFn = () => {
|
|
|
176
184
|
maxImages={1}
|
|
177
185
|
onTyping={typingHandler}
|
|
178
186
|
onStopTyping={stopTypingHandler}
|
|
187
|
+
placeholder="Message"
|
|
188
|
+
editingLabel="Editing message"
|
|
179
189
|
/>
|
|
180
190
|
);
|
|
181
191
|
};
|
|
@@ -200,6 +210,7 @@ export const ManyInputsDemo: StoryFn = () => {
|
|
|
200
210
|
placeholder={`Message #${idx + 1}`}
|
|
201
211
|
onTyping={() => logEvent(`typing-${idx}`)}
|
|
202
212
|
onStopTyping={() => logEvent(`stop-typing-${idx}`)}
|
|
213
|
+
editingLabel="Editing message"
|
|
203
214
|
/>
|
|
204
215
|
))}
|
|
205
216
|
</ScrollView>
|
|
@@ -33,7 +33,8 @@ interface InputMessageProps {
|
|
|
33
33
|
onStopTyping?: () => void; // будет вызвано через ~2s тишины
|
|
34
34
|
|
|
35
35
|
// Placeholder
|
|
36
|
-
placeholder
|
|
36
|
+
placeholder: string;
|
|
37
|
+
editingLabel: string;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export const InputMessage: FC<InputMessageProps> = ({
|
|
@@ -56,7 +57,8 @@ export const InputMessage: FC<InputMessageProps> = ({
|
|
|
56
57
|
onTyping,
|
|
57
58
|
onStopTyping,
|
|
58
59
|
|
|
59
|
-
placeholder
|
|
60
|
+
placeholder,
|
|
61
|
+
editingLabel,
|
|
60
62
|
}) => {
|
|
61
63
|
const { theme, typography } = useTheme();
|
|
62
64
|
const styles = getStyles(theme);
|
|
@@ -119,7 +121,7 @@ export const InputMessage: FC<InputMessageProps> = ({
|
|
|
119
121
|
{/* Edit */}
|
|
120
122
|
{editMessage && (
|
|
121
123
|
<View style={[styles.replyContainer, { borderLeftColor: theme.primaryLight }]}>
|
|
122
|
-
<Text style={styles.replyLabel}>
|
|
124
|
+
<Text style={styles.replyLabel}>{editingLabel}</Text>
|
|
123
125
|
<View style={styles.replyContent}>
|
|
124
126
|
<Text numberOfLines={1} style={styles.replyText}>
|
|
125
127
|
{editMessage.content ?? ''}
|
|
@@ -16,6 +16,15 @@ const meta: Meta = {
|
|
|
16
16
|
userName: 'Alex Johnson',
|
|
17
17
|
},
|
|
18
18
|
],
|
|
19
|
+
typingText: 'typing…',
|
|
20
|
+
onlineText: 'Online',
|
|
21
|
+
offlineText: 'Offline',
|
|
22
|
+
groupTypingSuffix: ': typing…',
|
|
23
|
+
backAccessibilityLabel: 'Back',
|
|
24
|
+
imageAccessibilityLabel: 'Open chat image',
|
|
25
|
+
actionsAccessibilityLabel: 'More actions',
|
|
26
|
+
onlineStatusAccessibilityLabel: 'online',
|
|
27
|
+
offlineStatusAccessibilityLabel: 'offline',
|
|
19
28
|
},
|
|
20
29
|
argTypes: {
|
|
21
30
|
onBackPress: { action: 'back' },
|
|
@@ -22,6 +22,15 @@ interface HeaderProps {
|
|
|
22
22
|
isOnline?: boolean;
|
|
23
23
|
isTyping?: boolean;
|
|
24
24
|
typingUserName?: string;
|
|
25
|
+
typingText: string;
|
|
26
|
+
onlineText: string;
|
|
27
|
+
offlineText: string;
|
|
28
|
+
groupTypingSuffix: string;
|
|
29
|
+
backAccessibilityLabel: string;
|
|
30
|
+
imageAccessibilityLabel: string;
|
|
31
|
+
actionsAccessibilityLabel: string;
|
|
32
|
+
onlineStatusAccessibilityLabel: string;
|
|
33
|
+
offlineStatusAccessibilityLabel: string;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
@@ -35,6 +44,15 @@ const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
|
35
44
|
isOnline = false,
|
|
36
45
|
isTyping = false,
|
|
37
46
|
typingUserName,
|
|
47
|
+
typingText,
|
|
48
|
+
onlineText,
|
|
49
|
+
offlineText,
|
|
50
|
+
groupTypingSuffix,
|
|
51
|
+
backAccessibilityLabel,
|
|
52
|
+
imageAccessibilityLabel,
|
|
53
|
+
actionsAccessibilityLabel,
|
|
54
|
+
onlineStatusAccessibilityLabel,
|
|
55
|
+
offlineStatusAccessibilityLabel,
|
|
38
56
|
}) => {
|
|
39
57
|
const { globalStyleSheet, theme, sizes, commonStyles, typography } = useTheme();
|
|
40
58
|
const styles = useMemo(
|
|
@@ -46,7 +64,7 @@ const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
|
46
64
|
|
|
47
65
|
return (
|
|
48
66
|
<View style={styles.container}>
|
|
49
|
-
<TouchableOpacity onPress={onBackPress} accessibilityRole="button" accessibilityLabel=
|
|
67
|
+
<TouchableOpacity onPress={onBackPress} accessibilityRole="button" accessibilityLabel={backAccessibilityLabel}>
|
|
50
68
|
<Ionicons name="arrow-back" size={24} color={theme.text} />
|
|
51
69
|
</TouchableOpacity>
|
|
52
70
|
|
|
@@ -56,7 +74,7 @@ const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
|
56
74
|
style={globalStyleSheet.flexRowCenter}
|
|
57
75
|
onPress={onImgPress}
|
|
58
76
|
accessibilityRole="imagebutton"
|
|
59
|
-
accessibilityLabel=
|
|
77
|
+
accessibilityLabel={imageAccessibilityLabel}
|
|
60
78
|
activeOpacity={0.8}
|
|
61
79
|
>
|
|
62
80
|
<Image source={{ uri: imgUrl }} style={styles.photo} />
|
|
@@ -69,15 +87,17 @@ const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
|
69
87
|
{/* отображение статусов */}
|
|
70
88
|
{!isGroupChat ? (
|
|
71
89
|
isTyping ? (
|
|
72
|
-
<Text style={[typography.body, styles.subtitleItalic]}>
|
|
90
|
+
<Text style={[typography.body, styles.subtitleItalic]}>{typingText}</Text>
|
|
73
91
|
) : (
|
|
74
92
|
<View style={styles.statusRow}>
|
|
75
93
|
<Text style={[typography.body, styles.subtitleItalic]}>
|
|
76
|
-
{isOnline ?
|
|
94
|
+
{isOnline ? onlineText : offlineText}
|
|
77
95
|
</Text>
|
|
78
96
|
<View
|
|
79
97
|
style={[styles.statusDot, isOnline ? styles.online : styles.offline]}
|
|
80
|
-
accessibilityLabel={
|
|
98
|
+
accessibilityLabel={
|
|
99
|
+
isOnline ? onlineStatusAccessibilityLabel : offlineStatusAccessibilityLabel
|
|
100
|
+
}
|
|
81
101
|
/>
|
|
82
102
|
</View>
|
|
83
103
|
)
|
|
@@ -87,7 +107,7 @@ const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
|
87
107
|
ellipsizeMode="tail"
|
|
88
108
|
style={[typography.body, styles.subtitleItalic]}
|
|
89
109
|
>
|
|
90
|
-
{typingUserName}
|
|
110
|
+
{`${typingUserName}${groupTypingSuffix}`}
|
|
91
111
|
</Text>
|
|
92
112
|
) : null}
|
|
93
113
|
</View>
|
|
@@ -99,7 +119,7 @@ const HeaderWithImg: React.FC<HeaderProps> = ({
|
|
|
99
119
|
style={styles.actionButton}
|
|
100
120
|
onPress={onActionPress}
|
|
101
121
|
accessibilityRole="button"
|
|
102
|
-
accessibilityLabel=
|
|
122
|
+
accessibilityLabel={actionsAccessibilityLabel}
|
|
103
123
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
104
124
|
>
|
|
105
125
|
<Ionicons name="ellipsis-horizontal-sharp" size={20} color={theme.primary} />
|
|
@@ -21,6 +21,8 @@ const sampleMessages: PureChatMessage[] = [
|
|
|
21
21
|
},
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
+
const defaultLimitLabel = (remaining: number, limit: number) => `Осталось сообщений: ${remaining} / ${limit}`;
|
|
25
|
+
|
|
24
26
|
const meta = {
|
|
25
27
|
title: 'Modals/GuestAiChatModal',
|
|
26
28
|
component: GuestAiChatModalView,
|
|
@@ -36,6 +38,7 @@ const meta = {
|
|
|
36
38
|
theme: { table: { disable: true } },
|
|
37
39
|
typography: { table: { disable: true } },
|
|
38
40
|
sizes: { table: { disable: true } },
|
|
41
|
+
limitLabel: { table: { disable: true } },
|
|
39
42
|
},
|
|
40
43
|
args: {
|
|
41
44
|
visible: true,
|
|
@@ -49,6 +52,9 @@ const meta = {
|
|
|
49
52
|
theme,
|
|
50
53
|
typography,
|
|
51
54
|
sizes: SIZES,
|
|
55
|
+
defaultBotName: 'AI-бот',
|
|
56
|
+
limitLabel: defaultLimitLabel,
|
|
57
|
+
inputPlaceholder: 'Спросите что-нибудь...'
|
|
52
58
|
},
|
|
53
59
|
decorators: [
|
|
54
60
|
(StoryComponent) => (
|
|
@@ -174,9 +180,11 @@ Playground.args = {
|
|
|
174
180
|
remaining: 5,
|
|
175
181
|
};
|
|
176
182
|
|
|
177
|
-
export const DefaultOpen: StoryFn<GuestAiChatProps> =
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
export const DefaultOpen: StoryFn<GuestAiChatProps> = {
|
|
184
|
+
render: Template,
|
|
185
|
+
args: {
|
|
186
|
+
visible: true,
|
|
187
|
+
},
|
|
180
188
|
};
|
|
181
189
|
|
|
182
190
|
export const WithErrorState: StoryFn<GuestAiChatProps> = (args) => (
|
|
@@ -32,6 +32,9 @@ export type GuestAiChatViewProps = {
|
|
|
32
32
|
isSending: boolean;
|
|
33
33
|
limit?: number;
|
|
34
34
|
remaining?: number;
|
|
35
|
+
defaultBotName: string;
|
|
36
|
+
limitLabel: (remaining: number, limit: number) => string;
|
|
37
|
+
inputPlaceholder: string;
|
|
35
38
|
|
|
36
39
|
// экшены
|
|
37
40
|
onChangeInput: (v: string) => void;
|
|
@@ -57,6 +60,9 @@ export const GuestAiChatModalView: FC<GuestAiChatViewProps> = memo(
|
|
|
57
60
|
isSending,
|
|
58
61
|
limit,
|
|
59
62
|
remaining,
|
|
63
|
+
defaultBotName,
|
|
64
|
+
limitLabel,
|
|
65
|
+
inputPlaceholder,
|
|
60
66
|
onChangeInput,
|
|
61
67
|
onSend,
|
|
62
68
|
listRef,
|
|
@@ -92,10 +98,10 @@ export const GuestAiChatModalView: FC<GuestAiChatViewProps> = memo(
|
|
|
92
98
|
<View style={styles.modalContainer}>
|
|
93
99
|
<View style={styles.header}>
|
|
94
100
|
<View>
|
|
95
|
-
<Text style={typography.titleH6}>{botName ||
|
|
101
|
+
<Text style={typography.titleH6}>{botName || defaultBotName}</Text>
|
|
96
102
|
{limit !== undefined && remaining !== undefined && (
|
|
97
103
|
<Text style={styles.limitText}>
|
|
98
|
-
|
|
104
|
+
{limitLabel(remaining, limit)}
|
|
99
105
|
</Text>
|
|
100
106
|
)}
|
|
101
107
|
</View>
|
|
@@ -118,7 +124,7 @@ export const GuestAiChatModalView: FC<GuestAiChatViewProps> = memo(
|
|
|
118
124
|
<View style={styles.inputRow}>
|
|
119
125
|
<TextInput
|
|
120
126
|
style={styles.input}
|
|
121
|
-
placeholder=
|
|
127
|
+
placeholder={inputPlaceholder}
|
|
122
128
|
placeholderTextColor={theme.greyText}
|
|
123
129
|
value={inputValue}
|
|
124
130
|
onChangeText={onChangeInput}
|
|
@@ -13,6 +13,23 @@ const submitLogger = (payload: { reason: string; details?: string }) => {
|
|
|
13
13
|
console.log('[storybook:report-modal:submit]', payload);
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const userReasonOptions = [
|
|
17
|
+
'Spam or scam',
|
|
18
|
+
'Harassment or bullying',
|
|
19
|
+
'Inappropriate content',
|
|
20
|
+
'Fake profile',
|
|
21
|
+
'Other',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const postReasonOptions = [
|
|
25
|
+
'Spam or misleading',
|
|
26
|
+
'Hate speech or discrimination',
|
|
27
|
+
'Violence or threats',
|
|
28
|
+
'Sexually explicit content',
|
|
29
|
+
'Harassment or bullying',
|
|
30
|
+
'Other',
|
|
31
|
+
];
|
|
32
|
+
|
|
16
33
|
const meta: Meta<ReportModalProps> = {
|
|
17
34
|
title: 'Modals/ReportModal',
|
|
18
35
|
component: ReportModal,
|
|
@@ -92,9 +109,18 @@ const Template: StoryFn<ReportModalProps> = (args) => {
|
|
|
92
109
|
);
|
|
93
110
|
};
|
|
94
111
|
|
|
95
|
-
const baseArgs: Pick<
|
|
112
|
+
const baseArgs: Pick<
|
|
113
|
+
ReportModalProps,
|
|
114
|
+
'visible' | 'type' | 'title' | 'userReasons' | 'postReasons' | 'inputPlaceholder' | 'cancelText' | 'submitText'
|
|
115
|
+
> = {
|
|
96
116
|
visible: true,
|
|
97
117
|
type: 'user',
|
|
118
|
+
title: 'Report User',
|
|
119
|
+
userReasons: userReasonOptions,
|
|
120
|
+
postReasons: postReasonOptions,
|
|
121
|
+
inputPlaceholder: 'Additional details (optional)',
|
|
122
|
+
cancelText: 'Cancel',
|
|
123
|
+
submitText: 'Send',
|
|
98
124
|
};
|
|
99
125
|
|
|
100
126
|
export const UserReport: StoryFn<ReportModalProps> = Template.bind({});
|
|
@@ -113,6 +139,7 @@ export const PostReport: StoryFn<ReportModalProps> = Template.bind({});
|
|
|
113
139
|
PostReport.args = {
|
|
114
140
|
...baseArgs,
|
|
115
141
|
type: 'post',
|
|
142
|
+
title: 'Report Post',
|
|
116
143
|
};
|
|
117
144
|
PostReport.parameters = {
|
|
118
145
|
docs: {
|
|
@@ -160,6 +187,7 @@ export const InteractivePlayground: StoryFn<ReportModalProps> = (args) => {
|
|
|
160
187
|
{...args}
|
|
161
188
|
visible={isVisible}
|
|
162
189
|
type={reportType}
|
|
190
|
+
title={reportType === 'user' ? 'Report User' : 'Report Post'}
|
|
163
191
|
onClose={() => {
|
|
164
192
|
closeLogger();
|
|
165
193
|
setIsVisible(false);
|
|
@@ -170,6 +198,11 @@ export const InteractivePlayground: StoryFn<ReportModalProps> = (args) => {
|
|
|
170
198
|
setIsVisible(false);
|
|
171
199
|
args.onSubmit(reason, details);
|
|
172
200
|
}}
|
|
201
|
+
userReasons={userReasonOptions}
|
|
202
|
+
postReasons={postReasonOptions}
|
|
203
|
+
inputPlaceholder={args.inputPlaceholder}
|
|
204
|
+
cancelText={args.cancelText}
|
|
205
|
+
submitText={args.submitText}
|
|
173
206
|
/>
|
|
174
207
|
</View>
|
|
175
208
|
);
|
|
@@ -16,26 +16,25 @@ type Props = {
|
|
|
16
16
|
onClose: () => void;
|
|
17
17
|
onSubmit: (reason: string, details: string) => void;
|
|
18
18
|
type: "user" | "post";
|
|
19
|
+
title: string;
|
|
20
|
+
userReasons: string[];
|
|
21
|
+
postReasons: string[];
|
|
22
|
+
inputPlaceholder: string;
|
|
23
|
+
cancelText: string;
|
|
24
|
+
submitText: string;
|
|
19
25
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'Violence or threats',
|
|
33
|
-
'Sexually explicit content',
|
|
34
|
-
'Harassment or bullying',
|
|
35
|
-
'Other',
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const ReportModal: React.FC<Props> = ({ visible, onClose, onSubmit, type }) => {
|
|
26
|
+
const ReportModal: React.FC<Props> = ({
|
|
27
|
+
visible,
|
|
28
|
+
onClose,
|
|
29
|
+
onSubmit,
|
|
30
|
+
type,
|
|
31
|
+
title,
|
|
32
|
+
userReasons,
|
|
33
|
+
postReasons,
|
|
34
|
+
inputPlaceholder,
|
|
35
|
+
cancelText,
|
|
36
|
+
submitText,
|
|
37
|
+
}) => {
|
|
39
38
|
const { theme } = useTheme();
|
|
40
39
|
const styles = getStyles({ theme });
|
|
41
40
|
|
|
@@ -50,7 +49,7 @@ const ReportModal: React.FC<Props> = ({ visible, onClose, onSubmit, type }) => {
|
|
|
50
49
|
onClose();
|
|
51
50
|
};
|
|
52
51
|
|
|
53
|
-
const reasons = type === "user" ?
|
|
52
|
+
const reasons = type === "user" ? userReasons : postReasons;
|
|
54
53
|
|
|
55
54
|
return (
|
|
56
55
|
<Modal
|
|
@@ -61,7 +60,7 @@ const ReportModal: React.FC<Props> = ({ visible, onClose, onSubmit, type }) => {
|
|
|
61
60
|
>
|
|
62
61
|
<View style={styles.overlay}>
|
|
63
62
|
<View style={styles.container}>
|
|
64
|
-
<Text style={styles.title}>
|
|
63
|
+
<Text style={styles.title}>{title}</Text>
|
|
65
64
|
<ScrollView style={styles.reasonsContainer}>
|
|
66
65
|
{reasons.map((reason) => (
|
|
67
66
|
<TouchableOpacity
|
|
@@ -86,7 +85,7 @@ const ReportModal: React.FC<Props> = ({ visible, onClose, onSubmit, type }) => {
|
|
|
86
85
|
|
|
87
86
|
<TextInput
|
|
88
87
|
style={styles.input}
|
|
89
|
-
placeholder=
|
|
88
|
+
placeholder={inputPlaceholder}
|
|
90
89
|
placeholderTextColor="#888"
|
|
91
90
|
value={details}
|
|
92
91
|
onChangeText={setDetails}
|
|
@@ -96,7 +95,7 @@ const ReportModal: React.FC<Props> = ({ visible, onClose, onSubmit, type }) => {
|
|
|
96
95
|
|
|
97
96
|
<View style={styles.actions}>
|
|
98
97
|
<TouchableOpacity style={styles.cancelBtn} onPress={onClose}>
|
|
99
|
-
<Text style={styles.cancelText}>
|
|
98
|
+
<Text style={styles.cancelText}>{cancelText}</Text>
|
|
100
99
|
</TouchableOpacity>
|
|
101
100
|
<TouchableOpacity
|
|
102
101
|
style={[
|
|
@@ -106,7 +105,7 @@ const ReportModal: React.FC<Props> = ({ visible, onClose, onSubmit, type }) => {
|
|
|
106
105
|
disabled={!selectedReason}
|
|
107
106
|
onPress={handleSend}
|
|
108
107
|
>
|
|
109
|
-
<Text style={styles.submitText}>
|
|
108
|
+
<Text style={styles.submitText}>{submitText}</Text>
|
|
110
109
|
</TouchableOpacity>
|
|
111
110
|
</View>
|
|
112
111
|
</View>
|