rn-vs-lb 1.0.65 → 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.
- package/components/Button/Button.stories.tsx +1 -1
- package/components/Button/DeleteAccountButton.stories.tsx +1 -1
- package/components/Button/PostButton.stories.tsx +1 -1
- package/components/Button/TelegramFeedbackLink.stories.tsx +1 -1
- package/components/Cards/BusinessIdeaCard.stories.tsx +73 -0
- package/components/Cards/BusinessIdeaCard.tsx +251 -0
- package/components/Cards/EventCard.stories.tsx +1 -1
- package/components/Cards/PlaceCard.stories.tsx +1 -1
- package/components/Cards/index.tsx +1 -0
- package/components/Chat/ChatItem.stories.tsx +1 -1
- package/components/Chat/ChatTab/ChatTabs.stories.tsx +1 -1
- package/components/Chat/ChatTab/SubTabButton.stories.tsx +1 -1
- package/components/Chat/InputMessage.stories.tsx +1 -1
- package/components/Chat/MessageItem/MessageItem.stories.tsx +1 -1
- package/components/Chat/PinnedMessagesBar/PinnedMessagesBar.stories.tsx +1 -1
- package/components/Gallery/AiAgentGallery.stories.tsx +1 -1
- package/components/Gallery/ProfileMediaGallery.stories.tsx +66 -0
- package/components/Gallery/ProfileMediaGallery.tsx +239 -0
- package/components/Gallery/ProfileMediaNavigationItem.tsx +73 -0
- package/components/Gallery/ProfileSelfiesGallery.pure.tsx +97 -0
- package/components/Gallery/ProfileSelfiesGallery.stories.tsx +86 -0
- package/components/Gallery/index.ts +4 -1
- package/components/Header/HeaderDefault.stories.tsx +1 -1
- package/components/Header/HeaderEdit.stories.tsx +1 -1
- package/components/Header/HeaderHome.stories.tsx +1 -1
- package/components/Header/HeaderSwitcher.stories.tsx +1 -1
- package/components/Header/HeaderWithImg.stories.tsx +1 -1
- package/components/Modals/GalleryModal.stories.tsx +1 -1
- package/components/Modals/GuestAiChatModal.stories.tsx +1 -1
- package/components/Modals/ReportModal.stories.tsx +1 -1
- package/components/Poll/CommentItem.stories.tsx +1 -1
- package/components/Poll/PollCardList.stories.tsx +1 -1
- package/components/Posts/EventCardList.stories.tsx +1 -1
- package/components/Posts/PlaceCardList.stories.tsx +1 -1
- package/components/Prank/HeroPrankCard.stories.tsx +47 -0
- package/components/Prank/HeroPrankCard.tsx +114 -0
- package/components/Prank/HomelessPrankPage.stories.tsx +58 -0
- package/components/Prank/UploadPromptCard.stories.tsx +55 -0
- package/components/Prank/UploadPromptCard.tsx +130 -0
- package/components/Prank/index.ts +5 -0
- package/components/Profile/ModalProfilePhoto.stories.tsx +1 -1
- package/components/Profile/ProfileCard/ProfileCard.stories.tsx +1 -1
- package/components/Profile/ProfilePhotoBanner.stories.tsx +1 -1
- package/components/Profile/ProfilePhotoUpload/ProfilePhotoUpload.stories.tsx +1 -1
- package/components/Profile/ProfileSummary/ProfileSummary.stories.tsx +52 -0
- package/components/Profile/ProfileSummary/ProfileSummary.tsx +188 -0
- package/components/Profile/ProfileTabs/UserProfileTabs.stories.tsx +1 -1
- package/components/Profile/index.ts +2 -0
- package/components/Screens/AppScreens.stories.tsx +572 -0
- package/components/Screens/HomeScreen.stories.tsx +159 -0
- package/components/Screens/LibraryScreen.stories.tsx +97 -0
- package/components/Settings/AppSettingsScreen.stories.tsx +134 -0
- package/components/Settings/AppSettingsScreen.tsx +215 -0
- package/components/Settings/SettingsHeader.tsx +37 -0
- package/components/Settings/SettingsListItem.tsx +142 -0
- package/components/Settings/SettingsManageAccountButton.tsx +58 -0
- package/components/Settings/SettingsOptionList.tsx +82 -0
- package/components/Settings/SettingsProfileCard.tsx +58 -0
- package/components/Settings/SettingsScreen.stories.tsx +66 -0
- package/components/Settings/SettingsScreen.tsx +129 -0
- package/components/Settings/SettingsSection.tsx +56 -0
- package/components/Settings/SettingsToggleItem.tsx +70 -0
- package/components/Settings/index.ts +23 -0
- package/components/Specialist/Hero.stories.tsx +1 -1
- package/components/Specialist/PortfolioCarousel.stories.tsx +1 -1
- package/components/Specialist/ServicesList.stories.tsx +1 -1
- package/components/Tooltip/DangerTooltip.stories.tsx +1 -1
- package/components/Tooltip/InfoTooltip.stories.tsx +1 -1
- package/components/Tooltip/InfoTooltipBase.stories.tsx +1 -1
- package/components/Tooltip/SucceedTooltip.stories.tsx +1 -1
- package/components/Tooltip/WarningTooltip.stories.tsx +1 -1
- package/components/UI/HorizontalCardSection.stories.tsx +85 -0
- package/components/UI/HorizontalCardSection.tsx +199 -0
- package/components/UI/TabBar/BottomTabBar.stories.tsx +87 -0
- package/components/UI/TabBar/BottomTabBar.tsx +128 -0
- package/components/UI/index.ts +4 -0
- package/components/UserCards/AiAgentHeroCard.stories.tsx +66 -0
- package/components/UserCards/AiAgentHeroCard.tsx +242 -0
- package/components/UserCards/Organizer.stories.tsx +1 -1
- package/components/UserCards/SpecialistCard.stories.tsx +1 -1
- package/components/UserCards/StoryCard.stories.tsx +1 -1
- package/components/UserCards/UserProfileCard.stories.tsx +1 -1
- package/components/UserCards/UserRow.stories.tsx +1 -1
- package/components/UserCards/index.ts +2 -0
- package/components/index.ts +2 -0
- package/package.json +2 -1
|
@@ -11,7 +11,7 @@ const IMAGES = [
|
|
|
11
11
|
];
|
|
12
12
|
|
|
13
13
|
const meta = {
|
|
14
|
-
title: 'Specialist/PortfolioCarousel',
|
|
14
|
+
title: 'Features/Specialist/PortfolioCarousel',
|
|
15
15
|
component: PortfolioCarousel,
|
|
16
16
|
decorators: [(Story) => <View style={{ paddingVertical: 12 }}><Story /></View>],
|
|
17
17
|
argTypes: { onPressImage: { action: 'onPressImage' } },
|
|
@@ -30,7 +30,7 @@ const SERVICES: ServiceItem[] = [
|
|
|
30
30
|
];
|
|
31
31
|
|
|
32
32
|
const meta = {
|
|
33
|
-
title: 'Specialist/ServicesList',
|
|
33
|
+
title: 'Features/Specialist/ServicesList',
|
|
34
34
|
component: ServicesList,
|
|
35
35
|
decorators: [(Story) => <View style={{ paddingVertical: 12 }}><Story /></View>],
|
|
36
36
|
argTypes: {
|
|
@@ -39,7 +39,7 @@ const renderTooltip = ({ contentKey, ...args }: DangerTooltipStoryProps) => (
|
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
const meta: Meta<DangerTooltipStoryProps> = {
|
|
42
|
-
title: 'Tooltip/DangerTooltip',
|
|
42
|
+
title: 'UI/Tooltip/DangerTooltip',
|
|
43
43
|
component: DangerTooltip,
|
|
44
44
|
argTypes: {
|
|
45
45
|
contentKey: {
|
|
@@ -34,7 +34,7 @@ const renderTooltip = ({ contentKey, ...args }: InfoTooltipBaseStoryProps) => (
|
|
|
34
34
|
);
|
|
35
35
|
|
|
36
36
|
const meta: Meta<InfoTooltipBaseStoryProps> = {
|
|
37
|
-
title: 'Tooltip/InfoTooltipBase',
|
|
37
|
+
title: 'UI/Tooltip/InfoTooltipBase',
|
|
38
38
|
component: InfoTooltipBase,
|
|
39
39
|
argTypes: {
|
|
40
40
|
contentKey: {
|
|
@@ -39,7 +39,7 @@ const renderTooltip = ({ contentKey, ...args }: SucceedTooltipStoryProps) => (
|
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
const meta: Meta<SucceedTooltipStoryProps> = {
|
|
42
|
-
title: 'Tooltip/SucceedTooltip',
|
|
42
|
+
title: 'UI/Tooltip/SucceedTooltip',
|
|
43
43
|
component: SucceedTooltip,
|
|
44
44
|
argTypes: {
|
|
45
45
|
contentKey: {
|
|
@@ -41,7 +41,7 @@ const renderTooltip = ({ contentKey = 'pendingReview', ...args }: WarningTooltip
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
const meta: Meta<WarningTooltipStoryProps> = {
|
|
44
|
-
title: 'Tooltip/WarningTooltip',
|
|
44
|
+
title: 'UI/Tooltip/WarningTooltip',
|
|
45
45
|
component: WarningTooltip,
|
|
46
46
|
argTypes: {
|
|
47
47
|
contentKey: {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
|
|
4
|
+
import { ThemeProvider } from "../../theme";
|
|
5
|
+
import HorizontalCardSection, { HorizontalCard } from "./HorizontalCardSection";
|
|
6
|
+
|
|
7
|
+
const cards: HorizontalCard[] = [
|
|
8
|
+
{
|
|
9
|
+
id: "1",
|
|
10
|
+
title: "AI Agents Meetup",
|
|
11
|
+
image: {
|
|
12
|
+
uri: "https://images.unsplash.com/photo-1521737604893-d14cc237f11d?auto=format&fit=crop&w=600&q=80",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "2",
|
|
17
|
+
title: "Design Sprint Workshop",
|
|
18
|
+
image: {
|
|
19
|
+
uri: "https://images.unsplash.com/photo-1521737604893-0f34a1f438b8?auto=format&fit=crop&w=600&q=80",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "3",
|
|
24
|
+
title: "Community Hack Night",
|
|
25
|
+
image: {
|
|
26
|
+
uri: "https://images.unsplash.com/photo-1521737604893-9d53e79e4d4a?auto=format&fit=crop&w=600&q=80",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const meta: Meta<typeof HorizontalCardSection> = {
|
|
32
|
+
title: "UI/Cards/HorizontalCardSection",
|
|
33
|
+
component: HorizontalCardSection,
|
|
34
|
+
decorators: [
|
|
35
|
+
(Story) => (
|
|
36
|
+
<ThemeProvider>
|
|
37
|
+
<Story />
|
|
38
|
+
</ThemeProvider>
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
argTypes: {
|
|
42
|
+
onPressSeeAll: { action: "see all press" },
|
|
43
|
+
onPressCard: { action: "card press" },
|
|
44
|
+
isDark: { control: "boolean" },
|
|
45
|
+
},
|
|
46
|
+
parameters: {
|
|
47
|
+
backgrounds: {
|
|
48
|
+
default: "light",
|
|
49
|
+
values: [
|
|
50
|
+
{ name: "light", value: "#f4f4f6" },
|
|
51
|
+
{ name: "dark", value: "#070C1F" },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default meta;
|
|
58
|
+
|
|
59
|
+
type Story = StoryObj<typeof HorizontalCardSection>;
|
|
60
|
+
|
|
61
|
+
export const Default: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
title: "Popular this week",
|
|
64
|
+
cards,
|
|
65
|
+
seeAllLabel: "See all",
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const WithCustomLabel: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
...Default.args,
|
|
72
|
+
title: "Recommended spaces",
|
|
73
|
+
seeAllLabel: "View more",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const DarkModePreview: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
...Default.args,
|
|
80
|
+
isDark: true,
|
|
81
|
+
},
|
|
82
|
+
parameters: {
|
|
83
|
+
backgrounds: { default: "dark" },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import React, { memo, useCallback, useMemo } from "react";
|
|
2
|
+
import {
|
|
3
|
+
FlatList,
|
|
4
|
+
ImageBackground,
|
|
5
|
+
ImageSourcePropType,
|
|
6
|
+
ListRenderItemInfo,
|
|
7
|
+
StyleProp,
|
|
8
|
+
StyleSheet,
|
|
9
|
+
Text,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
View,
|
|
12
|
+
ViewStyle,
|
|
13
|
+
} from "react-native";
|
|
14
|
+
import { LinearGradient } from "expo-linear-gradient";
|
|
15
|
+
|
|
16
|
+
import { SizesType, ThemeType, useTheme } from "../../theme";
|
|
17
|
+
|
|
18
|
+
export type HorizontalCard = {
|
|
19
|
+
id?: string | number;
|
|
20
|
+
title: string;
|
|
21
|
+
image: ImageSourcePropType;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface HorizontalCardSectionProps {
|
|
25
|
+
title: string;
|
|
26
|
+
cards: HorizontalCard[];
|
|
27
|
+
onPressSeeAll?: () => void;
|
|
28
|
+
onPressCard?: (card: HorizontalCard, index: number) => void;
|
|
29
|
+
seeAllLabel?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Force dark styles regardless of the theme. Useful for previewing in storybook.
|
|
32
|
+
*/
|
|
33
|
+
isDark?: boolean;
|
|
34
|
+
style?: StyleProp<ViewStyle>;
|
|
35
|
+
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const CARD_WIDTH = 152;
|
|
39
|
+
const CARD_HEIGHT = 230;
|
|
40
|
+
|
|
41
|
+
const HorizontalCardSectionComponent: React.FC<HorizontalCardSectionProps> = ({
|
|
42
|
+
title,
|
|
43
|
+
cards,
|
|
44
|
+
onPressSeeAll,
|
|
45
|
+
onPressCard,
|
|
46
|
+
seeAllLabel = "See All",
|
|
47
|
+
isDark,
|
|
48
|
+
style,
|
|
49
|
+
contentContainerStyle,
|
|
50
|
+
}) => {
|
|
51
|
+
const { theme, typography, sizes, isDark: themeIsDark } = useTheme();
|
|
52
|
+
const resolvedIsDark = isDark ?? themeIsDark;
|
|
53
|
+
|
|
54
|
+
const styles = useMemo(
|
|
55
|
+
() => getStyles({ theme, sizes, isDark: resolvedIsDark }),
|
|
56
|
+
[resolvedIsDark, sizes, theme],
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const gradientColors = useMemo(
|
|
60
|
+
() => ["rgba(0,0,0,0)", resolvedIsDark ? "rgba(7,12,31,0.85)" : "rgba(0,0,0,0.75)"],
|
|
61
|
+
[resolvedIsDark],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const handleCardPress = useCallback(
|
|
65
|
+
(card: HorizontalCard, index: number) => () => {
|
|
66
|
+
if (onPressCard) {
|
|
67
|
+
onPressCard(card, index);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
[onPressCard],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const renderCard = useCallback(
|
|
74
|
+
({ item, index }: ListRenderItemInfo<HorizontalCard>) => (
|
|
75
|
+
<TouchableOpacity
|
|
76
|
+
accessibilityLabel={item.title}
|
|
77
|
+
accessibilityRole={onPressCard ? "button" : undefined}
|
|
78
|
+
activeOpacity={0.85}
|
|
79
|
+
disabled={!onPressCard}
|
|
80
|
+
onPress={handleCardPress(item, index)}
|
|
81
|
+
style={styles.cardWrapper}
|
|
82
|
+
>
|
|
83
|
+
<ImageBackground source={item.image} style={styles.card} imageStyle={styles.cardImage}>
|
|
84
|
+
<LinearGradient colors={gradientColors} style={styles.gradient} />
|
|
85
|
+
<Text style={[typography.titleH6, styles.cardTitle]} numberOfLines={2}>
|
|
86
|
+
{item.title}
|
|
87
|
+
</Text>
|
|
88
|
+
</ImageBackground>
|
|
89
|
+
</TouchableOpacity>
|
|
90
|
+
),
|
|
91
|
+
[gradientColors, handleCardPress, onPressCard, styles, typography.titleH6],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const keyExtractor = useCallback(
|
|
95
|
+
(item: HorizontalCard, index: number) => `${item.id ?? index}`,
|
|
96
|
+
[],
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const listFooter = useMemo(
|
|
100
|
+
() => (cards.length > 0 ? <View style={styles.footerSpacer} /> : null),
|
|
101
|
+
[cards.length, styles.footerSpacer],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<View style={[styles.container, style]}>
|
|
106
|
+
<View style={styles.header}>
|
|
107
|
+
<Text style={[typography.titleH4, styles.title]} numberOfLines={1}>
|
|
108
|
+
{title}
|
|
109
|
+
</Text>
|
|
110
|
+
|
|
111
|
+
{onPressSeeAll ? (
|
|
112
|
+
<TouchableOpacity
|
|
113
|
+
accessibilityRole="button"
|
|
114
|
+
activeOpacity={0.7}
|
|
115
|
+
onPress={onPressSeeAll}
|
|
116
|
+
style={styles.seeAllButton}
|
|
117
|
+
>
|
|
118
|
+
<Text style={[typography.bodySm, styles.seeAllText]}>{seeAllLabel}</Text>
|
|
119
|
+
</TouchableOpacity>
|
|
120
|
+
) : null}
|
|
121
|
+
</View>
|
|
122
|
+
|
|
123
|
+
<FlatList
|
|
124
|
+
horizontal
|
|
125
|
+
data={cards}
|
|
126
|
+
keyExtractor={keyExtractor}
|
|
127
|
+
renderItem={renderCard}
|
|
128
|
+
showsHorizontalScrollIndicator={false}
|
|
129
|
+
contentContainerStyle={[styles.listContent, contentContainerStyle]}
|
|
130
|
+
ListFooterComponent={listFooter}
|
|
131
|
+
/>
|
|
132
|
+
</View>
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getStyles = ({
|
|
137
|
+
theme,
|
|
138
|
+
sizes,
|
|
139
|
+
isDark,
|
|
140
|
+
}: {
|
|
141
|
+
theme: ThemeType;
|
|
142
|
+
sizes: SizesType;
|
|
143
|
+
isDark: boolean;
|
|
144
|
+
}) =>
|
|
145
|
+
StyleSheet.create({
|
|
146
|
+
container: {
|
|
147
|
+
gap: sizes.md,
|
|
148
|
+
},
|
|
149
|
+
header: {
|
|
150
|
+
flexDirection: "row",
|
|
151
|
+
alignItems: "center",
|
|
152
|
+
justifyContent: "space-between",
|
|
153
|
+
paddingHorizontal: sizes.xs,
|
|
154
|
+
},
|
|
155
|
+
title: {
|
|
156
|
+
color: theme.title,
|
|
157
|
+
},
|
|
158
|
+
seeAllButton: {
|
|
159
|
+
paddingVertical: sizes.xs,
|
|
160
|
+
paddingHorizontal: sizes.sm,
|
|
161
|
+
borderRadius: sizes.radius_sm,
|
|
162
|
+
},
|
|
163
|
+
seeAllText: {
|
|
164
|
+
color: isDark ? theme.white : theme.primary,
|
|
165
|
+
fontWeight: "500",
|
|
166
|
+
},
|
|
167
|
+
listContent: {
|
|
168
|
+
paddingHorizontal: sizes.xs,
|
|
169
|
+
},
|
|
170
|
+
cardWrapper: {
|
|
171
|
+
marginRight: sizes.md,
|
|
172
|
+
},
|
|
173
|
+
card: {
|
|
174
|
+
width: CARD_WIDTH,
|
|
175
|
+
height: CARD_HEIGHT,
|
|
176
|
+
borderRadius: sizes.radius_lg,
|
|
177
|
+
overflow: "hidden",
|
|
178
|
+
justifyContent: "flex-end",
|
|
179
|
+
backgroundColor: theme.background,
|
|
180
|
+
},
|
|
181
|
+
cardImage: {
|
|
182
|
+
borderRadius: sizes.radius_lg,
|
|
183
|
+
},
|
|
184
|
+
gradient: {
|
|
185
|
+
...StyleSheet.absoluteFillObject,
|
|
186
|
+
},
|
|
187
|
+
cardTitle: {
|
|
188
|
+
color: theme.white,
|
|
189
|
+
paddingHorizontal: sizes.sm,
|
|
190
|
+
paddingBottom: sizes.sm,
|
|
191
|
+
},
|
|
192
|
+
footerSpacer: {
|
|
193
|
+
width: sizes.md,
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
export const HorizontalCardSection = memo(HorizontalCardSectionComponent);
|
|
198
|
+
|
|
199
|
+
export default HorizontalCardSection;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons';
|
|
5
|
+
import BottomTabBar, { BottomTabBarItem } from './BottomTabBar';
|
|
6
|
+
|
|
7
|
+
const ITEMS: BottomTabBarItem[] = [
|
|
8
|
+
{
|
|
9
|
+
key: 'overview',
|
|
10
|
+
renderIcon: ({ color, size }) => (
|
|
11
|
+
<MaterialCommunityIcons name="view-grid-outline" size={size} color={color} />
|
|
12
|
+
),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: 'private',
|
|
16
|
+
renderIcon: ({ color, size }) => <MaterialIcons name="lock-outline" size={size} color={color} />,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: 'bookmarks',
|
|
20
|
+
renderIcon: ({ color, size }) => (
|
|
21
|
+
<MaterialCommunityIcons name="bookmark-off-outline" size={size} color={color} />
|
|
22
|
+
),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
key: 'favorites',
|
|
26
|
+
renderIcon: ({ color, size }) => (
|
|
27
|
+
<MaterialCommunityIcons name="heart-off-outline" size={size} color={color} />
|
|
28
|
+
),
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const meta = {
|
|
33
|
+
title: 'UI/BottomTabBar',
|
|
34
|
+
component: BottomTabBar,
|
|
35
|
+
decorators: [(Story) => <View style={{ paddingTop: 24 }}><Story /></View>],
|
|
36
|
+
argTypes: {
|
|
37
|
+
onChange: { action: 'onChange' },
|
|
38
|
+
},
|
|
39
|
+
args: {
|
|
40
|
+
items: ITEMS,
|
|
41
|
+
},
|
|
42
|
+
} satisfies Meta<typeof BottomTabBar>;
|
|
43
|
+
|
|
44
|
+
export default meta;
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<typeof BottomTabBar>;
|
|
47
|
+
|
|
48
|
+
const Controlled = (props: React.ComponentProps<typeof BottomTabBar>) => {
|
|
49
|
+
const [active, setActive] = useState(0);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<BottomTabBar
|
|
53
|
+
{...props}
|
|
54
|
+
activeIndex={active}
|
|
55
|
+
onChange={(index) => {
|
|
56
|
+
setActive(index);
|
|
57
|
+
props.onChange?.(index);
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Default: Story = {
|
|
64
|
+
render: (args) => <Controlled {...args} />,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const CustomColors: Story = {
|
|
68
|
+
render: (args) => <Controlled {...args} />,
|
|
69
|
+
args: {
|
|
70
|
+
activeColor: '#111',
|
|
71
|
+
inactiveColor: '#B0B0B0',
|
|
72
|
+
indicatorColor: '#111',
|
|
73
|
+
indicatorWidth: 36,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const WithDisabled: Story = {
|
|
78
|
+
render: (args) => <Controlled {...args} />,
|
|
79
|
+
args: {
|
|
80
|
+
items: [
|
|
81
|
+
ITEMS[0],
|
|
82
|
+
ITEMS[1],
|
|
83
|
+
{ ...ITEMS[2], disabled: true },
|
|
84
|
+
ITEMS[3],
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, { FC, memo, useMemo } from 'react';
|
|
2
|
+
import { Pressable, StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
|
|
3
|
+
import { useTheme, ThemeType } from '../../../theme';
|
|
4
|
+
|
|
5
|
+
export type BottomTabBarIconProps = {
|
|
6
|
+
color: string;
|
|
7
|
+
size: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type BottomTabBarItem = {
|
|
11
|
+
key: string;
|
|
12
|
+
renderIcon: (props: BottomTabBarIconProps) => React.ReactNode;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type BottomTabBarProps = {
|
|
17
|
+
items: BottomTabBarItem[];
|
|
18
|
+
activeIndex: number;
|
|
19
|
+
onChange: (index: number) => void;
|
|
20
|
+
style?: StyleProp<ViewStyle>;
|
|
21
|
+
activeColor?: string;
|
|
22
|
+
inactiveColor?: string;
|
|
23
|
+
indicatorColor?: string;
|
|
24
|
+
indicatorWidth?: number;
|
|
25
|
+
indicatorHeight?: number;
|
|
26
|
+
iconSize?: number;
|
|
27
|
+
pressableStyle?: StyleProp<ViewStyle>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const BottomTabBar: FC<BottomTabBarProps> = memo(
|
|
31
|
+
({
|
|
32
|
+
items,
|
|
33
|
+
activeIndex,
|
|
34
|
+
onChange,
|
|
35
|
+
style,
|
|
36
|
+
pressableStyle,
|
|
37
|
+
activeColor,
|
|
38
|
+
inactiveColor,
|
|
39
|
+
indicatorColor,
|
|
40
|
+
indicatorWidth = 32,
|
|
41
|
+
indicatorHeight = 3,
|
|
42
|
+
iconSize = 26,
|
|
43
|
+
}) => {
|
|
44
|
+
const { theme } = useTheme();
|
|
45
|
+
const styles = useMemo(() => getStyles({ theme, indicatorHeight }), [theme, indicatorHeight]);
|
|
46
|
+
|
|
47
|
+
const resolvedActiveColor = activeColor ?? theme.black;
|
|
48
|
+
const resolvedInactiveColor = inactiveColor ?? theme.greyText;
|
|
49
|
+
const resolvedIndicatorColor = indicatorColor ?? theme.black;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View style={[styles.container, style]} accessibilityRole="tablist">
|
|
53
|
+
{items.map((item, index) => {
|
|
54
|
+
const isActive = index === activeIndex;
|
|
55
|
+
const color = isActive ? resolvedActiveColor : resolvedInactiveColor;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Pressable
|
|
59
|
+
key={item.key}
|
|
60
|
+
onPress={() => onChange(index)}
|
|
61
|
+
disabled={item.disabled}
|
|
62
|
+
style={({ pressed }) => [
|
|
63
|
+
styles.item,
|
|
64
|
+
pressableStyle,
|
|
65
|
+
item.disabled && styles.disabled,
|
|
66
|
+
pressed && !item.disabled ? styles.pressed : null,
|
|
67
|
+
]}
|
|
68
|
+
accessibilityRole="tab"
|
|
69
|
+
accessibilityState={{ selected: isActive, disabled: !!item.disabled }}
|
|
70
|
+
>
|
|
71
|
+
<View style={styles.iconWrapper}>{item.renderIcon({ color, size: iconSize })}</View>
|
|
72
|
+
<View
|
|
73
|
+
style={[
|
|
74
|
+
styles.indicator,
|
|
75
|
+
{
|
|
76
|
+
backgroundColor: isActive ? resolvedIndicatorColor : 'transparent',
|
|
77
|
+
width: indicatorWidth,
|
|
78
|
+
},
|
|
79
|
+
]}
|
|
80
|
+
/>
|
|
81
|
+
</Pressable>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</View>
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
BottomTabBar.displayName = 'BottomTabBar';
|
|
90
|
+
|
|
91
|
+
const getStyles = ({ theme, indicatorHeight }: { theme: ThemeType; indicatorHeight: number }) =>
|
|
92
|
+
StyleSheet.create({
|
|
93
|
+
container: {
|
|
94
|
+
flexDirection: 'row',
|
|
95
|
+
justifyContent: 'space-around',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
paddingTop: 12,
|
|
98
|
+
paddingBottom: 6,
|
|
99
|
+
backgroundColor: theme.white,
|
|
100
|
+
borderTopWidth: 1,
|
|
101
|
+
borderTopColor: theme.border,
|
|
102
|
+
},
|
|
103
|
+
item: {
|
|
104
|
+
flex: 1,
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
justifyContent: 'center',
|
|
107
|
+
paddingHorizontal: 4,
|
|
108
|
+
},
|
|
109
|
+
iconWrapper: {
|
|
110
|
+
height: 28,
|
|
111
|
+
justifyContent: 'center',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
marginBottom: 8,
|
|
114
|
+
},
|
|
115
|
+
indicator: {
|
|
116
|
+
height: indicatorHeight,
|
|
117
|
+
borderRadius: indicatorHeight / 2,
|
|
118
|
+
marginTop: 2,
|
|
119
|
+
},
|
|
120
|
+
pressed: {
|
|
121
|
+
opacity: 0.7,
|
|
122
|
+
},
|
|
123
|
+
disabled: {
|
|
124
|
+
opacity: 0.4,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export default BottomTabBar;
|
package/components/UI/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { LinkPreview } from './LinkPreview';
|
|
|
10
10
|
export { default as ListBlockItem } from './ListBlockItem';
|
|
11
11
|
export { default as ListItem } from './ListItem';
|
|
12
12
|
export { default as LoadingScreen } from './LoadingScreen';
|
|
13
|
+
export { default as HorizontalCardSection } from './HorizontalCardSection';
|
|
14
|
+
export type { HorizontalCardSectionProps, HorizontalCard } from './HorizontalCardSection';
|
|
13
15
|
export { NoAuth } from './NoAuth';
|
|
14
16
|
export { ParticipantItem } from './ParticipantItem';
|
|
15
17
|
export * from './Social';
|
|
@@ -20,6 +22,8 @@ export { default as TabBar } from './TabBar/TabBar';
|
|
|
20
22
|
export type { TabItem } from './TabBar/TabBar';
|
|
21
23
|
export { default as TabBarAi } from './TabBar/TabBarAi';
|
|
22
24
|
export type { TabItem as TabItemAi } from './TabBar/TabBarAi';
|
|
25
|
+
export { default as BottomTabBar } from './TabBar/BottomTabBar';
|
|
26
|
+
export type { BottomTabBarItem, BottomTabBarIconProps } from './TabBar/BottomTabBar';
|
|
23
27
|
export { default as TabButton } from './TabButton/TabButton';
|
|
24
28
|
export type { TabButtonProps } from './TabButton/TabButton';
|
|
25
29
|
export { default as TextWithLinks } from './TextWithLinks';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import AiAgentHeroCard from './AiAgentHeroCard';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: 'Features/UserCards/AiAgentHeroCard',
|
|
9
|
+
component: AiAgentHeroCard,
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<View style={{ padding: 16 }}>
|
|
13
|
+
<Story />
|
|
14
|
+
</View>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
args: {
|
|
18
|
+
avatarUri: 'https://i.pravatar.cc/150?img=47',
|
|
19
|
+
displayName: 'Алина Лебедева',
|
|
20
|
+
profession: 'HR-аналитик и карьерный консультант',
|
|
21
|
+
categories: ['HR', 'Карьера', 'AI-инструменты'],
|
|
22
|
+
followButtonTitle: 'Подписаться',
|
|
23
|
+
isFollowUpdating: false,
|
|
24
|
+
disableFollowAction: false,
|
|
25
|
+
isChatLoading: false,
|
|
26
|
+
aiBotId: 'ai-agent-42',
|
|
27
|
+
isFollowing: false,
|
|
28
|
+
},
|
|
29
|
+
argTypes: {
|
|
30
|
+
onToggleFollow: { action: 'toggle-follow' },
|
|
31
|
+
onStartChat: { action: 'start-chat' },
|
|
32
|
+
onAvatarPress: { action: 'avatar-press' },
|
|
33
|
+
},
|
|
34
|
+
} satisfies Meta<typeof AiAgentHeroCard>;
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
|
|
38
|
+
type Story = StoryObj<typeof AiAgentHeroCard>;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {};
|
|
41
|
+
|
|
42
|
+
export const Following: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
isFollowing: true,
|
|
45
|
+
followButtonTitle: 'Отписаться',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const LoadingStates: Story = {
|
|
50
|
+
args: {
|
|
51
|
+
isFollowUpdating: true,
|
|
52
|
+
isChatLoading: true,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const WithoutCategories: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
categories: [],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const ChatUnavailable: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
aiBotId: undefined,
|
|
65
|
+
},
|
|
66
|
+
};
|