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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crew-recommendation-ui",
3
- "version": "0.0.1",
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-dom": ">=17.0.0"
47
+ "react-native": ">=0.70.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/react": "^18.2.55",
51
- "@types/react-dom": "^18.2.19",
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 React from 'react';
2
- import { clsx } from 'clsx';
3
- import { colors, spacing, textStyles } from '../../dls/tokens';
4
- import type { BaseRecommendationItem, RecommendationCardProps } from '../types';
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
- * Specific card types (Hotel, Flight, Cab) will extend this.
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 handleClick = () => {
23
+ const handlePress = () => {
20
24
  if (onExpand) {
21
- onExpand(item);
25
+ onExpand(item)
22
26
  }
23
- };
27
+ }
24
28
 
25
- const handleSelect = (e: React.MouseEvent) => {
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
- <div
37
- onClick={handleClick}
38
- className={clsx(
39
- 'recommendation-card',
40
- isSelected && 'recommendation-card--selected',
41
- isExpanded && 'recommendation-card--expanded'
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
- <div
57
- style={{
58
- width: '100%',
59
- height: 180,
60
- overflow: 'hidden',
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
- </div>
55
+ </View>
73
56
  )}
74
57
 
75
58
  {/* Content Section */}
76
- <div
77
- style={{
78
- padding: spacing[4],
79
- display: 'flex',
80
- flexDirection: 'column',
81
- gap: spacing[2],
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
- <h3
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
- </h3>
70
+ </Text>
94
71
 
95
72
  {/* Subtitle */}
96
73
  {item.subtitle && (
97
- <p
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
- </p>
76
+ </Text>
106
77
  )}
107
78
 
108
79
  {/* Tags */}
109
80
  {item.tags && item.tags.length > 0 && (
110
- <div
111
- style={{
112
- display: 'flex',
113
- flexWrap: 'wrap',
114
- gap: spacing[1],
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
- </div>
87
+ </View>
132
88
  )}
133
89
 
134
90
  {/* Price Section */}
135
- <div
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
- <span
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
- </div>
96
+ </View>
163
97
 
164
98
  {/* Select Button */}
165
- <button
166
- onClick={handleSelect}
167
- style={{
168
- marginTop: spacing[2],
169
- padding: `${spacing[2]}px ${spacing[4]}px`,
170
- backgroundColor: isSelected ? colors.success[600] : colors.primary[600],
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
- {isSelected ? '✓ Selected' : 'Select'}
180
- </button>
181
- </div>
182
- </div>
183
- );
184
- };
185
-
186
- Card.displayName = 'RecommendationCard';
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
-