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.
@@ -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,14 @@
1
+ import type { Preview } from '@storybook/react';
2
+
3
+ const preview: Preview = {
4
+ parameters: {
5
+ controls: {
6
+ matchers: {
7
+ color: /(background|color)$/i,
8
+ date: /Date$/,
9
+ },
10
+ },
11
+ },
12
+ };
13
+
14
+ export default preview;
@@ -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
@@ -0,0 +1,5 @@
1
+ import { EventCard } from "./src/components";
2
+
3
+ export {
4
+ EventCard
5
+ }
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
Binary file
Binary file
@@ -0,0 +1,6 @@
1
+ module.exports = function(api) {
2
+ api.cache(true);
3
+ return {
4
+ presets: ['babel-preset-expo'],
5
+ };
6
+ };
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,6 @@
1
+ import EventCard from "./EventCard";
2
+
3
+
4
+ export {
5
+ EventCard
6
+ }
@@ -0,0 +1,5 @@
1
+ import { EventCard } from "./eventCard";
2
+
3
+ export {
4
+ EventCard
5
+ }
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ import { formatDate } from "./formatDate"
2
+
3
+ export {
4
+ formatDate
5
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true
5
+ }
6
+ }