@sudobility/marketing-components-rn 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/dist/CrmDashboard.d.ts +12 -0
- package/dist/CrmDashboard.d.ts.map +1 -0
- package/dist/CtaBanner.d.ts +13 -0
- package/dist/CtaBanner.d.ts.map +1 -0
- package/dist/FeatureListItem.d.ts +13 -0
- package/dist/FeatureListItem.d.ts.map +1 -0
- package/dist/FeatureSpotlight.d.ts +13 -0
- package/dist/FeatureSpotlight.d.ts.map +1 -0
- package/dist/HeroBannerWithBadge.d.ts +27 -0
- package/dist/HeroBannerWithBadge.d.ts.map +1 -0
- package/dist/InternalLinkClusters.d.ts +33 -0
- package/dist/InternalLinkClusters.d.ts.map +1 -0
- package/dist/NpsSurvey.d.ts +13 -0
- package/dist/NpsSurvey.d.ts.map +1 -0
- package/dist/SalesReport.d.ts +12 -0
- package/dist/SalesReport.d.ts.map +1 -0
- package/dist/TestimonialSlider.d.ts +13 -0
- package/dist/TestimonialSlider.d.ts.map +1 -0
- package/dist/UseCaseGrid.d.ts +23 -0
- package/dist/UseCaseGrid.d.ts.map +1 -0
- package/dist/WelcomeScreen.d.ts +17 -0
- package/dist/WelcomeScreen.d.ts.map +1 -0
- package/dist/index.cjs.js +1459 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +1459 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +56 -0
- package/src/CrmDashboard.tsx +39 -0
- package/src/CtaBanner.tsx +46 -0
- package/src/FeatureListItem.tsx +47 -0
- package/src/FeatureSpotlight.tsx +46 -0
- package/src/HeroBannerWithBadge.tsx +134 -0
- package/src/InternalLinkClusters.tsx +193 -0
- package/src/NpsSurvey.tsx +46 -0
- package/src/SalesReport.tsx +39 -0
- package/src/TestimonialSlider.tsx +46 -0
- package/src/UseCaseGrid.tsx +128 -0
- package/src/WelcomeScreen.tsx +82 -0
- package/src/index.ts +32 -0
- package/src/nativewind.d.ts +23 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, Pressable, type ViewProps } from 'react-native';
|
|
3
|
+
import { cn } from '@sudobility/components-rn';
|
|
4
|
+
|
|
5
|
+
export interface InternalLinkProps {
|
|
6
|
+
to: string;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
variant?: 'primary' | 'secondary' | 'subtle';
|
|
10
|
+
onPress?: (url: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const linkVariants = {
|
|
14
|
+
primary: 'text-blue-600 dark:text-blue-400',
|
|
15
|
+
secondary: 'text-green-600 dark:text-green-400',
|
|
16
|
+
subtle: 'text-gray-600 dark:text-gray-400',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const InternalLink: React.FC<InternalLinkProps> = ({
|
|
20
|
+
to,
|
|
21
|
+
children,
|
|
22
|
+
className,
|
|
23
|
+
variant = 'primary',
|
|
24
|
+
onPress,
|
|
25
|
+
}) => (
|
|
26
|
+
<Pressable
|
|
27
|
+
onPress={() => onPress?.(to)}
|
|
28
|
+
accessibilityRole="link"
|
|
29
|
+
accessibilityLabel={typeof children === 'string' ? `Navigate to ${children}` : undefined}
|
|
30
|
+
>
|
|
31
|
+
<Text className={cn('underline font-medium', linkVariants[variant], className)}>
|
|
32
|
+
{children}
|
|
33
|
+
</Text>
|
|
34
|
+
</Pressable>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
type ClusterType = 'gettingStarted' | 'benefits' | 'technical' | 'integration';
|
|
38
|
+
|
|
39
|
+
export interface TopicClusterLinksProps extends ViewProps {
|
|
40
|
+
cluster: ClusterType;
|
|
41
|
+
context?: string;
|
|
42
|
+
onLinkPress?: (url: string) => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const WEB3_EMAIL_CLUSTERS: Record<ClusterType, Record<string, string>> = {
|
|
46
|
+
gettingStarted: {
|
|
47
|
+
documentation: '/document#getting-started',
|
|
48
|
+
connect: '/connect',
|
|
49
|
+
features: '/document#email-management',
|
|
50
|
+
},
|
|
51
|
+
benefits: {
|
|
52
|
+
users: '/web3-users',
|
|
53
|
+
projects: '/web3-projects',
|
|
54
|
+
security: '/document#technical-details',
|
|
55
|
+
nameService: '/document#name-service-subscription',
|
|
56
|
+
},
|
|
57
|
+
technical: {
|
|
58
|
+
documentation: '/document',
|
|
59
|
+
apiDocs: '/document#api-documentation',
|
|
60
|
+
smartContracts: '/document#smart-contracts',
|
|
61
|
+
security: '/document#technical-details',
|
|
62
|
+
},
|
|
63
|
+
integration: {
|
|
64
|
+
projects: '/web3-projects',
|
|
65
|
+
delegation: '/document#email-delegation',
|
|
66
|
+
nameService: '/document#name-service-subscription',
|
|
67
|
+
troubleshooting: '/document#troubleshooting',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const LINK_TEXTS: Record<ClusterType, Record<string, string>> = {
|
|
72
|
+
gettingStarted: {
|
|
73
|
+
documentation: 'Learn how it works',
|
|
74
|
+
connect: 'Get started now',
|
|
75
|
+
features: 'Explore features',
|
|
76
|
+
},
|
|
77
|
+
benefits: {
|
|
78
|
+
users: 'Benefits for users',
|
|
79
|
+
projects: 'For Web3 projects',
|
|
80
|
+
security: 'Security details',
|
|
81
|
+
nameService: 'ENS/SNS domains',
|
|
82
|
+
},
|
|
83
|
+
technical: {
|
|
84
|
+
documentation: 'Full documentation',
|
|
85
|
+
apiDocs: 'API reference',
|
|
86
|
+
smartContracts: 'Smart contract integration',
|
|
87
|
+
security: 'Technical security',
|
|
88
|
+
},
|
|
89
|
+
integration: {
|
|
90
|
+
projects: 'Integration examples',
|
|
91
|
+
delegation: 'Email delegation',
|
|
92
|
+
nameService: 'Domain setup',
|
|
93
|
+
troubleshooting: 'Troubleshooting guide',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const TopicClusterLinks: React.FC<TopicClusterLinksProps> = ({
|
|
98
|
+
cluster,
|
|
99
|
+
className,
|
|
100
|
+
onLinkPress,
|
|
101
|
+
...props
|
|
102
|
+
}) => {
|
|
103
|
+
const links = WEB3_EMAIL_CLUSTERS[cluster];
|
|
104
|
+
const texts = LINK_TEXTS[cluster];
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<View className={cn('flex-row flex-wrap gap-2', className)} {...props}>
|
|
108
|
+
{Object.entries(links).map(([key, url], index) => (
|
|
109
|
+
<React.Fragment key={key}>
|
|
110
|
+
<InternalLink to={url} variant="primary" onPress={onLinkPress}>
|
|
111
|
+
{texts[key]}
|
|
112
|
+
</InternalLink>
|
|
113
|
+
{index < Object.keys(links).length - 1 && (
|
|
114
|
+
<Text className="text-gray-400"> • </Text>
|
|
115
|
+
)}
|
|
116
|
+
</React.Fragment>
|
|
117
|
+
))}
|
|
118
|
+
</View>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export interface RelatedLinksProps extends ViewProps {
|
|
123
|
+
title?: string;
|
|
124
|
+
links: Array<{
|
|
125
|
+
text: string;
|
|
126
|
+
url: string;
|
|
127
|
+
variant?: 'primary' | 'secondary' | 'subtle';
|
|
128
|
+
}>;
|
|
129
|
+
onLinkPress?: (url: string) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const RelatedLinks: React.FC<RelatedLinksProps> = ({
|
|
133
|
+
title = 'Related:',
|
|
134
|
+
links,
|
|
135
|
+
className,
|
|
136
|
+
onLinkPress,
|
|
137
|
+
...props
|
|
138
|
+
}) => (
|
|
139
|
+
<View
|
|
140
|
+
className={cn(
|
|
141
|
+
'mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800',
|
|
142
|
+
className
|
|
143
|
+
)}
|
|
144
|
+
{...props}
|
|
145
|
+
>
|
|
146
|
+
<View className="flex-row flex-wrap items-center gap-2">
|
|
147
|
+
<Text className="text-sm font-medium text-blue-900 dark:text-blue-200">
|
|
148
|
+
{title}
|
|
149
|
+
</Text>
|
|
150
|
+
{links.map((link, index) => (
|
|
151
|
+
<React.Fragment key={index}>
|
|
152
|
+
<InternalLink
|
|
153
|
+
to={link.url}
|
|
154
|
+
variant={link.variant || 'primary'}
|
|
155
|
+
onPress={onLinkPress}
|
|
156
|
+
>
|
|
157
|
+
{link.text}
|
|
158
|
+
</InternalLink>
|
|
159
|
+
{index < links.length - 1 && (
|
|
160
|
+
<Text className="text-gray-400"> • </Text>
|
|
161
|
+
)}
|
|
162
|
+
</React.Fragment>
|
|
163
|
+
))}
|
|
164
|
+
</View>
|
|
165
|
+
</View>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
export const generateContextualLinks = (pageType: string, _userStatus?: string) => {
|
|
169
|
+
const baseLinks: Record<string, Array<{ text: string; url: string }>> = {
|
|
170
|
+
homepage: [
|
|
171
|
+
{ text: 'How it works', url: '/document#getting-started' },
|
|
172
|
+
{ text: 'User benefits', url: '/web3-users' },
|
|
173
|
+
{ text: 'For projects', url: '/web3-projects' },
|
|
174
|
+
],
|
|
175
|
+
documentation: [
|
|
176
|
+
{ text: 'Get started', url: '/connect' },
|
|
177
|
+
{ text: 'User guide', url: '/web3-users' },
|
|
178
|
+
{ text: 'API docs', url: '/document#api-documentation' },
|
|
179
|
+
],
|
|
180
|
+
users: [
|
|
181
|
+
{ text: 'Start now', url: '/connect' },
|
|
182
|
+
{ text: 'Documentation', url: '/document' },
|
|
183
|
+
{ text: 'For projects', url: '/web3-projects' },
|
|
184
|
+
],
|
|
185
|
+
projects: [
|
|
186
|
+
{ text: 'API integration', url: '/document#api-documentation' },
|
|
187
|
+
{ text: 'Smart contracts', url: '/document#smart-contracts' },
|
|
188
|
+
{ text: 'User benefits', url: '/web3-users' },
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return baseLinks[pageType] || [];
|
|
193
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Pressable, type ViewProps } from 'react-native';
|
|
3
|
+
import { cn } from '@sudobility/components-rn';
|
|
4
|
+
|
|
5
|
+
export interface NpsSurveyProps extends ViewProps {
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
onPress?: () => void;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* NpsSurvey component for React Native
|
|
13
|
+
* NPS survey display
|
|
14
|
+
*/
|
|
15
|
+
export const NpsSurvey: React.FC<NpsSurveyProps> = ({
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
disabled = false,
|
|
19
|
+
onPress,
|
|
20
|
+
...props
|
|
21
|
+
}) => {
|
|
22
|
+
return (
|
|
23
|
+
<Pressable
|
|
24
|
+
onPress={disabled ? undefined : onPress}
|
|
25
|
+
disabled={disabled}
|
|
26
|
+
accessibilityRole="button"
|
|
27
|
+
accessibilityLabel="NPS Survey"
|
|
28
|
+
accessibilityState={{ disabled }}
|
|
29
|
+
className={cn(
|
|
30
|
+
'p-4 rounded-lg border',
|
|
31
|
+
'bg-white dark:bg-gray-900',
|
|
32
|
+
'border-gray-200 dark:border-gray-700',
|
|
33
|
+
disabled && 'opacity-50',
|
|
34
|
+
'active:bg-gray-50 dark:active:bg-gray-800',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children || (
|
|
40
|
+
<Text className="text-gray-900 dark:text-white">
|
|
41
|
+
NpsSurvey Component
|
|
42
|
+
</Text>
|
|
43
|
+
)}
|
|
44
|
+
</Pressable>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, type ViewProps } from 'react-native';
|
|
3
|
+
import { cn } from '@sudobility/components-rn';
|
|
4
|
+
|
|
5
|
+
export interface SalesReportProps extends ViewProps {
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SalesReport component for React Native
|
|
12
|
+
* Sales report display container
|
|
13
|
+
*/
|
|
14
|
+
export const SalesReport: React.FC<SalesReportProps> = ({
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
disabled,
|
|
18
|
+
...props
|
|
19
|
+
}) => {
|
|
20
|
+
return (
|
|
21
|
+
<View
|
|
22
|
+
className={cn(
|
|
23
|
+
'p-4 rounded-lg border',
|
|
24
|
+
'bg-white dark:bg-gray-900',
|
|
25
|
+
'border-gray-200 dark:border-gray-700',
|
|
26
|
+
disabled && 'opacity-50',
|
|
27
|
+
className
|
|
28
|
+
)}
|
|
29
|
+
accessibilityLabel="Sales Report"
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{children || (
|
|
33
|
+
<Text className="text-gray-900 dark:text-white">
|
|
34
|
+
SalesReport Component
|
|
35
|
+
</Text>
|
|
36
|
+
)}
|
|
37
|
+
</View>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Pressable, type ViewProps } from 'react-native';
|
|
3
|
+
import { cn } from '@sudobility/components-rn';
|
|
4
|
+
|
|
5
|
+
export interface TestimonialSliderProps extends ViewProps {
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
onPress?: () => void;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* TestimonialSlider component for React Native
|
|
13
|
+
* Testimonial slider display
|
|
14
|
+
*/
|
|
15
|
+
export const TestimonialSlider: React.FC<TestimonialSliderProps> = ({
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
disabled = false,
|
|
19
|
+
onPress,
|
|
20
|
+
...props
|
|
21
|
+
}) => {
|
|
22
|
+
return (
|
|
23
|
+
<Pressable
|
|
24
|
+
onPress={disabled ? undefined : onPress}
|
|
25
|
+
disabled={disabled}
|
|
26
|
+
accessibilityRole="button"
|
|
27
|
+
accessibilityLabel="Testimonial Slider"
|
|
28
|
+
accessibilityState={{ disabled }}
|
|
29
|
+
className={cn(
|
|
30
|
+
'p-4 rounded-lg border',
|
|
31
|
+
'bg-white dark:bg-gray-900',
|
|
32
|
+
'border-gray-200 dark:border-gray-700',
|
|
33
|
+
disabled && 'opacity-50',
|
|
34
|
+
'active:bg-gray-50 dark:active:bg-gray-800',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children || (
|
|
40
|
+
<Text className="text-gray-900 dark:text-white">
|
|
41
|
+
TestimonialSlider Component
|
|
42
|
+
</Text>
|
|
43
|
+
)}
|
|
44
|
+
</Pressable>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, FlatList, type ViewProps } from 'react-native';
|
|
3
|
+
import { cn } from '@sudobility/components-rn';
|
|
4
|
+
|
|
5
|
+
type UseCaseColor = 'blue' | 'green' | 'purple' | 'orange' | 'pink' | 'gray';
|
|
6
|
+
|
|
7
|
+
export interface UseCase {
|
|
8
|
+
icon: React.ReactNode;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
examples: string[];
|
|
12
|
+
color?: UseCaseColor;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseCaseGridProps extends ViewProps {
|
|
16
|
+
title?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
useCases: UseCase[];
|
|
19
|
+
columns?: 2 | 3 | 4;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const colorClasses: Record<UseCaseColor, string> = {
|
|
23
|
+
blue: 'text-blue-600 dark:text-blue-400',
|
|
24
|
+
green: 'text-green-600 dark:text-green-400',
|
|
25
|
+
purple: 'text-purple-600 dark:text-purple-400',
|
|
26
|
+
orange: 'text-orange-600 dark:text-orange-400',
|
|
27
|
+
pink: 'text-pink-600 dark:text-pink-400',
|
|
28
|
+
gray: 'text-gray-600 dark:text-gray-400',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const bulletColors: Record<UseCaseColor, string> = {
|
|
32
|
+
blue: 'bg-blue-500',
|
|
33
|
+
green: 'bg-green-500',
|
|
34
|
+
purple: 'bg-purple-500',
|
|
35
|
+
orange: 'bg-orange-500',
|
|
36
|
+
pink: 'bg-pink-500',
|
|
37
|
+
gray: 'bg-gray-500',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface UseCaseCardProps {
|
|
41
|
+
useCase: UseCase;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const UseCaseCard: React.FC<UseCaseCardProps> = ({ useCase }) => {
|
|
45
|
+
const iconColor = useCase.color ? colorClasses[useCase.color] : colorClasses.blue;
|
|
46
|
+
const bulletColor = useCase.color ? bulletColors[useCase.color] : bulletColors.blue;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<View className="bg-white dark:bg-gray-800 rounded-xl p-6 border border-gray-200 dark:border-gray-700 mb-4 mx-2">
|
|
50
|
+
<View className={cn('mb-4', iconColor)}>{useCase.icon}</View>
|
|
51
|
+
|
|
52
|
+
<Text className="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
|
53
|
+
{useCase.title}
|
|
54
|
+
</Text>
|
|
55
|
+
|
|
56
|
+
<Text className="text-gray-600 dark:text-gray-300 mb-4">
|
|
57
|
+
{useCase.description}
|
|
58
|
+
</Text>
|
|
59
|
+
|
|
60
|
+
{useCase.examples && useCase.examples.length > 0 && (
|
|
61
|
+
<View>
|
|
62
|
+
<Text className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">
|
|
63
|
+
Examples:
|
|
64
|
+
</Text>
|
|
65
|
+
<View className="gap-1">
|
|
66
|
+
{useCase.examples.map((example, exampleIndex) => (
|
|
67
|
+
<View key={exampleIndex} className="flex-row items-start">
|
|
68
|
+
<View
|
|
69
|
+
className={cn(
|
|
70
|
+
'w-1.5 h-1.5 rounded-full mt-2 mr-2',
|
|
71
|
+
bulletColor
|
|
72
|
+
)}
|
|
73
|
+
/>
|
|
74
|
+
<Text className="text-sm text-gray-600 dark:text-gray-400 flex-1">
|
|
75
|
+
{example}
|
|
76
|
+
</Text>
|
|
77
|
+
</View>
|
|
78
|
+
))}
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
)}
|
|
82
|
+
</View>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* UseCaseGrid component for React Native
|
|
88
|
+
* Grid display of use cases
|
|
89
|
+
*/
|
|
90
|
+
export const UseCaseGrid: React.FC<UseCaseGridProps> = ({
|
|
91
|
+
title,
|
|
92
|
+
description,
|
|
93
|
+
useCases,
|
|
94
|
+
columns = 2,
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}) => {
|
|
98
|
+
return (
|
|
99
|
+
<View className={cn('py-8 px-4', className)} {...props}>
|
|
100
|
+
{(title || description) && (
|
|
101
|
+
<View className="items-center mb-8">
|
|
102
|
+
{title && (
|
|
103
|
+
<Text className="text-2xl font-bold text-gray-900 dark:text-white mb-4 text-center">
|
|
104
|
+
{title}
|
|
105
|
+
</Text>
|
|
106
|
+
)}
|
|
107
|
+
{description && (
|
|
108
|
+
<Text className="text-lg text-gray-600 dark:text-gray-300 text-center max-w-lg">
|
|
109
|
+
{description}
|
|
110
|
+
</Text>
|
|
111
|
+
)}
|
|
112
|
+
</View>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
<FlatList
|
|
116
|
+
data={useCases}
|
|
117
|
+
keyExtractor={(_, index) => index.toString()}
|
|
118
|
+
numColumns={columns > 2 ? 2 : columns}
|
|
119
|
+
renderItem={({ item }) => (
|
|
120
|
+
<View className={columns > 2 ? 'flex-1' : 'flex-1'}>
|
|
121
|
+
<UseCaseCard useCase={item} />
|
|
122
|
+
</View>
|
|
123
|
+
)}
|
|
124
|
+
scrollEnabled={false}
|
|
125
|
+
/>
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, type ViewProps } from 'react-native';
|
|
3
|
+
import { cn, Button } from '@sudobility/components-rn';
|
|
4
|
+
|
|
5
|
+
export interface WelcomeScreenProps extends ViewProps {
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
illustration?: React.ReactNode;
|
|
10
|
+
primaryButtonText?: string;
|
|
11
|
+
secondaryButtonText?: string;
|
|
12
|
+
onPrimaryPress?: () => void;
|
|
13
|
+
onSecondaryPress?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Welcome screen component for onboarding flows
|
|
18
|
+
*/
|
|
19
|
+
export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({
|
|
20
|
+
title,
|
|
21
|
+
subtitle,
|
|
22
|
+
description,
|
|
23
|
+
illustration,
|
|
24
|
+
primaryButtonText = 'Get Started',
|
|
25
|
+
secondaryButtonText,
|
|
26
|
+
onPrimaryPress,
|
|
27
|
+
onSecondaryPress,
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}) => {
|
|
31
|
+
return (
|
|
32
|
+
<View
|
|
33
|
+
className={cn(
|
|
34
|
+
'flex-1 items-center justify-center px-6 py-12',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{illustration && (
|
|
40
|
+
<View className="mb-8">{illustration}</View>
|
|
41
|
+
)}
|
|
42
|
+
|
|
43
|
+
{subtitle && (
|
|
44
|
+
<Text className="text-sm font-medium text-blue-600 dark:text-blue-400 mb-2 uppercase tracking-wide">
|
|
45
|
+
{subtitle}
|
|
46
|
+
</Text>
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
<Text className="text-3xl font-bold text-gray-900 dark:text-white text-center mb-4">
|
|
50
|
+
{title}
|
|
51
|
+
</Text>
|
|
52
|
+
|
|
53
|
+
{description && (
|
|
54
|
+
<Text className="text-base text-gray-600 dark:text-gray-400 text-center mb-8 max-w-sm">
|
|
55
|
+
{description}
|
|
56
|
+
</Text>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
<View className="w-full max-w-xs gap-3">
|
|
60
|
+
<Button
|
|
61
|
+
variant="primary"
|
|
62
|
+
size="lg"
|
|
63
|
+
onPress={onPrimaryPress}
|
|
64
|
+
className="w-full"
|
|
65
|
+
>
|
|
66
|
+
{primaryButtonText}
|
|
67
|
+
</Button>
|
|
68
|
+
|
|
69
|
+
{secondaryButtonText && (
|
|
70
|
+
<Button
|
|
71
|
+
variant="ghost"
|
|
72
|
+
size="lg"
|
|
73
|
+
onPress={onSecondaryPress}
|
|
74
|
+
className="w-full"
|
|
75
|
+
>
|
|
76
|
+
{secondaryButtonText}
|
|
77
|
+
</Button>
|
|
78
|
+
)}
|
|
79
|
+
</View>
|
|
80
|
+
</View>
|
|
81
|
+
);
|
|
82
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sudobility/marketing-components-rn
|
|
3
|
+
* React Native Marketing components for Sudobility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { CrmDashboard, type CrmDashboardProps } from './CrmDashboard';
|
|
7
|
+
export { CtaBanner, type CtaBannerProps } from './CtaBanner';
|
|
8
|
+
export { FeatureListItem, type FeatureListItemProps } from './FeatureListItem';
|
|
9
|
+
export { FeatureSpotlight, type FeatureSpotlightProps } from './FeatureSpotlight';
|
|
10
|
+
export {
|
|
11
|
+
HeroBannerWithBadge,
|
|
12
|
+
type HeroBannerWithBadgeProps,
|
|
13
|
+
type ButtonConfig,
|
|
14
|
+
} from './HeroBannerWithBadge';
|
|
15
|
+
export {
|
|
16
|
+
InternalLink,
|
|
17
|
+
TopicClusterLinks,
|
|
18
|
+
RelatedLinks,
|
|
19
|
+
generateContextualLinks,
|
|
20
|
+
type InternalLinkProps,
|
|
21
|
+
type TopicClusterLinksProps,
|
|
22
|
+
type RelatedLinksProps,
|
|
23
|
+
} from './InternalLinkClusters';
|
|
24
|
+
export { NpsSurvey, type NpsSurveyProps } from './NpsSurvey';
|
|
25
|
+
export { SalesReport, type SalesReportProps } from './SalesReport';
|
|
26
|
+
export { TestimonialSlider, type TestimonialSliderProps } from './TestimonialSlider';
|
|
27
|
+
export {
|
|
28
|
+
UseCaseGrid,
|
|
29
|
+
type UseCaseGridProps,
|
|
30
|
+
type UseCase,
|
|
31
|
+
} from './UseCaseGrid';
|
|
32
|
+
export { WelcomeScreen, type WelcomeScreenProps } from './WelcomeScreen';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Type declarations for NativeWind className prop
|
|
2
|
+
import 'react-native';
|
|
3
|
+
|
|
4
|
+
declare module 'react-native' {
|
|
5
|
+
interface ViewProps {
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
interface TextProps {
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
interface ImageProps {
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
interface PressableProps {
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
interface TouchableOpacityProps {
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
interface ScrollViewProps {
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
}
|