rn-vs-lb 1.0.0
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/.storybook/index.ts +11 -0
- package/.storybook/main.ts +8 -0
- package/.storybook/preview.tsx +14 -0
- package/.storybook/stories/Button/Button.stories.tsx +32 -0
- package/.storybook/stories/eventCard/EventCard.stories.tsx +56 -0
- package/.storybook/storybook.requires.ts +34 -0
- package/App.tsx +5 -0
- package/App2.tsx +1 -0
- package/app.json +27 -0
- package/assets/adaptive-icon.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/splash.png +0 -0
- package/babel.config.js +6 -0
- package/package.json +38 -0
- package/src/components/buttons/Button.tsx +25 -0
- package/src/components/eventCard/EventCard.tsx +129 -0
- package/src/components/eventCard/Footer.tsx +50 -0
- package/src/components/eventCard/OrganizerContainer.tsx +60 -0
- package/src/components/eventCard/Tag.tsx +25 -0
- package/src/components/eventCard/index.ts +6 -0
- package/src/components/index.ts +5 -0
- package/src/utils/formatDate.ts +13 -0
- package/src/utils/index.ts +5 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import { view } from './storybook.requires';
|
|
3
|
+
|
|
4
|
+
const StorybookUIRoot = view.getStorybookUI({
|
|
5
|
+
storage: {
|
|
6
|
+
getItem: AsyncStorage.getItem,
|
|
7
|
+
setItem: AsyncStorage.setItem,
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export default StorybookUIRoot;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StorybookConfig } from '@storybook/react-native';
|
|
2
|
+
|
|
3
|
+
const main: StorybookConfig = {
|
|
4
|
+
stories: ['./stories/**/*.stories.?(ts|tsx|js|jsx)'],
|
|
5
|
+
addons: ['@storybook/addon-ondevice-controls', '@storybook/addon-ondevice-actions'],
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default main;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { MyButton } from '../../../src/components/buttons/Button';
|
|
5
|
+
|
|
6
|
+
const MyButtonMeta: Meta<typeof MyButton> = {
|
|
7
|
+
title: 'MyButton',
|
|
8
|
+
component: MyButton,
|
|
9
|
+
argTypes: {
|
|
10
|
+
onPress: { action: 'pressed the button' },
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
text: 'Hello world',
|
|
14
|
+
},
|
|
15
|
+
decorators: [
|
|
16
|
+
(Story) => (
|
|
17
|
+
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
|
|
18
|
+
<Story />
|
|
19
|
+
</View>
|
|
20
|
+
),
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default MyButtonMeta;
|
|
25
|
+
|
|
26
|
+
export const Basic: StoryObj<typeof MyButton> = {};
|
|
27
|
+
|
|
28
|
+
export const AnotherExample: StoryObj<typeof MyButton> = {
|
|
29
|
+
args: {
|
|
30
|
+
text: 'Another example',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { EventCard } from '../../../src/components/eventCard';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'EventCard',
|
|
7
|
+
component: EventCard,
|
|
8
|
+
argTypes: {
|
|
9
|
+
imageUri: { control: 'text', description: 'URL of the event image' },
|
|
10
|
+
date: { control: 'text', description: 'Date and time of the event' },
|
|
11
|
+
title: { control: 'text', description: 'Title of the event' },
|
|
12
|
+
description: { control: 'text', description: 'Description of the event' },
|
|
13
|
+
organizerAvatarUri: { control: 'text', description: 'URL of the organizer\'s avatar' },
|
|
14
|
+
organizerName: { control: 'text', description: 'Name of the organizer' },
|
|
15
|
+
organizerLink: { control: 'text', description: 'Link to Organizer Profile' },
|
|
16
|
+
likes: { control: 'number', description: 'Number of likes' },
|
|
17
|
+
views: { control: 'number', description: 'Number of views' },
|
|
18
|
+
tags: {
|
|
19
|
+
control: 'object',
|
|
20
|
+
description: 'Array of tag objects with label, backgroundColor, textColor, and borderColor',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
} as Meta<typeof EventCard>;
|
|
24
|
+
|
|
25
|
+
export const Default: StoryObj<typeof EventCard> = {
|
|
26
|
+
args: {
|
|
27
|
+
imageUri: 'https://pllace.online:5001/images/6511ee57e3578ef445159ed6/posts/65c7ae1354def36407b72362/DSC_8251.jpg',
|
|
28
|
+
date: '20:45 25 Feb, 2025',
|
|
29
|
+
title: 'Crash Drums Studio',
|
|
30
|
+
description: 'Crash Drum Studio — это место, где звуки становятся настоящими, где каждый может найти своё вдохновение и воплотить свои музыкальные мечты в жизнь. Присоединяйтесь к нам и почувствуйте силу музыки!',
|
|
31
|
+
organizerAvatarUri: 'https://pllace.online:5001/images/6511ee57e3578ef445159ed6/ahjq2678qem61.png',
|
|
32
|
+
organizerName: 'Admin Belgrade',
|
|
33
|
+
organizerLink: 'https://pllace.online/profile/6511ee57e3578ef445159ed6',
|
|
34
|
+
likes: 12,
|
|
35
|
+
views: 13,
|
|
36
|
+
tags: [
|
|
37
|
+
{ label: 'event', backgroundColor: '#FFF7E5', textColor: '#D78902', borderColor: '#FFD480' },
|
|
38
|
+
{ label: 'place', backgroundColor: '#F3E8FF', textColor: '#8A2BE2', borderColor: '#CBA3FF' },
|
|
39
|
+
],
|
|
40
|
+
|
|
41
|
+
},
|
|
42
|
+
render: (args) => (
|
|
43
|
+
<EventCard
|
|
44
|
+
imageUri={args.imageUri}
|
|
45
|
+
date={args.date}
|
|
46
|
+
tags={args.tags}
|
|
47
|
+
title={args.title}
|
|
48
|
+
description={args.description}
|
|
49
|
+
organizerAvatarUri={args.organizerAvatarUri}
|
|
50
|
+
organizerName={args.organizerName}
|
|
51
|
+
organizerLink={args.organizerLink}
|
|
52
|
+
likes={args.likes}
|
|
53
|
+
views={args.views}
|
|
54
|
+
/>
|
|
55
|
+
),
|
|
56
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* do not change this file, it is auto generated by storybook. */
|
|
2
|
+
|
|
3
|
+
import { start } from '@storybook/react-native';
|
|
4
|
+
|
|
5
|
+
import '@storybook/addon-ondevice-controls/register';
|
|
6
|
+
import '@storybook/addon-ondevice-actions/register';
|
|
7
|
+
|
|
8
|
+
const normalizedStories = [
|
|
9
|
+
{
|
|
10
|
+
titlePrefix: '',
|
|
11
|
+
directory: './.storybook/stories',
|
|
12
|
+
files: '**/*.stories.?(ts|tsx|js|jsx)',
|
|
13
|
+
importPathMatcher:
|
|
14
|
+
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
req: require.context(
|
|
17
|
+
'./stories',
|
|
18
|
+
true,
|
|
19
|
+
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/
|
|
20
|
+
),
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
global.STORIES = normalizedStories;
|
|
26
|
+
|
|
27
|
+
export const view = start({
|
|
28
|
+
annotations: [
|
|
29
|
+
require('./preview'),
|
|
30
|
+
require('@storybook/react-native/dist/preview'),
|
|
31
|
+
require('@storybook/addon-actions/preview'),
|
|
32
|
+
],
|
|
33
|
+
storyEntries: normalizedStories,
|
|
34
|
+
});
|
package/App.tsx
ADDED
package/App2.tsx
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './.storybook';
|
package/app.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expo": {
|
|
3
|
+
"name": "rn-vs-lb",
|
|
4
|
+
"slug": "rn-vs-lb",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"orientation": "portrait",
|
|
7
|
+
"icon": "./assets/icon.png",
|
|
8
|
+
"userInterfaceStyle": "light",
|
|
9
|
+
"splash": {
|
|
10
|
+
"image": "./assets/splash.png",
|
|
11
|
+
"resizeMode": "contain",
|
|
12
|
+
"backgroundColor": "#ffffff"
|
|
13
|
+
},
|
|
14
|
+
"ios": {
|
|
15
|
+
"supportsTablet": true
|
|
16
|
+
},
|
|
17
|
+
"android": {
|
|
18
|
+
"adaptiveIcon": {
|
|
19
|
+
"foregroundImage": "./assets/adaptive-icon.png",
|
|
20
|
+
"backgroundColor": "#ffffff"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"web": {
|
|
24
|
+
"favicon": "./assets/favicon.png"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
Binary file
|
|
Binary file
|
package/assets/icon.png
ADDED
|
Binary file
|
|
Binary file
|
package/babel.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rn-vs-lb",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "expo/AppEntry.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "expo start",
|
|
7
|
+
"android": "expo start --android",
|
|
8
|
+
"ios": "expo start --ios",
|
|
9
|
+
"web": "expo start --web",
|
|
10
|
+
"storybook-generate": "sb-rn-get-stories"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@ant-design/react-native": "^5.2.2",
|
|
14
|
+
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
15
|
+
"expo": "~51.0.28",
|
|
16
|
+
"expo-status-bar": "~1.12.1",
|
|
17
|
+
"react": "18.2.0",
|
|
18
|
+
"react-native": "0.74.5",
|
|
19
|
+
"react-native-gesture-handler": "^2.18.1",
|
|
20
|
+
"react-native-reanimated": "^3.15.0",
|
|
21
|
+
"react-native-safe-area-context": "^4.10.9",
|
|
22
|
+
"react-native-screens": "^3.34.0",
|
|
23
|
+
"react-native-vector-icons": "^10.1.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@babel/core": "^7.20.0",
|
|
27
|
+
"@react-native-community/datetimepicker": "^8.2.0",
|
|
28
|
+
"@react-native-community/slider": "^4.5.2",
|
|
29
|
+
"@storybook/addon-ondevice-actions": "^7.6.20",
|
|
30
|
+
"@storybook/addon-ondevice-controls": "^7.6.20",
|
|
31
|
+
"@storybook/react-native": "^7.6.20",
|
|
32
|
+
"@types/react": "~18.2.45",
|
|
33
|
+
"babel-loader": "^8.3.0",
|
|
34
|
+
"react-dom": "18.2.0",
|
|
35
|
+
"typescript": "^5.1.3"
|
|
36
|
+
},
|
|
37
|
+
"private": false
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export type MyButtonProps = {
|
|
5
|
+
onPress: () => void;
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const MyButton = ({ onPress, text }: MyButtonProps) => {
|
|
10
|
+
return (
|
|
11
|
+
<TouchableOpacity style={styles.container} onPress={onPress} activeOpacity={0.8}>
|
|
12
|
+
<Text style={styles.text}>{text}</Text>
|
|
13
|
+
</TouchableOpacity>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const styles = StyleSheet.create({
|
|
18
|
+
container: {
|
|
19
|
+
paddingHorizontal: 16,
|
|
20
|
+
paddingVertical: 8,
|
|
21
|
+
backgroundColor: 'purple',
|
|
22
|
+
borderRadius: 8,
|
|
23
|
+
},
|
|
24
|
+
text: { color: 'white' },
|
|
25
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, Image, StyleSheet } from 'react-native';
|
|
3
|
+
import Tag from './Tag';
|
|
4
|
+
import OrganizerContainer from './OrganizerContainer';
|
|
5
|
+
import Footer from './Footer';
|
|
6
|
+
|
|
7
|
+
interface EventCardProps {
|
|
8
|
+
imageUri: string;
|
|
9
|
+
date: string;
|
|
10
|
+
tags: { label: string; backgroundColor: string; textColor: string; borderColor: string; }[];
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
organizerAvatarUri: string;
|
|
14
|
+
organizerName: string;
|
|
15
|
+
organizerLink: string;
|
|
16
|
+
likes: number;
|
|
17
|
+
views: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const EventCard: React.FC<EventCardProps> = ({
|
|
21
|
+
imageUri,
|
|
22
|
+
date,
|
|
23
|
+
tags,
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
organizerAvatarUri,
|
|
27
|
+
organizerName,
|
|
28
|
+
organizerLink,
|
|
29
|
+
likes,
|
|
30
|
+
views
|
|
31
|
+
}) => {
|
|
32
|
+
return (
|
|
33
|
+
<View style={styles.container}>
|
|
34
|
+
<View style={styles.imageContainer}>
|
|
35
|
+
<Image
|
|
36
|
+
source={{ uri: imageUri }}
|
|
37
|
+
style={styles.image}
|
|
38
|
+
resizeMode="cover"
|
|
39
|
+
/>
|
|
40
|
+
</View>
|
|
41
|
+
<View style={styles.content}>
|
|
42
|
+
<View style={styles.dateAndTags}>
|
|
43
|
+
<Text style={styles.date}>{date}</Text>
|
|
44
|
+
{tags.map((tag, index) => (
|
|
45
|
+
<Tag
|
|
46
|
+
key={index}
|
|
47
|
+
label={tag.label}
|
|
48
|
+
backgroundColor={tag.backgroundColor}
|
|
49
|
+
textColor={tag.textColor}
|
|
50
|
+
borderColor={tag.borderColor}
|
|
51
|
+
/>
|
|
52
|
+
))}
|
|
53
|
+
</View>
|
|
54
|
+
<Text
|
|
55
|
+
style={styles.title}
|
|
56
|
+
numberOfLines={1}
|
|
57
|
+
ellipsizeMode="tail"
|
|
58
|
+
>{title}</Text>
|
|
59
|
+
<Text
|
|
60
|
+
style={styles.description}
|
|
61
|
+
numberOfLines={2}
|
|
62
|
+
ellipsizeMode="tail"
|
|
63
|
+
>
|
|
64
|
+
{description}
|
|
65
|
+
</Text>
|
|
66
|
+
<View style={styles.organizerContainer}>
|
|
67
|
+
<OrganizerContainer
|
|
68
|
+
avatarUri={organizerAvatarUri}
|
|
69
|
+
organizerName={organizerName}
|
|
70
|
+
organizerLink={organizerLink}
|
|
71
|
+
/>
|
|
72
|
+
</View>
|
|
73
|
+
<Footer likes={likes} views={views} />
|
|
74
|
+
</View>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
container: {
|
|
81
|
+
backgroundColor: '#fff',
|
|
82
|
+
borderRadius: 10,
|
|
83
|
+
overflow: 'hidden',
|
|
84
|
+
margin: 10,
|
|
85
|
+
elevation: 3, // For Android shadow
|
|
86
|
+
shadowColor: '#000',
|
|
87
|
+
shadowOffset: { width: 0, height: 2 },
|
|
88
|
+
shadowOpacity: 0.3,
|
|
89
|
+
shadowRadius: 3,
|
|
90
|
+
},
|
|
91
|
+
imageContainer: {
|
|
92
|
+
width: '100%',
|
|
93
|
+
height: 270,
|
|
94
|
+
justifyContent: 'center',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
},
|
|
97
|
+
image: {
|
|
98
|
+
width: '100%',
|
|
99
|
+
height: '100%',
|
|
100
|
+
},
|
|
101
|
+
content: {
|
|
102
|
+
padding: 14,
|
|
103
|
+
},
|
|
104
|
+
dateAndTags: {
|
|
105
|
+
flexDirection: 'row',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
marginBottom: 5,
|
|
108
|
+
},
|
|
109
|
+
date: {
|
|
110
|
+
fontSize: 14,
|
|
111
|
+
color: '#888',
|
|
112
|
+
marginRight: 10,
|
|
113
|
+
},
|
|
114
|
+
title: {
|
|
115
|
+
fontSize: 20,
|
|
116
|
+
fontWeight: 'bold',
|
|
117
|
+
marginBottom: 5,
|
|
118
|
+
},
|
|
119
|
+
description: {
|
|
120
|
+
fontSize: 16,
|
|
121
|
+
color: '#666',
|
|
122
|
+
marginBottom: 14,
|
|
123
|
+
},
|
|
124
|
+
organizerContainer: {
|
|
125
|
+
marginBottom: 16,
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export default EventCard;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
|
3
|
+
import Icon from 'react-native-vector-icons/Ionicons';
|
|
4
|
+
|
|
5
|
+
interface FooterProps {
|
|
6
|
+
likes: number;
|
|
7
|
+
views: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Footer: React.FC<FooterProps> = ({ likes, views }) => {
|
|
11
|
+
const [isLiked, setIsLiked] = useState(false);
|
|
12
|
+
|
|
13
|
+
const handleLikePress = () => {
|
|
14
|
+
setIsLiked(!isLiked);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<View style={styles.container}>
|
|
19
|
+
<TouchableOpacity onPress={handleLikePress} style={styles.item}>
|
|
20
|
+
<Icon name={isLiked ? "heart" : "heart-outline"} size={20} color={isLiked ? "#ff0000" : "#666"} />
|
|
21
|
+
<Text style={styles.text}>{isLiked ? likes + 1 : likes} {likes + (isLiked ? 1 : 0) === 1 ? 'Like' : 'Likes'}</Text>
|
|
22
|
+
</TouchableOpacity>
|
|
23
|
+
<View style={styles.item}>
|
|
24
|
+
<Icon name="eye-outline" size={20} color="#666" />
|
|
25
|
+
<Text style={styles.text}>{views} Views</Text>
|
|
26
|
+
</View>
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const styles = StyleSheet.create({
|
|
32
|
+
container: {
|
|
33
|
+
flexDirection: 'row',
|
|
34
|
+
justifyContent: 'space-between',
|
|
35
|
+
borderTopWidth: 1,
|
|
36
|
+
borderTopColor: '#eee',
|
|
37
|
+
paddingTop: 10,
|
|
38
|
+
},
|
|
39
|
+
item: {
|
|
40
|
+
flexDirection: 'row',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
},
|
|
43
|
+
text: {
|
|
44
|
+
fontSize: 14,
|
|
45
|
+
color: '#666',
|
|
46
|
+
marginLeft: 5,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export default Footer;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, Image, StyleSheet, TouchableOpacity, Linking } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface OrganizerContainerProps {
|
|
5
|
+
avatarUri: string;
|
|
6
|
+
organizerName: string;
|
|
7
|
+
organizerLink: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const OrganizerContainer: React.FC<OrganizerContainerProps> = ({ avatarUri, organizerName, organizerLink }) => {
|
|
11
|
+
|
|
12
|
+
const handlePress = () => {
|
|
13
|
+
if (organizerLink) {
|
|
14
|
+
Linking.openURL(organizerLink).catch(err => console.error("Couldn't load page", err));
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<TouchableOpacity onPress={handlePress} style={styles.container}>
|
|
20
|
+
<Image source={{ uri: avatarUri }} style={styles.avatar} />
|
|
21
|
+
<View style={styles.organizerTextContainer}>
|
|
22
|
+
<Text style={styles.organizerTitle}>Organizer</Text>
|
|
23
|
+
<Text
|
|
24
|
+
style={styles.organizerText}
|
|
25
|
+
numberOfLines={1}
|
|
26
|
+
ellipsizeMode="tail"
|
|
27
|
+
>
|
|
28
|
+
{organizerName}
|
|
29
|
+
</Text>
|
|
30
|
+
</View>
|
|
31
|
+
</TouchableOpacity>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const styles = StyleSheet.create({
|
|
36
|
+
container: {
|
|
37
|
+
flexDirection: 'row',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
marginBottom: 10,
|
|
40
|
+
},
|
|
41
|
+
avatar: {
|
|
42
|
+
width: 40,
|
|
43
|
+
height: 40,
|
|
44
|
+
borderRadius: 20,
|
|
45
|
+
marginRight: 10,
|
|
46
|
+
},
|
|
47
|
+
organizerTextContainer: {
|
|
48
|
+
flex: 1,
|
|
49
|
+
paddingRight: 10,
|
|
50
|
+
},
|
|
51
|
+
organizerTitle: {
|
|
52
|
+
fontSize: 14,
|
|
53
|
+
color: '#666',
|
|
54
|
+
},
|
|
55
|
+
organizerText: {
|
|
56
|
+
fontSize: 16,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default OrganizerContainer;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
|
|
4
|
+
const Tag = ({ label, backgroundColor, textColor, borderColor }) => (
|
|
5
|
+
<View style={[styles.tagContainer, { backgroundColor, borderColor }]}>
|
|
6
|
+
<Text style={[styles.tagText, { color: textColor }]}>{label}</Text>
|
|
7
|
+
</View>
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const styles = StyleSheet.create({
|
|
11
|
+
tagContainer: {
|
|
12
|
+
paddingHorizontal: 8,
|
|
13
|
+
paddingVertical: 4,
|
|
14
|
+
borderRadius: 8,
|
|
15
|
+
borderWidth: 1,
|
|
16
|
+
marginLeft: 5,
|
|
17
|
+
},
|
|
18
|
+
tagText: {
|
|
19
|
+
fontSize: 14,
|
|
20
|
+
fontWeight: '600',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
export default Tag;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const formatDate = (isoDate: string): string => {
|
|
2
|
+
const date = new Date(isoDate);
|
|
3
|
+
|
|
4
|
+
const options: Intl.DateTimeFormatOptions = {
|
|
5
|
+
hour: '2-digit',
|
|
6
|
+
minute: '2-digit',
|
|
7
|
+
day: '2-digit',
|
|
8
|
+
month: 'short',
|
|
9
|
+
year: 'numeric',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return date.toLocaleDateString('en-GB', options);
|
|
13
|
+
}
|