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.
Files changed (42) hide show
  1. package/components/Button/DeleteAccountButton.stories.tsx +12 -0
  2. package/components/Button/DeleteAccountButton.tsx +28 -8
  3. package/components/Button/TelegramFeedbackLink.stories.tsx +6 -0
  4. package/components/Button/TelegramFeedbackLink.tsx +12 -4
  5. package/components/Cards/EventCard.stories.tsx +3 -0
  6. package/components/Cards/EventCard.tsx +9 -2
  7. package/components/Chat/InputMessage.stories.tsx +11 -0
  8. package/components/Chat/InputMessage.tsx +5 -3
  9. package/components/Header/HeaderWithImg.stories.tsx +9 -0
  10. package/components/Header/HeaderWithImg.tsx +27 -7
  11. package/components/Modals/GuestAiChatModal.stories.tsx +11 -3
  12. package/components/Modals/GuestAiChatModal.tsx +9 -3
  13. package/components/Modals/ReportModal.stories.tsx +34 -1
  14. package/components/Modals/ReportModal.tsx +23 -24
  15. package/components/Poll/PollCardList.stories.tsx +4 -0
  16. package/components/Poll/PollCardList.tsx +4 -2
  17. package/components/Profile/ProfilePhotoBanner.stories.tsx +10 -1
  18. package/components/Profile/ProfilePhotoBanner.tsx +5 -3
  19. package/components/Specialist/Hero.stories.tsx +1 -0
  20. package/components/Specialist/Hero.tsx +3 -1
  21. package/components/Specialist/ServicesList.stories.tsx +1 -0
  22. package/components/Specialist/ServicesList.tsx +4 -3
  23. package/components/UI/DeletedState.stories.tsx +6 -1
  24. package/components/UI/DeletedState.tsx +12 -3
  25. package/components/UI/DescriptionMore.stories.tsx +2 -0
  26. package/components/UI/DescriptionMore.tsx +13 -3
  27. package/components/UI/EmptyState.stories.tsx +4 -1
  28. package/components/UI/EmptyState.tsx +3 -2
  29. package/components/UI/NoAuth.stories.tsx +3 -0
  30. package/components/UI/NoAuth.tsx +7 -6
  31. package/components/UI/ParticipantItem.stories.tsx +21 -0
  32. package/components/UI/ParticipantItem.tsx +30 -6
  33. package/components/UI/StepProgress.tsx +1 -1
  34. package/components/UI/ThemeSwitcher.stories.tsx +5 -1
  35. package/components/UI/ThemeSwitcher.tsx +7 -2
  36. package/components/UI/UpdateRequiredView.stories.tsx +2 -0
  37. package/components/UI/UpdateRequiredView.tsx +3 -2
  38. package/components/UserCards/Organazer.tsx +3 -2
  39. package/components/UserCards/Organizer.stories.tsx +4 -0
  40. package/components/UserCards/SpecialistCard.stories.tsx +2 -0
  41. package/components/UserCards/SpecialistCard.tsx +3 -2
  42. 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> = ({ deleteAccount }) => {
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 }]}>Delete account</Text>
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}>Confirm Deletion</Text>
47
+ <Text style={typography.titleH5}>{modalTitle}</Text>
36
48
  <Spacer size="xxs" />
37
49
  <Text style={[typography.body, { textAlign: "center" }]}>
38
- All your data, including profile, events, and chat history, will be permanently deleted.
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 onPress={() => setModalVisible(false)} style={styles.cancelButton} type="gray-outline" title="Cancel" />
45
- <Button onPress={handleDeleteAccount} type="report-outline" style={styles.confirmButton} title="Delete" />
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> = ({ link }) => {
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("Can't open Telegram URL");
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 }]}>Feedback & Bugs</Text>
28
- <Text style={typography.bodySm}>Tap to write us in Telegram</Text>
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}>You’re participating</Text>
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]}>DEMO</Text>
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?: string;
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 = 'Message',
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}>Editing message</Text>
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="Back">
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="Open chat image"
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]}>typing…</Text>
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 ? 'Online' : 'Offline'}
94
+ {isOnline ? onlineText : offlineText}
77
95
  </Text>
78
96
  <View
79
97
  style={[styles.statusDot, isOnline ? styles.online : styles.offline]}
80
- accessibilityLabel={isOnline ? 'online' : 'offline'}
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}: typing…
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="More actions"
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> = (args) => Template({ ...args, visible: true });
178
- DefaultOpen.args = {
179
- visible: true,
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 || 'AI-бот'}</Text>
101
+ <Text style={typography.titleH6}>{botName || defaultBotName}</Text>
96
102
  {limit !== undefined && remaining !== undefined && (
97
103
  <Text style={styles.limitText}>
98
- Осталось сообщений: {remaining} / {limit}
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<ReportModalProps, 'visible' | 'type'> = {
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
- const userReportReasons = [
22
- 'Spam or scam',
23
- 'Harassment or bullying',
24
- 'Inappropriate content',
25
- 'Fake profile',
26
- 'Other',
27
- ];
28
-
29
- const postReportReasons = [
30
- 'Spam or misleading',
31
- 'Hate speech or discrimination',
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" ? userReportReasons : postReportReasons
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}>Report User</Text>
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="Additional details (optional)"
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}>Cancel</Text>
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}>Send</Text>
108
+ <Text style={styles.submitText}>{submitText}</Text>
110
109
  </TouchableOpacity>
111
110
  </View>
112
111
  </View>