crew-recommendation-ui 0.0.1 → 0.0.2
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/package.json +5 -6
- package/src/recommendation/components/Card.tsx +164 -143
- package/README.md +0 -156
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crew-recommendation-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Shared UI library for recommendation listing components - DLS tokens and React components",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -44,17 +44,16 @@
|
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": ">=17.0.0",
|
|
47
|
-
"react-
|
|
47
|
+
"react-native": ">=0.70.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/react": "^18.2.55",
|
|
51
|
-
"@types/react-
|
|
51
|
+
"@types/react-native": "^0.72.0",
|
|
52
52
|
"@vitejs/plugin-react": "^4.3.4",
|
|
53
|
+
"react-native": "^0.74.0",
|
|
53
54
|
"typescript": "~5.7.2",
|
|
54
55
|
"vite": "^6.3.1",
|
|
55
56
|
"vite-plugin-dts": "^4.5.0"
|
|
56
57
|
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"clsx": "^2.1.1"
|
|
59
|
-
}
|
|
58
|
+
"dependencies": {}
|
|
60
59
|
}
|
|
@@ -1,191 +1,212 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
View,
|
|
3
|
+
Text,
|
|
4
|
+
Image,
|
|
5
|
+
TouchableOpacity,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
} from 'react-native'
|
|
8
|
+
import { colors, spacing } from '../../dls/tokens'
|
|
9
|
+
import type { BaseRecommendationItem, RecommendationCardProps } from '../types'
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
|
-
* Base Recommendation Card Component
|
|
8
|
-
*
|
|
12
|
+
* Base Recommendation Card Component (React Native Web compatible)
|
|
13
|
+
*
|
|
9
14
|
* This is a generic card that can display any recommendation item.
|
|
10
|
-
*
|
|
15
|
+
* Works on both React Native and Web via react-native-web.
|
|
11
16
|
*/
|
|
12
17
|
export const Card = <T extends BaseRecommendationItem>({
|
|
13
18
|
item,
|
|
14
19
|
onSelect,
|
|
15
20
|
onExpand,
|
|
16
21
|
isSelected = false,
|
|
17
|
-
isExpanded = false,
|
|
18
22
|
}: RecommendationCardProps<T>) => {
|
|
19
|
-
const
|
|
23
|
+
const handlePress = () => {
|
|
20
24
|
if (onExpand) {
|
|
21
|
-
onExpand(item)
|
|
25
|
+
onExpand(item)
|
|
22
26
|
}
|
|
23
|
-
}
|
|
27
|
+
}
|
|
24
28
|
|
|
25
|
-
const handleSelect = (
|
|
26
|
-
e.stopPropagation();
|
|
29
|
+
const handleSelect = () => {
|
|
27
30
|
if (onSelect) {
|
|
28
|
-
onSelect(item)
|
|
31
|
+
onSelect(item)
|
|
29
32
|
}
|
|
30
|
-
}
|
|
33
|
+
}
|
|
31
34
|
|
|
32
35
|
// Get the first image URL
|
|
33
|
-
const imageUrl = item.image_signed_urls?.[0] || item.image?.[0]?.data
|
|
36
|
+
const imageUrl = item.image_signed_urls?.[0] || item.image?.[0]?.data
|
|
34
37
|
|
|
35
38
|
return (
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
style={{
|
|
44
|
-
display: 'flex',
|
|
45
|
-
flexDirection: 'column',
|
|
46
|
-
backgroundColor: isSelected ? colors.card.selected.dark : colors.card.background.dark,
|
|
47
|
-
borderRadius: 16,
|
|
48
|
-
overflow: 'hidden',
|
|
49
|
-
cursor: 'pointer',
|
|
50
|
-
transition: 'all 0.2s ease-in-out',
|
|
51
|
-
border: `1px solid ${isSelected ? colors.primary[500] : colors.card.border.dark}`,
|
|
52
|
-
}}
|
|
39
|
+
<TouchableOpacity
|
|
40
|
+
onPress={handlePress}
|
|
41
|
+
activeOpacity={0.8}
|
|
42
|
+
style={[
|
|
43
|
+
styles.container,
|
|
44
|
+
isSelected && styles.containerSelected,
|
|
45
|
+
]}
|
|
53
46
|
>
|
|
54
47
|
{/* Image Section */}
|
|
55
48
|
{imageUrl && (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}}
|
|
62
|
-
>
|
|
63
|
-
<img
|
|
64
|
-
src={imageUrl}
|
|
65
|
-
alt={item.title}
|
|
66
|
-
style={{
|
|
67
|
-
width: '100%',
|
|
68
|
-
height: '100%',
|
|
69
|
-
objectFit: 'cover',
|
|
70
|
-
}}
|
|
49
|
+
<View style={styles.imageContainer}>
|
|
50
|
+
<Image
|
|
51
|
+
source={{ uri: imageUrl }}
|
|
52
|
+
style={styles.image}
|
|
53
|
+
resizeMode="cover"
|
|
71
54
|
/>
|
|
72
|
-
</
|
|
55
|
+
</View>
|
|
73
56
|
)}
|
|
74
57
|
|
|
75
58
|
{/* Content Section */}
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
59
|
+
<View style={styles.content}>
|
|
60
|
+
{/* Recommended Badge */}
|
|
61
|
+
{item.recommended && (
|
|
62
|
+
<View style={styles.recommendedBadge}>
|
|
63
|
+
<Text style={styles.recommendedText}>★ Recommended</Text>
|
|
64
|
+
</View>
|
|
65
|
+
)}
|
|
66
|
+
|
|
84
67
|
{/* Title */}
|
|
85
|
-
<
|
|
86
|
-
style={{
|
|
87
|
-
...textStyles.cardTitle,
|
|
88
|
-
color: colors.neutral[0],
|
|
89
|
-
margin: 0,
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
68
|
+
<Text style={styles.title} numberOfLines={2}>
|
|
92
69
|
{item.title}
|
|
93
|
-
</
|
|
70
|
+
</Text>
|
|
94
71
|
|
|
95
72
|
{/* Subtitle */}
|
|
96
73
|
{item.subtitle && (
|
|
97
|
-
<
|
|
98
|
-
style={{
|
|
99
|
-
...textStyles.cardSubtitle,
|
|
100
|
-
color: colors.neutral[400],
|
|
101
|
-
margin: 0,
|
|
102
|
-
}}
|
|
103
|
-
>
|
|
74
|
+
<Text style={styles.subtitle} numberOfLines={1}>
|
|
104
75
|
{item.subtitle}
|
|
105
|
-
</
|
|
76
|
+
</Text>
|
|
106
77
|
)}
|
|
107
78
|
|
|
108
79
|
{/* Tags */}
|
|
109
80
|
{item.tags && item.tags.length > 0 && (
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}}
|
|
116
|
-
>
|
|
117
|
-
{item.tags.map((tag, index) => (
|
|
118
|
-
<span
|
|
119
|
-
key={index}
|
|
120
|
-
style={{
|
|
121
|
-
...textStyles.caption,
|
|
122
|
-
backgroundColor: colors.primary[900],
|
|
123
|
-
color: colors.primary[300],
|
|
124
|
-
padding: `${spacing[0.5]}px ${spacing[2]}px`,
|
|
125
|
-
borderRadius: 4,
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
{tag}
|
|
129
|
-
</span>
|
|
81
|
+
<View style={styles.tagsContainer}>
|
|
82
|
+
{item.tags.slice(0, 3).map((tag, index) => (
|
|
83
|
+
<View key={index} style={styles.tag}>
|
|
84
|
+
<Text style={styles.tagText}>{tag}</Text>
|
|
85
|
+
</View>
|
|
130
86
|
))}
|
|
131
|
-
</
|
|
87
|
+
</View>
|
|
132
88
|
)}
|
|
133
89
|
|
|
134
90
|
{/* Price Section */}
|
|
135
|
-
<
|
|
136
|
-
style={{
|
|
137
|
-
display: 'flex',
|
|
138
|
-
alignItems: 'baseline',
|
|
139
|
-
gap: spacing[2],
|
|
140
|
-
marginTop: spacing[2],
|
|
141
|
-
}}
|
|
142
|
-
>
|
|
143
|
-
<span
|
|
144
|
-
style={{
|
|
145
|
-
...textStyles.cardPrice,
|
|
146
|
-
color: colors.neutral[0],
|
|
147
|
-
}}
|
|
148
|
-
>
|
|
149
|
-
{item.price}
|
|
150
|
-
</span>
|
|
91
|
+
<View style={styles.priceContainer}>
|
|
92
|
+
<Text style={styles.price}>{item.price}</Text>
|
|
151
93
|
{item.originalPrice && (
|
|
152
|
-
<
|
|
153
|
-
style={{
|
|
154
|
-
...textStyles.bodySmall,
|
|
155
|
-
color: colors.neutral[500],
|
|
156
|
-
textDecoration: 'line-through',
|
|
157
|
-
}}
|
|
158
|
-
>
|
|
159
|
-
{item.originalPrice}
|
|
160
|
-
</span>
|
|
94
|
+
<Text style={styles.originalPrice}>{item.originalPrice}</Text>
|
|
161
95
|
)}
|
|
162
|
-
</
|
|
96
|
+
</View>
|
|
163
97
|
|
|
164
98
|
{/* Select Button */}
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
style={
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
color: colors.neutral[0],
|
|
172
|
-
border: 'none',
|
|
173
|
-
borderRadius: 8,
|
|
174
|
-
cursor: 'pointer',
|
|
175
|
-
...textStyles.label,
|
|
176
|
-
transition: 'background-color 0.2s ease-in-out',
|
|
177
|
-
}}
|
|
99
|
+
<TouchableOpacity
|
|
100
|
+
onPress={handleSelect}
|
|
101
|
+
style={[
|
|
102
|
+
styles.selectButton,
|
|
103
|
+
isSelected && styles.selectButtonSelected,
|
|
104
|
+
]}
|
|
178
105
|
>
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
export default Card;
|
|
106
|
+
<Text style={styles.selectButtonText}>
|
|
107
|
+
{isSelected ? '✓ Selected' : 'Select'}
|
|
108
|
+
</Text>
|
|
109
|
+
</TouchableOpacity>
|
|
110
|
+
</View>
|
|
111
|
+
</TouchableOpacity>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
189
114
|
|
|
115
|
+
const styles = StyleSheet.create({
|
|
116
|
+
container: {
|
|
117
|
+
backgroundColor: colors.card.background.dark,
|
|
118
|
+
borderRadius: 16,
|
|
119
|
+
overflow: 'hidden',
|
|
120
|
+
borderWidth: 1,
|
|
121
|
+
borderColor: colors.card.border.dark,
|
|
122
|
+
},
|
|
123
|
+
containerSelected: {
|
|
124
|
+
backgroundColor: colors.card.selected.dark,
|
|
125
|
+
borderColor: colors.primary[500],
|
|
126
|
+
},
|
|
127
|
+
imageContainer: {
|
|
128
|
+
width: '100%',
|
|
129
|
+
height: 180,
|
|
130
|
+
overflow: 'hidden',
|
|
131
|
+
},
|
|
132
|
+
image: {
|
|
133
|
+
width: '100%',
|
|
134
|
+
height: '100%',
|
|
135
|
+
},
|
|
136
|
+
content: {
|
|
137
|
+
padding: spacing[4],
|
|
138
|
+
gap: spacing[2],
|
|
139
|
+
},
|
|
140
|
+
recommendedBadge: {
|
|
141
|
+
backgroundColor: colors.warning[500],
|
|
142
|
+
paddingHorizontal: spacing[2],
|
|
143
|
+
paddingVertical: spacing[1],
|
|
144
|
+
borderRadius: 4,
|
|
145
|
+
alignSelf: 'flex-start',
|
|
146
|
+
},
|
|
147
|
+
recommendedText: {
|
|
148
|
+
fontSize: 12,
|
|
149
|
+
fontWeight: '600',
|
|
150
|
+
color: colors.neutral[900],
|
|
151
|
+
},
|
|
152
|
+
title: {
|
|
153
|
+
fontSize: 18,
|
|
154
|
+
fontWeight: '600',
|
|
155
|
+
color: colors.neutral[0],
|
|
156
|
+
},
|
|
157
|
+
subtitle: {
|
|
158
|
+
fontSize: 14,
|
|
159
|
+
color: colors.neutral[400],
|
|
160
|
+
},
|
|
161
|
+
tagsContainer: {
|
|
162
|
+
flexDirection: 'row',
|
|
163
|
+
flexWrap: 'wrap',
|
|
164
|
+
gap: spacing[1],
|
|
165
|
+
},
|
|
166
|
+
tag: {
|
|
167
|
+
backgroundColor: colors.primary[900],
|
|
168
|
+
paddingHorizontal: spacing[2],
|
|
169
|
+
paddingVertical: spacing[0.5],
|
|
170
|
+
borderRadius: 4,
|
|
171
|
+
},
|
|
172
|
+
tagText: {
|
|
173
|
+
fontSize: 12,
|
|
174
|
+
color: colors.primary[300],
|
|
175
|
+
},
|
|
176
|
+
priceContainer: {
|
|
177
|
+
flexDirection: 'row',
|
|
178
|
+
alignItems: 'baseline',
|
|
179
|
+
gap: spacing[2],
|
|
180
|
+
marginTop: spacing[2],
|
|
181
|
+
},
|
|
182
|
+
price: {
|
|
183
|
+
fontSize: 20,
|
|
184
|
+
fontWeight: '700',
|
|
185
|
+
color: colors.neutral[0],
|
|
186
|
+
},
|
|
187
|
+
originalPrice: {
|
|
188
|
+
fontSize: 14,
|
|
189
|
+
color: colors.neutral[500],
|
|
190
|
+
textDecorationLine: 'line-through',
|
|
191
|
+
},
|
|
192
|
+
selectButton: {
|
|
193
|
+
marginTop: spacing[2],
|
|
194
|
+
paddingVertical: spacing[2],
|
|
195
|
+
paddingHorizontal: spacing[4],
|
|
196
|
+
backgroundColor: colors.primary[600],
|
|
197
|
+
borderRadius: 8,
|
|
198
|
+
alignItems: 'center',
|
|
199
|
+
},
|
|
200
|
+
selectButtonSelected: {
|
|
201
|
+
backgroundColor: colors.success[600],
|
|
202
|
+
},
|
|
203
|
+
selectButtonText: {
|
|
204
|
+
fontSize: 14,
|
|
205
|
+
fontWeight: '600',
|
|
206
|
+
color: colors.neutral[0],
|
|
207
|
+
},
|
|
208
|
+
})
|
|
190
209
|
|
|
210
|
+
Card.displayName = 'RecommendationCard'
|
|
191
211
|
|
|
212
|
+
export default Card
|
package/README.md
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
# @crew-copilot/recommendation-ui
|
|
2
|
-
|
|
3
|
-
A shared UI library for recommendation listing components that can be used across web, mobile, and dashboard applications.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **DLS (Design Language System)**: Consistent design tokens for colors, spacing, typography
|
|
8
|
-
- **Recommendation Cards**: Pre-built components for hotels, flights, cabs, etc.
|
|
9
|
-
- **Platform Agnostic**: Works with React DOM and React Native Web
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
# From the monorepo root
|
|
15
|
-
yarn workspace @crew-copilot/recommendation-ui build
|
|
16
|
-
|
|
17
|
-
# Or install as a dependency in another package
|
|
18
|
-
yarn add @crew-copilot/recommendation-ui
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Usage
|
|
22
|
-
|
|
23
|
-
### Import DLS Tokens
|
|
24
|
-
|
|
25
|
-
```tsx
|
|
26
|
-
import { colors, spacing, textStyles, tokens } from '@crew-copilot/recommendation-ui/dls';
|
|
27
|
-
|
|
28
|
-
// Use tokens in your styles
|
|
29
|
-
const styles = {
|
|
30
|
-
container: {
|
|
31
|
-
padding: spacing[4],
|
|
32
|
-
backgroundColor: colors.neutral[900],
|
|
33
|
-
},
|
|
34
|
-
title: {
|
|
35
|
-
...textStyles.h2,
|
|
36
|
-
color: colors.neutral[0],
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### Import Recommendation Components
|
|
42
|
-
|
|
43
|
-
```tsx
|
|
44
|
-
import { Card, RecommendationCard } from '@crew-copilot/recommendation-ui/recommendation';
|
|
45
|
-
import type { HotelItem } from '@crew-copilot/recommendation-ui/recommendation';
|
|
46
|
-
|
|
47
|
-
const hotelData: HotelItem = {
|
|
48
|
-
id: '1',
|
|
49
|
-
type: 'hotel',
|
|
50
|
-
title: 'Luxury Resort & Spa',
|
|
51
|
-
subtitle: 'Beachfront property',
|
|
52
|
-
price: '₹12,500',
|
|
53
|
-
originalPrice: '₹15,000',
|
|
54
|
-
rating: '4.8',
|
|
55
|
-
location: 'Goa, India',
|
|
56
|
-
image_signed_urls: ['https://example.com/hotel.jpg'],
|
|
57
|
-
tags: ['Pool', 'Spa', 'Beach Access'],
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
function MyComponent() {
|
|
61
|
-
return (
|
|
62
|
-
<Card
|
|
63
|
-
item={hotelData}
|
|
64
|
-
onSelect={(item) => console.log('Selected:', item)}
|
|
65
|
-
onExpand={(item) => console.log('Expanded:', item)}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Import Everything
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
import {
|
|
75
|
-
// DLS
|
|
76
|
-
colors,
|
|
77
|
-
spacing,
|
|
78
|
-
tokens,
|
|
79
|
-
|
|
80
|
-
// Recommendation
|
|
81
|
-
Card,
|
|
82
|
-
RecommendationCard,
|
|
83
|
-
|
|
84
|
-
// Types
|
|
85
|
-
HotelItem,
|
|
86
|
-
FlightItem,
|
|
87
|
-
CabItem,
|
|
88
|
-
} from '@crew-copilot/recommendation-ui';
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Development
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
# Install dependencies
|
|
95
|
-
cd packages/recommendation-ui
|
|
96
|
-
yarn install
|
|
97
|
-
|
|
98
|
-
# Build the library
|
|
99
|
-
yarn build
|
|
100
|
-
|
|
101
|
-
# Watch mode for development
|
|
102
|
-
yarn dev
|
|
103
|
-
|
|
104
|
-
# Type check
|
|
105
|
-
yarn type-check
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Library Structure
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
src/
|
|
112
|
-
├── index.ts # Main entry point
|
|
113
|
-
├── dls/ # Design Language System
|
|
114
|
-
│ ├── index.ts
|
|
115
|
-
│ └── tokens/
|
|
116
|
-
│ ├── colors.ts
|
|
117
|
-
│ ├── spacing.ts
|
|
118
|
-
│ ├── typography.ts
|
|
119
|
-
│ └── index.ts
|
|
120
|
-
└── recommendation/ # Recommendation components
|
|
121
|
-
├── index.ts
|
|
122
|
-
├── types.ts
|
|
123
|
-
└── components/
|
|
124
|
-
├── Card.tsx
|
|
125
|
-
└── index.ts
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Consuming in Other Packages
|
|
129
|
-
|
|
130
|
-
### In crew-website (Vite)
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
// Already configured in yarn workspaces
|
|
134
|
-
import { Card } from '@crew-copilot/recommendation-ui';
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### In crew-app (React Native / Expo)
|
|
138
|
-
|
|
139
|
-
```tsx
|
|
140
|
-
// Add to package.json dependencies:
|
|
141
|
-
// "@crew-copilot/recommendation-ui": "workspace:*"
|
|
142
|
-
|
|
143
|
-
import { tokens, Card } from '@crew-copilot/recommendation-ui';
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Future Additions
|
|
147
|
-
|
|
148
|
-
- [ ] HotelCard component
|
|
149
|
-
- [ ] FlightCard component
|
|
150
|
-
- [ ] CabCard component
|
|
151
|
-
- [ ] VoteButton component
|
|
152
|
-
- [ ] BottomMsgBar component
|
|
153
|
-
- [ ] React Native Web primitives
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|