rn-vs-lb 1.0.64 → 1.0.66

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 (113) hide show
  1. package/components/Button/Button.stories.tsx +1 -1
  2. package/components/Button/DeleteAccountButton.stories.tsx +13 -1
  3. package/components/Button/DeleteAccountButton.tsx +28 -8
  4. package/components/Button/PostButton.stories.tsx +1 -1
  5. package/components/Button/TelegramFeedbackLink.stories.tsx +7 -1
  6. package/components/Button/TelegramFeedbackLink.tsx +12 -4
  7. package/components/Cards/BusinessIdeaCard.stories.tsx +73 -0
  8. package/components/Cards/BusinessIdeaCard.tsx +251 -0
  9. package/components/Cards/EventCard.stories.tsx +4 -1
  10. package/components/Cards/EventCard.tsx +9 -2
  11. package/components/Cards/PlaceCard.stories.tsx +1 -1
  12. package/components/Cards/index.tsx +1 -0
  13. package/components/Chat/ChatItem.stories.tsx +1 -1
  14. package/components/Chat/ChatTab/ChatTabs.stories.tsx +1 -1
  15. package/components/Chat/ChatTab/SubTabButton.stories.tsx +1 -1
  16. package/components/Chat/InputMessage.stories.tsx +12 -1
  17. package/components/Chat/InputMessage.tsx +5 -3
  18. package/components/Chat/MessageItem/MessageItem.stories.tsx +1 -1
  19. package/components/Chat/PinnedMessagesBar/PinnedMessagesBar.stories.tsx +1 -1
  20. package/components/Gallery/AiAgentGallery.stories.tsx +1 -1
  21. package/components/Gallery/ProfileMediaGallery.stories.tsx +66 -0
  22. package/components/Gallery/ProfileMediaGallery.tsx +239 -0
  23. package/components/Gallery/ProfileMediaNavigationItem.tsx +73 -0
  24. package/components/Gallery/ProfileSelfiesGallery.pure.tsx +97 -0
  25. package/components/Gallery/ProfileSelfiesGallery.stories.tsx +86 -0
  26. package/components/Gallery/index.ts +4 -1
  27. package/components/Header/HeaderDefault.stories.tsx +1 -1
  28. package/components/Header/HeaderEdit.stories.tsx +1 -1
  29. package/components/Header/HeaderHome.stories.tsx +1 -1
  30. package/components/Header/HeaderSwitcher.stories.tsx +1 -1
  31. package/components/Header/HeaderWithImg.stories.tsx +10 -1
  32. package/components/Header/HeaderWithImg.tsx +27 -7
  33. package/components/Modals/GalleryModal.stories.tsx +1 -1
  34. package/components/Modals/GuestAiChatModal.stories.tsx +12 -4
  35. package/components/Modals/GuestAiChatModal.tsx +9 -3
  36. package/components/Modals/ReportModal.stories.tsx +35 -2
  37. package/components/Modals/ReportModal.tsx +23 -24
  38. package/components/Poll/CommentItem.stories.tsx +1 -1
  39. package/components/Poll/PollCardList.stories.tsx +5 -1
  40. package/components/Poll/PollCardList.tsx +4 -2
  41. package/components/Posts/EventCardList.stories.tsx +1 -1
  42. package/components/Posts/PlaceCardList.stories.tsx +1 -1
  43. package/components/Prank/HeroPrankCard.stories.tsx +47 -0
  44. package/components/Prank/HeroPrankCard.tsx +114 -0
  45. package/components/Prank/HomelessPrankPage.stories.tsx +58 -0
  46. package/components/Prank/UploadPromptCard.stories.tsx +55 -0
  47. package/components/Prank/UploadPromptCard.tsx +130 -0
  48. package/components/Prank/index.ts +5 -0
  49. package/components/Profile/ModalProfilePhoto.stories.tsx +1 -1
  50. package/components/Profile/ProfileCard/ProfileCard.stories.tsx +1 -1
  51. package/components/Profile/ProfilePhotoBanner.stories.tsx +11 -2
  52. package/components/Profile/ProfilePhotoBanner.tsx +5 -3
  53. package/components/Profile/ProfilePhotoUpload/ProfilePhotoUpload.stories.tsx +1 -1
  54. package/components/Profile/ProfileSummary/ProfileSummary.stories.tsx +52 -0
  55. package/components/Profile/ProfileSummary/ProfileSummary.tsx +188 -0
  56. package/components/Profile/ProfileTabs/UserProfileTabs.stories.tsx +1 -1
  57. package/components/Profile/index.ts +2 -0
  58. package/components/Screens/AppScreens.stories.tsx +572 -0
  59. package/components/Screens/HomeScreen.stories.tsx +159 -0
  60. package/components/Screens/LibraryScreen.stories.tsx +97 -0
  61. package/components/Settings/AppSettingsScreen.stories.tsx +134 -0
  62. package/components/Settings/AppSettingsScreen.tsx +215 -0
  63. package/components/Settings/SettingsHeader.tsx +37 -0
  64. package/components/Settings/SettingsListItem.tsx +142 -0
  65. package/components/Settings/SettingsManageAccountButton.tsx +58 -0
  66. package/components/Settings/SettingsOptionList.tsx +82 -0
  67. package/components/Settings/SettingsProfileCard.tsx +58 -0
  68. package/components/Settings/SettingsScreen.stories.tsx +66 -0
  69. package/components/Settings/SettingsScreen.tsx +129 -0
  70. package/components/Settings/SettingsSection.tsx +56 -0
  71. package/components/Settings/SettingsToggleItem.tsx +70 -0
  72. package/components/Settings/index.ts +23 -0
  73. package/components/Specialist/Hero.stories.tsx +2 -1
  74. package/components/Specialist/Hero.tsx +3 -1
  75. package/components/Specialist/PortfolioCarousel.stories.tsx +1 -1
  76. package/components/Specialist/ServicesList.stories.tsx +2 -1
  77. package/components/Specialist/ServicesList.tsx +4 -3
  78. package/components/Tooltip/DangerTooltip.stories.tsx +1 -1
  79. package/components/Tooltip/InfoTooltip.stories.tsx +1 -1
  80. package/components/Tooltip/InfoTooltipBase.stories.tsx +1 -1
  81. package/components/Tooltip/SucceedTooltip.stories.tsx +1 -1
  82. package/components/Tooltip/WarningTooltip.stories.tsx +1 -1
  83. package/components/UI/DeletedState.stories.tsx +6 -1
  84. package/components/UI/DeletedState.tsx +12 -3
  85. package/components/UI/DescriptionMore.stories.tsx +2 -0
  86. package/components/UI/DescriptionMore.tsx +13 -3
  87. package/components/UI/EmptyState.stories.tsx +4 -1
  88. package/components/UI/EmptyState.tsx +3 -2
  89. package/components/UI/HorizontalCardSection.stories.tsx +85 -0
  90. package/components/UI/HorizontalCardSection.tsx +199 -0
  91. package/components/UI/NoAuth.stories.tsx +3 -0
  92. package/components/UI/NoAuth.tsx +7 -6
  93. package/components/UI/ParticipantItem.stories.tsx +21 -0
  94. package/components/UI/ParticipantItem.tsx +30 -6
  95. package/components/UI/TabBar/BottomTabBar.stories.tsx +87 -0
  96. package/components/UI/TabBar/BottomTabBar.tsx +128 -0
  97. package/components/UI/ThemeSwitcher.stories.tsx +5 -1
  98. package/components/UI/ThemeSwitcher.tsx +7 -2
  99. package/components/UI/UpdateRequiredView.stories.tsx +2 -0
  100. package/components/UI/UpdateRequiredView.tsx +3 -2
  101. package/components/UI/index.ts +4 -0
  102. package/components/UserCards/AiAgentHeroCard.stories.tsx +66 -0
  103. package/components/UserCards/AiAgentHeroCard.tsx +242 -0
  104. package/components/UserCards/Organazer.tsx +3 -2
  105. package/components/UserCards/Organizer.stories.tsx +5 -1
  106. package/components/UserCards/SpecialistCard.stories.tsx +3 -1
  107. package/components/UserCards/SpecialistCard.tsx +3 -2
  108. package/components/UserCards/StoryCard.stories.tsx +1 -1
  109. package/components/UserCards/UserProfileCard.stories.tsx +1 -1
  110. package/components/UserCards/UserRow.stories.tsx +1 -1
  111. package/components/UserCards/index.ts +2 -0
  112. package/components/index.ts +2 -0
  113. package/package.json +2 -1
@@ -1 +1,4 @@
1
- export * from "./AiAgentGallery.pure"
1
+ export * from "./AiAgentGallery.pure";
2
+ export * from "./ProfileMediaGallery";
3
+ export * from "./ProfileMediaNavigationItem";
4
+ export * from "./ProfileSelfiesGallery.pure";
@@ -4,7 +4,7 @@ import { Text, TouchableOpacity, View } from 'react-native';
4
4
  import HeaderDefault, { AccessType } from './HeaderDefault';
5
5
 
6
6
  const meta: Meta = {
7
- title: 'Header/HeaderDefault',
7
+ title: 'Layout/Header/HeaderDefault',
8
8
  component: HeaderDefault,
9
9
  args: {
10
10
  title: 'Community event',
@@ -4,7 +4,7 @@ import { View } from 'react-native';
4
4
  import { HeaderEdit } from './HeaderEdit';
5
5
 
6
6
  const meta: Meta = {
7
- title: 'Header/HeaderEdit',
7
+ title: 'Layout/Header/HeaderEdit',
8
8
  component: HeaderEdit,
9
9
  args: {
10
10
  isPinned: false,
@@ -4,7 +4,7 @@ import { Text, View } from 'react-native';
4
4
  import { HeaderHome } from './HeaderHome';
5
5
 
6
6
  const meta: Meta<typeof HeaderHome> = {
7
- title: 'Header/HeaderHome',
7
+ title: 'Layout/Header/HeaderHome',
8
8
  component: HeaderHome,
9
9
  argTypes: {
10
10
  onPress: { action: 'header press' },
@@ -4,7 +4,7 @@ import { View, Text } from 'react-native';
4
4
  import { HeaderSwitcher } from './HeaderSwitcher';
5
5
 
6
6
  const meta: Meta<typeof HeaderSwitcher> = {
7
- title: 'Header/HeaderSwitcher',
7
+ title: 'Layout/Header/HeaderSwitcher',
8
8
  component: HeaderSwitcher,
9
9
  args: {
10
10
  isFirst: true,
@@ -4,7 +4,7 @@ import { View } from 'react-native';
4
4
  import HeaderWithImg from './HeaderWithImg';
5
5
 
6
6
  const meta: Meta = {
7
- title: 'Header/HeaderWithImg',
7
+ title: 'Layout/Header/HeaderWithImg',
8
8
  component: HeaderWithImg,
9
9
  args: {
10
10
  imgUrl: 'https://placekitten.com/200/200',
@@ -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} />
@@ -10,7 +10,7 @@ const IMAGES = [
10
10
  ];
11
11
 
12
12
  const meta = {
13
- title: 'Modals/GalleryModal',
13
+ title: 'Features/Modals/GalleryModal',
14
14
  component: GalleryModal,
15
15
  decorators: [(Story) => <View style={{ flex: 1 }}><Story /></View>],
16
16
  argTypes: { onRequestClose: { action: 'onRequestClose' } },
@@ -21,8 +21,10 @@ const sampleMessages: PureChatMessage[] = [
21
21
  },
22
22
  ];
23
23
 
24
+ const defaultLimitLabel = (remaining: number, limit: number) => `Осталось сообщений: ${remaining} / ${limit}`;
25
+
24
26
  const meta = {
25
- title: 'Modals/GuestAiChatModal',
27
+ title: 'Features/Modals/GuestAiChatModal',
26
28
  component: GuestAiChatModalView,
27
29
  argTypes: {
28
30
  onClose: { action: 'close modal' },
@@ -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,8 +13,25 @@ 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
- title: 'Modals/ReportModal',
34
+ title: 'Features/Modals/ReportModal',
18
35
  component: ReportModal,
19
36
  argTypes: {
20
37
  type: {
@@ -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>
@@ -46,7 +46,7 @@ const generateComments = (count: number): Comment[] =>
46
46
  }));
47
47
 
48
48
  const meta: Meta<Props> = {
49
- title: 'Poll/CommentItem',
49
+ title: 'Features/Poll/CommentItem',
50
50
  component: CommentItem,
51
51
  decorators: [
52
52
  (Story) => (
@@ -4,7 +4,7 @@ import { View } from 'react-native';
4
4
  import PollCardList from './PollCardList';
5
5
 
6
6
  const meta: Meta<React.ComponentProps<typeof PollCardList>> = {
7
- title: 'Poll/PollCardList',
7
+ title: 'Features/Poll/PollCardList',
8
8
  component: PollCardList,
9
9
  decorators: [
10
10
  (Story) => (
@@ -29,6 +29,7 @@ Default.args = {
29
29
  creatorAvatar: 'https://i.pravatar.cc/150?img=11',
30
30
  votesCount: 42,
31
31
  createdAt: '2 days ago',
32
+ votesLabel: 'votes',
32
33
  };
33
34
 
34
35
  export const WithLongQuestion = Template.bind({});
@@ -39,6 +40,7 @@ WithLongQuestion.args = {
39
40
  creatorAvatar: 'https://i.pravatar.cc/150?img=5',
40
41
  votesCount: 128,
41
42
  createdAt: '5 hours ago',
43
+ votesLabel: 'votes',
42
44
  };
43
45
 
44
46
  export const WithoutDate = Template.bind({});
@@ -48,6 +50,7 @@ WithoutDate.args = {
48
50
  creatorAvatar: 'https://i.pravatar.cc/150?img=24',
49
51
  votesCount: 8,
50
52
  createdAt: null,
53
+ votesLabel: 'votes',
51
54
  };
52
55
 
53
56
  export const MinimalInfo = Template.bind({});
@@ -57,4 +60,5 @@ MinimalInfo.args = {
57
60
  creatorAvatar: 'https://i.pravatar.cc/150?img=36',
58
61
  votesCount: 230,
59
62
  createdAt: 'just now',
63
+ votesLabel: 'votes',
60
64
  };
@@ -9,6 +9,7 @@ interface PollCardProps {
9
9
  votesCount: number;
10
10
  onPress?: () => void;
11
11
  createdAt: string | null;
12
+ votesLabel: string;
12
13
  }
13
14
 
14
15
  const PollCardList: React.FC<PollCardProps> = ({
@@ -17,7 +18,8 @@ const PollCardList: React.FC<PollCardProps> = ({
17
18
  creatorAvatar,
18
19
  votesCount,
19
20
  onPress,
20
- createdAt
21
+ createdAt,
22
+ votesLabel,
21
23
  }) => {
22
24
  const { globalStyleSheet, commonStyles, typography } = useTheme();
23
25
  const styles = getStyles({ commonStyles });
@@ -33,7 +35,7 @@ const PollCardList: React.FC<PollCardProps> = ({
33
35
  </View>
34
36
  <Text style={typography.bodyXs}>{creatorName}</Text>
35
37
  <View style={globalStyleSheet.flexRowCenterBetween}>
36
- <Text style={typography.bodyXs}>{votesCount} votes</Text>
38
+ <Text style={typography.bodyXs}>{votesCount} {votesLabel}</Text>
37
39
  <Text style={typography.bodyXs}>{createdAt}</Text>
38
40
  </View>
39
41
  </View>
@@ -7,7 +7,7 @@ import EventCardList from './EventCardList';
7
7
  type Props = React.ComponentProps<typeof EventCardList>;
8
8
 
9
9
  const meta: Meta<Props> = {
10
- title: 'Posts/EventCardList',
10
+ title: 'Features/Posts/EventCardList',
11
11
  component: EventCardList,
12
12
  decorators: [
13
13
  (Story) => (
@@ -4,7 +4,7 @@ import { View } from 'react-native';
4
4
  import PlaceCardList from './PlaceCardList';
5
5
 
6
6
  const meta: Meta<React.ComponentProps<typeof PlaceCardList>> = {
7
- title: 'Posts/PlaceCardList',
7
+ title: 'Features/Posts/PlaceCardList',
8
8
  component: PlaceCardList,
9
9
  decorators: [
10
10
  (Story) => (
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ import { ThemeProvider } from '../../theme';
6
+ import HeroPrankCard from './HeroPrankCard';
7
+
8
+ const meta: Meta<typeof HeroPrankCard> = {
9
+ title: 'Features/Prank/HeroPrankCard',
10
+ component: HeroPrankCard,
11
+ decorators: [
12
+ (Story) => (
13
+ <ThemeProvider>
14
+ <View style={{ padding: 24, backgroundColor: '#070C1F', flex: 1 }}>
15
+ <Story />
16
+ </View>
17
+ </ThemeProvider>
18
+ ),
19
+ ],
20
+ args: {
21
+ imageUri:
22
+ 'https://images.unsplash.com/photo-1621447462558-42b4d8bc5d9b?auto=format&fit=crop&w=900&q=80',
23
+ title: 'Homeless Prank',
24
+ description: 'Prank your loved ones with a unknown guest in your home!',
25
+ },
26
+ parameters: {
27
+ backgrounds: {
28
+ default: 'dark',
29
+ values: [
30
+ { name: 'dark', value: '#070C1F' },
31
+ { name: 'light', value: '#f5f5f5' },
32
+ ],
33
+ },
34
+ },
35
+ };
36
+
37
+ export default meta;
38
+
39
+ type Story = StoryObj<typeof HeroPrankCard>;
40
+
41
+ export const Default: Story = {};
42
+
43
+ export const WithAction: Story = {
44
+ args: {
45
+ onPress: () => console.log('Hero card pressed'),
46
+ },
47
+ };
@@ -0,0 +1,114 @@
1
+ import React from 'react';
2
+ import {
3
+ ImageBackground,
4
+ StyleProp,
5
+ StyleSheet,
6
+ Text,
7
+ TouchableOpacity,
8
+ View,
9
+ ViewStyle,
10
+ } from 'react-native';
11
+ import { LinearGradient } from 'expo-linear-gradient';
12
+
13
+ import { ThemeType, SizesType, useTheme } from '../../theme';
14
+
15
+ export interface HeroPrankCardProps {
16
+ imageUri: string;
17
+ title: string;
18
+ description: string;
19
+ onPress?: () => void;
20
+ style?: StyleProp<ViewStyle>;
21
+ testID?: string;
22
+ }
23
+
24
+ const HeroPrankCard: React.FC<HeroPrankCardProps> = ({
25
+ imageUri,
26
+ title,
27
+ description,
28
+ onPress,
29
+ style,
30
+ testID,
31
+ }) => {
32
+ const { theme, sizes } = useTheme();
33
+ const styles = React.useMemo(() => getStyles({ theme, sizes }), [theme, sizes]);
34
+
35
+ const content = (
36
+ <ImageBackground
37
+ source={{ uri: imageUri }}
38
+ style={styles.imageBackground}
39
+ imageStyle={styles.image}
40
+ accessibilityLabel={title}
41
+ >
42
+ <LinearGradient
43
+ colors={['rgba(0,0,0,0.05)', 'rgba(0,0,0,0.7)', 'rgba(0,0,0,0.95)']}
44
+ style={StyleSheet.absoluteFillObject}
45
+ />
46
+ <View style={styles.textContainer}>
47
+ <Text style={styles.title} numberOfLines={2}>
48
+ {title}
49
+ </Text>
50
+ <Text style={styles.description} numberOfLines={2}>
51
+ {description}
52
+ </Text>
53
+ </View>
54
+ </ImageBackground>
55
+ );
56
+
57
+ if (onPress) {
58
+ return (
59
+ <TouchableOpacity
60
+ activeOpacity={0.85}
61
+ onPress={onPress}
62
+ style={[styles.container, style]}
63
+ testID={testID}
64
+ >
65
+ {content}
66
+ </TouchableOpacity>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <View style={[styles.container, style]} testID={testID}>
72
+ {content}
73
+ </View>
74
+ );
75
+ };
76
+
77
+ interface StyleParams {
78
+ theme: ThemeType;
79
+ sizes: SizesType;
80
+ }
81
+
82
+ const getStyles = ({ theme, sizes }: StyleParams) =>
83
+ StyleSheet.create({
84
+ container: {
85
+ borderRadius: sizes.radius_lg,
86
+ overflow: 'hidden',
87
+ backgroundColor: theme.black,
88
+ },
89
+ imageBackground: {
90
+ height: 320,
91
+ justifyContent: 'flex-end',
92
+ },
93
+ image: {
94
+ borderRadius: sizes.radius_lg,
95
+ },
96
+ textContainer: {
97
+ paddingHorizontal: sizes.lg,
98
+ paddingVertical: sizes.xl,
99
+ },
100
+ title: {
101
+ fontSize: sizes.h4,
102
+ lineHeight: 30,
103
+ fontWeight: '700',
104
+ color: theme.white,
105
+ },
106
+ description: {
107
+ marginTop: sizes.xs,
108
+ fontSize: sizes.font,
109
+ lineHeight: 22,
110
+ color: 'rgba(255,255,255,0.82)',
111
+ },
112
+ });
113
+
114
+ export default HeroPrankCard;